summaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2019-11-17 20:45:02 +0100
committerAndreas Baumann <mail@andreasbaumann.cc>2019-11-17 20:45:02 +0100
commit8df3db566a3a937b45ebf11adb90d265e6f5e2d4 (patch)
tree4d541098d751d5a9acf8c12f6fb9f308ace066ac /vendor
downloadflyspray-8df3db566a3a937b45ebf11adb90d265e6f5e2d4.tar.xz
initial checking of customized version 1.0rc9
Diffstat (limited to 'vendor')
-rw-r--r--vendor/.htaccess9
-rw-r--r--vendor/adodb/adodb-php/.gitattributes17
-rw-r--r--vendor/adodb/adodb-php/.gitignore10
-rw-r--r--vendor/adodb/adodb-php/.mailmap4
-rw-r--r--vendor/adodb/adodb-php/LICENSE.md499
-rw-r--r--vendor/adodb/adodb-php/README.md103
-rw-r--r--vendor/adodb/adodb-php/adodb-active-record.inc.php1142
-rw-r--r--vendor/adodb/adodb-php/adodb-active-recordx.inc.php1497
-rw-r--r--vendor/adodb/adodb-php/adodb-csvlib.inc.php315
-rw-r--r--vendor/adodb/adodb-php/adodb-datadict.inc.php1033
-rw-r--r--vendor/adodb/adodb-php/adodb-error.inc.php265
-rw-r--r--vendor/adodb/adodb-php/adodb-errorhandler.inc.php80
-rw-r--r--vendor/adodb/adodb-php/adodb-errorpear.inc.php88
-rw-r--r--vendor/adodb/adodb-php/adodb-exceptions.inc.php81
-rw-r--r--vendor/adodb/adodb-php/adodb-iterator.inc.php26
-rw-r--r--vendor/adodb/adodb-php/adodb-lib.inc.php1259
-rw-r--r--vendor/adodb/adodb-php/adodb-memcache.lib.inc.php190
-rw-r--r--vendor/adodb/adodb-php/adodb-pager.inc.php289
-rw-r--r--vendor/adodb/adodb-php/adodb-pear.inc.php370
-rw-r--r--vendor/adodb/adodb-php/adodb-perf.inc.php1102
-rw-r--r--vendor/adodb/adodb-php/adodb-php4.inc.php16
-rw-r--r--vendor/adodb/adodb-php/adodb-time.inc.php1489
-rw-r--r--vendor/adodb/adodb-php/adodb-xmlschema.inc.php2226
-rw-r--r--vendor/adodb/adodb-php/adodb-xmlschema03.inc.php2408
-rw-r--r--vendor/adodb/adodb-php/adodb.inc.php5051
-rw-r--r--vendor/adodb/adodb-php/composer.json37
-rw-r--r--vendor/adodb/adodb-php/contrib/toxmlrpc.inc.php181
-rw-r--r--vendor/adodb/adodb-php/cute_icons_for_site/adodb.gifbin0 -> 1091 bytes
-rw-r--r--vendor/adodb/adodb-php/cute_icons_for_site/adodb2.gifbin0 -> 1458 bytes
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-access.inc.php95
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-db2.inc.php143
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-firebird.inc.php151
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-generic.inc.php127
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-ibase.inc.php67
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-informix.inc.php81
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-mssql.inc.php285
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-mssqlnative.inc.php369
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-mysql.inc.php183
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-oci8.inc.php300
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-postgres.inc.php484
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-sapdb.inc.php122
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-sqlite.inc.php90
-rw-r--r--vendor/adodb/adodb-php/datadict/datadict-sybase.inc.php230
-rw-r--r--vendor/adodb/adodb-php/docs/README.md17
-rw-r--r--vendor/adodb/adodb-php/docs/adodb.gifbin0 -> 1091 bytes
-rw-r--r--vendor/adodb/adodb-php/docs/adodb2.gifbin0 -> 1458 bytes
-rw-r--r--vendor/adodb/adodb-php/docs/changelog.md535
-rw-r--r--vendor/adodb/adodb-php/docs/changelog_v2.x.md531
-rw-r--r--vendor/adodb/adodb-php/docs/changelog_v3.x.md242
-rw-r--r--vendor/adodb/adodb-php/docs/changelog_v4+5.md109
-rw-r--r--vendor/adodb/adodb-php/docs/changelog_v4.x.md722
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-access.inc.php88
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ado.inc.php660
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ado5.inc.php708
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ado_access.inc.php50
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ado_mssql.inc.php150
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ads.inc.php776
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-borland_ibase.inc.php89
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-csv.inc.php209
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-db2.inc.php843
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-db2oci.inc.php226
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-db2ora.inc.php86
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-fbsql.inc.php267
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-firebird.inc.php73
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ibase.inc.php918
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-informix.inc.php41
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-informix72.inc.php525
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-ldap.inc.php428
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mssql.inc.php1194
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mssql_n.inc.php250
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mssqlnative.inc.php1200
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mssqlpo.inc.php58
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mysql.inc.php893
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mysqli.inc.php1295
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mysqlpo.inc.php128
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-mysqlt.inc.php137
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-netezza.inc.php157
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-oci8.inc.php1826
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-oci805.inc.php55
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-oci8po.inc.php286
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-oci8quercus.inc.php89
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbc.inc.php735
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbc_db2.inc.php369
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbc_mssql.inc.php365
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbc_oracle.inc.php108
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbtp.inc.php839
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-odbtp_unicode.inc.php35
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-oracle.inc.php343
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo.inc.php815
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_mssql.inc.php62
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_mysql.inc.php313
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_oci.inc.php102
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_pgsql.inc.php232
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_sqlite.inc.php206
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-pdo_sqlsrv.inc.php49
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-postgres.inc.php14
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-postgres64.inc.php1118
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-postgres7.inc.php388
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-postgres8.inc.php50
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-postgres9.inc.php32
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-proxy.inc.php33
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sapdb.inc.php185
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sqlanywhere.inc.php165
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sqlite.inc.php453
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sqlite3.inc.php440
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sqlitepo.inc.php58
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sybase.inc.php445
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-sybase_ase.inc.php120
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-text.inc.php388
-rw-r--r--vendor/adodb/adodb-php/drivers/adodb-vfp.inc.php103
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-ar.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-bg.inc.php36
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-ca.inc.php33
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-cn.inc.php33
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-cz.inc.php35
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-da.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-de.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-en.inc.php35
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-eo.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-es.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-fa.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-fr.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-hu.inc.php33
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-it.inc.php33
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-nl.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-pl.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-pt-br.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-ro.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-ru.inc.php34
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-sv.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-th.inc.php32
-rw-r--r--vendor/adodb/adodb-php/lang/adodb-uk.inc.php34
-rw-r--r--vendor/adodb/adodb-php/pear/Auth/Container/ADOdb.php406
-rw-r--r--vendor/adodb/adodb-php/pear/auth_adodb_example.php25
-rw-r--r--vendor/adodb/adodb-php/pear/readme.Auth.txt20
-rw-r--r--vendor/adodb/adodb-php/perf/perf-db2.inc.php108
-rw-r--r--vendor/adodb/adodb-php/perf/perf-informix.inc.php71
-rw-r--r--vendor/adodb/adodb-php/perf/perf-mssql.inc.php164
-rw-r--r--vendor/adodb/adodb-php/perf/perf-mssqlnative.inc.php164
-rw-r--r--vendor/adodb/adodb-php/perf/perf-mysql.inc.php316
-rw-r--r--vendor/adodb/adodb-php/perf/perf-oci8.inc.php703
-rw-r--r--vendor/adodb/adodb-php/perf/perf-postgres.inc.php154
-rw-r--r--vendor/adodb/adodb-php/pivottable.inc.php188
-rw-r--r--vendor/adodb/adodb-php/replicate/adodb-replicate.inc.php1180
-rw-r--r--vendor/adodb/adodb-php/replicate/replicate-steps.php137
-rw-r--r--vendor/adodb/adodb-php/replicate/test-tnb.php421
-rw-r--r--vendor/adodb/adodb-php/rsfilter.inc.php62
-rw-r--r--vendor/adodb/adodb-php/scripts/.gitignore2
-rw-r--r--vendor/adodb/adodb-php/scripts/TARADO5.BAT49
-rw-r--r--vendor/adodb/adodb-php/server.php100
-rw-r--r--vendor/adodb/adodb-php/session/adodb-compress-bzip2.php118
-rw-r--r--vendor/adodb/adodb-php/session/adodb-compress-gzip.php93
-rw-r--r--vendor/adodb/adodb-php/session/adodb-cryptsession.php27
-rw-r--r--vendor/adodb/adodb-php/session/adodb-cryptsession2.php27
-rw-r--r--vendor/adodb/adodb-php/session/adodb-encrypt-mcrypt.php109
-rw-r--r--vendor/adodb/adodb-php/session/adodb-encrypt-md5.php39
-rw-r--r--vendor/adodb/adodb-php/session/adodb-encrypt-secret.php48
-rw-r--r--vendor/adodb/adodb-php/session/adodb-encrypt-sha1.php31
-rw-r--r--vendor/adodb/adodb-php/session/adodb-sess.txt131
-rw-r--r--vendor/adodb/adodb-php/session/adodb-session-clob.php24
-rw-r--r--vendor/adodb/adodb-php/session/adodb-session-clob2.php24
-rw-r--r--vendor/adodb/adodb-php/session/adodb-session.php934
-rw-r--r--vendor/adodb/adodb-php/session/adodb-session2.php939
-rw-r--r--vendor/adodb/adodb-php/session/adodb-sessions.mysql.sql16
-rw-r--r--vendor/adodb/adodb-php/session/adodb-sessions.oracle.clob.sql15
-rw-r--r--vendor/adodb/adodb-php/session/adodb-sessions.oracle.sql16
-rw-r--r--vendor/adodb/adodb-php/session/crypt.inc.php157
-rw-r--r--vendor/adodb/adodb-php/session/old/adodb-cryptsession.php325
-rw-r--r--vendor/adodb/adodb-php/session/old/adodb-session-clob.php448
-rw-r--r--vendor/adodb/adodb-php/session/old/adodb-session.php439
-rw-r--r--vendor/adodb/adodb-php/session/old/crypt.inc.php63
-rw-r--r--vendor/adodb/adodb-php/session/session_schema.xml26
-rw-r--r--vendor/adodb/adodb-php/session/session_schema2.xml38
-rw-r--r--vendor/adodb/adodb-php/tests/benchmark.php86
-rw-r--r--vendor/adodb/adodb-php/tests/client.php199
-rw-r--r--vendor/adodb/adodb-php/tests/pdo.php92
-rw-r--r--vendor/adodb/adodb-php/tests/test-active-record.php140
-rw-r--r--vendor/adodb/adodb-php/tests/test-active-recs2.php76
-rw-r--r--vendor/adodb/adodb-php/tests/test-active-relations.php85
-rw-r--r--vendor/adodb/adodb-php/tests/test-active-relationsx.php418
-rw-r--r--vendor/adodb/adodb-php/tests/test-datadict.php251
-rw-r--r--vendor/adodb/adodb-php/tests/test-perf.php48
-rw-r--r--vendor/adodb/adodb-php/tests/test-pgblob.php86
-rw-r--r--vendor/adodb/adodb-php/tests/test-php5.php116
-rw-r--r--vendor/adodb/adodb-php/tests/test-xmlschema.php53
-rw-r--r--vendor/adodb/adodb-php/tests/test.php1781
-rw-r--r--vendor/adodb/adodb-php/tests/test2.php25
-rw-r--r--vendor/adodb/adodb-php/tests/test3.php44
-rw-r--r--vendor/adodb/adodb-php/tests/test4.php144
-rw-r--r--vendor/adodb/adodb-php/tests/test5.php48
-rw-r--r--vendor/adodb/adodb-php/tests/test_rs_array.php46
-rw-r--r--vendor/adodb/adodb-php/tests/testcache.php30
-rw-r--r--vendor/adodb/adodb-php/tests/testdatabases.inc.php478
-rw-r--r--vendor/adodb/adodb-php/tests/testgenid.php35
-rw-r--r--vendor/adodb/adodb-php/tests/testmssql.php77
-rw-r--r--vendor/adodb/adodb-php/tests/testoci8.php84
-rw-r--r--vendor/adodb/adodb-php/tests/testoci8cursor.php110
-rw-r--r--vendor/adodb/adodb-php/tests/testpaging.php87
-rw-r--r--vendor/adodb/adodb-php/tests/testpear.php35
-rw-r--r--vendor/adodb/adodb-php/tests/testsessions.php100
-rw-r--r--vendor/adodb/adodb-php/tests/time.php16
-rw-r--r--vendor/adodb/adodb-php/tests/tmssql.php79
-rw-r--r--vendor/adodb/adodb-php/tests/xmlschema-mssql.xml34
-rw-r--r--vendor/adodb/adodb-php/tests/xmlschema.xml33
-rw-r--r--vendor/adodb/adodb-php/toexport.inc.php136
-rw-r--r--vendor/adodb/adodb-php/tohtml.inc.php201
-rw-r--r--vendor/adodb/adodb-php/xmlschema.dtd39
-rw-r--r--vendor/adodb/adodb-php/xmlschema03.dtd43
-rw-r--r--vendor/adodb/adodb-php/xsl/convert-0.1-0.2.xsl205
-rw-r--r--vendor/adodb/adodb-php/xsl/convert-0.1-0.3.xsl221
-rw-r--r--vendor/adodb/adodb-php/xsl/convert-0.2-0.1.xsl207
-rw-r--r--vendor/adodb/adodb-php/xsl/convert-0.2-0.3.xsl281
-rw-r--r--vendor/adodb/adodb-php/xsl/remove-0.2.xsl54
-rw-r--r--vendor/adodb/adodb-php/xsl/remove-0.3.xsl54
-rw-r--r--vendor/autoload.php7
-rw-r--r--vendor/composer/ClassLoader.php445
-rw-r--r--vendor/composer/LICENSE21
-rw-r--r--vendor/composer/autoload_classmap.php32
-rw-r--r--vendor/composer/autoload_files.php13
-rw-r--r--vendor/composer/autoload_namespaces.php12
-rw-r--r--vendor/composer/autoload_psr4.php12
-rw-r--r--vendor/composer/autoload_real.php70
-rw-r--r--vendor/composer/autoload_static.php103
-rw-r--r--vendor/composer/installed.json473
-rw-r--r--vendor/dapphp/securimage/.gitattributes5
-rw-r--r--vendor/dapphp/securimage/AHGBold.ttfbin0 -> 144556 bytes
-rw-r--r--vendor/dapphp/securimage/LICENSE.txt25
-rw-r--r--vendor/dapphp/securimage/README.FONT.txt12
-rw-r--r--vendor/dapphp/securimage/README.md244
-rw-r--r--vendor/dapphp/securimage/README.txt222
-rw-r--r--vendor/dapphp/securimage/WavFile.php1913
-rw-r--r--vendor/dapphp/securimage/audio/.htaccess11
-rw-r--r--vendor/dapphp/securimage/composer.json27
-rw-r--r--vendor/dapphp/securimage/config.inc.php1
-rw-r--r--vendor/dapphp/securimage/database/.htaccess11
-rw-r--r--vendor/dapphp/securimage/database/index.html1
-rw-r--r--vendor/dapphp/securimage/database/securimage.sq3bin0 -> 4096 bytes
-rw-r--r--vendor/dapphp/securimage/images/audio_icon.pngbin0 -> 1684 bytes
-rw-r--r--vendor/dapphp/securimage/images/loading.pngbin0 -> 1136 bytes
-rw-r--r--vendor/dapphp/securimage/images/refresh.pngbin0 -> 4835 bytes
-rw-r--r--vendor/dapphp/securimage/securimage.css41
-rw-r--r--vendor/dapphp/securimage/securimage.js252
-rw-r--r--vendor/dapphp/securimage/securimage.php3468
-rw-r--r--vendor/dapphp/securimage/securimage_play.php70
-rw-r--r--vendor/dapphp/securimage/securimage_show.php79
-rw-r--r--vendor/dapphp/securimage/words/words.txt15457
-rw-r--r--vendor/ezyang/htmlpurifier/CREDITS9
-rw-r--r--vendor/ezyang/htmlpurifier/INSTALL373
-rw-r--r--vendor/ezyang/htmlpurifier/INSTALL.fr.utf860
-rw-r--r--vendor/ezyang/htmlpurifier/LICENSE504
-rw-r--r--vendor/ezyang/htmlpurifier/NEWS1190
-rw-r--r--vendor/ezyang/htmlpurifier/README.md29
-rw-r--r--vendor/ezyang/htmlpurifier/TODO150
-rw-r--r--vendor/ezyang/htmlpurifier/VERSION1
-rw-r--r--vendor/ezyang/htmlpurifier/WHATSNEW13
-rw-r--r--vendor/ezyang/htmlpurifier/WYSIWYG20
-rw-r--r--vendor/ezyang/htmlpurifier/composer.json25
-rw-r--r--vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php91
-rw-r--r--vendor/ezyang/htmlpurifier/extras/FSTools.php164
-rw-r--r--vendor/ezyang/htmlpurifier/extras/FSTools/File.php141
-rw-r--r--vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php11
-rw-r--r--vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php15
-rw-r--r--vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php23
-rw-r--r--vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php31
-rw-r--r--vendor/ezyang/htmlpurifier/extras/README32
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php24
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php4
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php234
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php30
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.php292
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php228
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php71
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php148
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php144
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php136
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php34
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php111
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php157
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php161
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php48
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php77
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php176
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php219
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php32
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php77
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php112
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php71
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php84
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php54
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php46
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php77
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php73
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php48
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php48
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php51
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php38
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php113
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php72
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php60
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php70
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php76
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php91
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php86
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php53
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php21
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php111
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php20
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php29
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php138
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php45
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php89
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php60
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php28
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php27
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php28
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php47
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php26
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php68
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php47
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php61
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php31
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php45
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php33
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php41
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php52
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php28
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php79
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php23
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php45
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php37
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php37
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php27
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php96
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php178
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php124
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php491
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php52
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php67
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php102
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php38
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php92
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php45
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php118
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php110
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php224
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php920
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php176
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php48
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php144
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php47
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php89
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php58
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php226
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php248
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php130
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.serbin0 -> 15923 bytes
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt19
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt5
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt31
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt46
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt18
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt18
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt29
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt17
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt7
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt19
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt36
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt34
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt29
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt74
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt19
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt23
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt20
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt18
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt23
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt33
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt21
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt20
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt7
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt24
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt8
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt7
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt18
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt17
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt14
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt19
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt13
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt83
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt17
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt30
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt22
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini3
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php170
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php95
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php55
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php129
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php112
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php78
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php85
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in82
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php76
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php311
-rwxr-xr-xvendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README3
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php106
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php73
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php142
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php216
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php617
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php48
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser1
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php285
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php244
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php74
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php341
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php65
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php286
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php493
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php284
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php31
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php55
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php190
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php51
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php49
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php186
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php51
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php26
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php20
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php62
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php42
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php36
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php62
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php73
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php33
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php75
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php28
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php24
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php21
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php21
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php87
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php230
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php33
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php34
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php43
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php16
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php26
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php179
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php20
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php467
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php57
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php283
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php356
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php64
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php71
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php112
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php84
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php124
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php204
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php9
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php12
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php55
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php209
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php162
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php382
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php328
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php539
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php4788
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php49
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php36
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php59
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php54
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php111
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php218
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js5
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php451
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php324
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php122
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php42
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php56
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php26
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php30
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php17
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php181
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php659
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php207
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php45
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php47
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php136
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php37
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php114
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php100
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php38
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php15
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php24
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php10
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php68
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php53
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php118
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php316
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php112
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php74
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php54
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php25
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php22
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php46
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php158
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php115
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php68
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php71
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php102
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php136
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php44
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php58
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php36
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php18
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php40
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php35
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php32
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php46
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php81
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php307
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php198
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php130
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php38
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php11
-rw-r--r--vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php157
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/.htaccess1
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/PH5P.patch102
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/PH5P.php3889
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/add-vimline.php130
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/common.php25
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/compile-doxygen.sh11
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/config-scanner.php155
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php42
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/flush.php30
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php75
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/generate-includes.php192
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.php22
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php45
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/generate-standalone.php159
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/merge-library.php11
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.php71
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php32
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php32
-rwxr-xr-xvendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh5
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php37
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/rename-config.php84
-rw-r--r--vendor/ezyang/htmlpurifier/maintenance/update-config.php34
-rw-r--r--vendor/ezyang/htmlpurifier/package.php61
-rw-r--r--vendor/ezyang/htmlpurifier/phpdoc.ini102
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/modx.txt112
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore2
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/Changelog27
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL84
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/README45
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php57
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php316
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/info.txt18
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php30
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php31
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/settings.php64
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php95
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php22
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php79
-rw-r--r--vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php29
-rw-r--r--vendor/ezyang/htmlpurifier/release1-update.php110
-rw-r--r--vendor/ezyang/htmlpurifier/release2-tag.php22
-rw-r--r--vendor/ezyang/htmlpurifier/test-settings.sample.php74
-rw-r--r--vendor/ezyang/htmlpurifier/test-settings.travis.php72
-rw-r--r--vendor/ezyang/htmlpurifier/tests/path2class.func.php15
-rw-r--r--vendor/guzzle/guzzle/.gitignore27
-rw-r--r--vendor/guzzle/guzzle/.travis.yml17
-rw-r--r--vendor/guzzle/guzzle/CHANGELOG.md751
-rw-r--r--vendor/guzzle/guzzle/LICENSE19
-rw-r--r--vendor/guzzle/guzzle/README.md57
-rw-r--r--vendor/guzzle/guzzle/UPGRADING.md537
-rw-r--r--vendor/guzzle/guzzle/build.xml45
-rw-r--r--vendor/guzzle/guzzle/composer.json82
-rw-r--r--vendor/guzzle/guzzle/docs/Makefile153
-rw-r--r--vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json176
-rw-r--r--vendor/guzzle/guzzle/docs/_static/guzzle-icon.pngbin0 -> 803 bytes
-rw-r--r--vendor/guzzle/guzzle/docs/_static/homepage.css122
-rw-r--r--vendor/guzzle/guzzle/docs/_static/logo.pngbin0 -> 247678 bytes
-rw-r--r--vendor/guzzle/guzzle/docs/_static/prettify.css41
-rw-r--r--vendor/guzzle/guzzle/docs/_static/prettify.js28
-rw-r--r--vendor/guzzle/guzzle/docs/_templates/index.html106
-rw-r--r--vendor/guzzle/guzzle/docs/_templates/leftbar.html0
-rw-r--r--vendor/guzzle/guzzle/docs/_templates/nav_links.html5
-rw-r--r--vendor/guzzle/guzzle/docs/batching/batching.rst183
-rw-r--r--vendor/guzzle/guzzle/docs/docs.rst73
-rw-r--r--vendor/guzzle/guzzle/docs/getting-started/faq.rst29
-rw-r--r--vendor/guzzle/guzzle/docs/getting-started/installation.rst154
-rw-r--r--vendor/guzzle/guzzle/docs/getting-started/overview.rst85
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/client.rst569
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst151
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/http-redirects.rst99
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/request.rst667
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/response.rst141
-rw-r--r--vendor/guzzle/guzzle/docs/http-client/uri-templates.rst52
-rw-r--r--vendor/guzzle/guzzle/docs/index.rst5
-rw-r--r--vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst97
-rw-r--r--vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst149
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/async-plugin.rst18
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst22
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst169
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst33
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst93
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst32
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/history-plugin.rst24
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/log-plugin.rst69
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst29
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst27
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst30
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc9
-rw-r--r--vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst59
-rw-r--r--vendor/guzzle/guzzle/docs/requirements.txt2
-rw-r--r--vendor/guzzle/guzzle/docs/testing/unit-testing.rst201
-rw-r--r--vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst619
-rw-r--r--vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst316
-rw-r--r--vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst659
-rw-r--r--vendor/guzzle/guzzle/phar-stub.php16
-rw-r--r--vendor/guzzle/guzzle/phing/build.properties.dist16
-rw-r--r--vendor/guzzle/guzzle/phing/imports/dependencies.xml33
-rw-r--r--vendor/guzzle/guzzle/phing/imports/deploy.xml142
-rw-r--r--vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php152
-rw-r--r--vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php338
-rw-r--r--vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php385
-rw-r--r--vendor/guzzle/guzzle/phpunit.xml.dist48
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php66
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php92
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php199
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php39
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php40
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php75
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchInterface.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchRequestTransfer.php65
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php47
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php16
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/Exception/BatchTransferException.php90
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php50
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php60
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php39
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php38
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json31
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php21
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php117
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php55
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/ClosureCacheAdapter.php57
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php41
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php31
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/Zf1CacheAdapter.php44
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php41
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php49
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php403
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Event.php52
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/ExceptionCollection.php108
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php8
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/RuntimeException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Exception/UnexpectedValueException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/FromConfigInterface.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/HasDispatcherInterface.php54
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/ToArrayInterface.php16
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/Version.php29
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Common/composer.json20
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php221
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php229
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Client.php524
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php223
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php464
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php423
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php58
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiProxy.php150
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php66
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php147
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php201
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php73
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/BadResponseException.php69
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php8
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CouldNotRewindStreamException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CurlException.php101
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php10
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/MultiTransferException.php145
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php39
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php8
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Exception/TooManyRedirectsException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/IoEmittingEntityBody.php83
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php220
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php247
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php137
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php182
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php121
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php108
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php26
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderInterface.php83
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/Link.php93
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php102
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFile.php124
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php83
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Request.php638
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php359
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php105
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php318
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Message/Response.php968
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php962
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php20
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php22
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php22
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/QueryString.php297
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php122
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php250
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem3870
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php157
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Url.php554
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/composer.json32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php38
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Inflection/InflectorInterface.php27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Inflection/MemoizingInflector.php70
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php59
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json26
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php56
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php36
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php34
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md25
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php16
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php34
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php23
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/MessageFormatter.php179
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php34
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php36
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php24
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php21
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Log/composer.json29
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php131
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php33
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Message/AbstractMessageParser.php58
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php110
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Message/PeclHttpMessageParser.php48
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php75
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php26
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplate.php254
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php21
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParser.php48
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Parser/composer.json19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php84
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php91
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php40
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php76
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php126
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CallbackBackoffStrategy.php47
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php34
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php28
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php25
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/HttpBackoffStrategy.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php36
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php25
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php36
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json28
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php11
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php353
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php43
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php53
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php46
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php266
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php174
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json28
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php538
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php237
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php85
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/FileCookieJar.php65
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php70
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php46
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php22
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponsePlugin.php72
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php163
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php161
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json28
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php57
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php88
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php245
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php306
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json44
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php177
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php189
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php40
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderLoader.php89
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php46
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Client.php297
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php68
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/AbstractCommand.php390
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php41
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php128
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/CreateResponseClassEvent.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php169
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php55
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php39
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php154
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php47
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php21
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/MapFactory.php27
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php71
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php69
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php58
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php44
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php63
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php24
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php31
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/ResponseBodyVisitor.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php252
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php26
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/BodyVisitor.php23
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php50
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php93
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php23
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php46
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/StatusCodeVisitor.php23
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php151
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php138
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php89
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php195
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php21
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseClassInterface.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseParserInterface.php18
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/ConfigLoaderInterface.php22
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/Operation.php547
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php159
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/Parameter.php925
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php156
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php291
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php271
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php106
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionLoader.php64
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php28
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandTransferException.php119
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/InconsistentClientTransferException.php38
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php9
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceBuilderException.php7
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceNotFoundException.php5
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ValidationException.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php37
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php67
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php34
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php64
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php254
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php111
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php60
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorInterface.php61
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Service/composer.json29
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php284
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php289
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php218
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Stream/StreamRequestFactoryInterface.php24
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Stream/composer.json30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php33
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php86
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php36
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php52
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php83
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php80
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php24
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php91
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php45
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php40
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php26
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php45
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php64
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php68
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php94
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php20
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php58
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php63
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php529
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php62
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php66
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php27
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/GuzzleTestCase.php235
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php34
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php249
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php601
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php947
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php110
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php455
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php39
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php67
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php182
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php27
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php66
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php51
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php47
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php136
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php434
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php29
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php63
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php135
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php115
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php162
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php88
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php616
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php639
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php677
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php31
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php32
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php233
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php81
-rwxr-xr-xvendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php277
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php191
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php67
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php303
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js146
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php37
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php46
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php45
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php29
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php52
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php28
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php28
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php28
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php23
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php143
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php25
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php51
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php25
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php11
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockMulti.php11
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php65
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php7
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserProvider.php381
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php22
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php225
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php58
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php36
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php33
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php113
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php27
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php106
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php93
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php86
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php110
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php297
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php31
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php20
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php36
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php23
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php47
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php32
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php441
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php72
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php193
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php40
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php248
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php19
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php19
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php385
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php63
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php134
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php223
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php39
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php137
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php140
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php95
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php97
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php120
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php199
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php345
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php149
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php177
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php317
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php43
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php320
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php16
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php54
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php445
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php122
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php59
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php76
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php124
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php49
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php37
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php68
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php110
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php63
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php48
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php60
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php33
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php54
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php48
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php20
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php558
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php29
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php98
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php157
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php21
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php431
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php53
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php102
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php335
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php308
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php411
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php61
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php326
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php177
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php240
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php66
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php15
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php17
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php31
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php32
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php30
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php7
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/MockClient.php36
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php42
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php37
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php40
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php65
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php41
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php184
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php172
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php189
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt0
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json3
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json3
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json8
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json3
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response3
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json18
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json11
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json71
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json40
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json7
-rw-r--r--vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json40
-rw-r--r--vendor/guzzle/guzzle/tests/bootstrap.php10
-rw-r--r--vendor/jamiebicknell/Sparkline/LICENSE.md21
-rw-r--r--vendor/jamiebicknell/Sparkline/README.md101
-rw-r--r--vendor/jamiebicknell/Sparkline/composer.json21
-rw-r--r--vendor/jamiebicknell/Sparkline/sparkline.php114
-rw-r--r--vendor/league/oauth2-client/CHANGELOG.md154
-rw-r--r--vendor/league/oauth2-client/CONTRIBUTING.md42
-rw-r--r--vendor/league/oauth2-client/LICENSE21
-rw-r--r--vendor/league/oauth2-client/README.md247
-rw-r--r--vendor/league/oauth2-client/composer.json44
-rw-r--r--vendor/league/oauth2-client/src/Entity/User.php117
-rw-r--r--vendor/league/oauth2-client/src/Exception/IDPException.php69
-rw-r--r--vendor/league/oauth2-client/src/Grant/AuthorizationCode.php27
-rw-r--r--vendor/league/oauth2-client/src/Grant/ClientCredentials.php25
-rw-r--r--vendor/league/oauth2-client/src/Grant/GrantInterface.php12
-rw-r--r--vendor/league/oauth2-client/src/Grant/Password.php33
-rw-r--r--vendor/league/oauth2-client/src/Grant/RefreshToken.php29
-rw-r--r--vendor/league/oauth2-client/src/Provider/AbstractProvider.php399
-rw-r--r--vendor/league/oauth2-client/src/Provider/Eventbrite.php51
-rw-r--r--vendor/league/oauth2-client/src/Provider/Facebook.php103
-rw-r--r--vendor/league/oauth2-client/src/Provider/Github.php99
-rw-r--r--vendor/league/oauth2-client/src/Provider/Google.php124
-rw-r--r--vendor/league/oauth2-client/src/Provider/Instagram.php59
-rw-r--r--vendor/league/oauth2-client/src/Provider/LinkedIn.php76
-rw-r--r--vendor/league/oauth2-client/src/Provider/Microsoft.php69
-rw-r--r--vendor/league/oauth2-client/src/Provider/ProviderInterface.php36
-rw-r--r--vendor/league/oauth2-client/src/Provider/Vkontakte.php99
-rwxr-xr-xvendor/league/oauth2-client/src/Token/AccessToken.php77
-rw-r--r--vendor/swiftmailer/swiftmailer/.gitattributes9
-rw-r--r--vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md19
-rw-r--r--vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md14
-rw-r--r--vendor/swiftmailer/swiftmailer/.gitignore8
-rw-r--r--vendor/swiftmailer/swiftmailer/.php_cs.dist15
-rw-r--r--vendor/swiftmailer/swiftmailer/.travis.yml31
-rw-r--r--vendor/swiftmailer/swiftmailer/CHANGES287
-rw-r--r--vendor/swiftmailer/swiftmailer/LICENSE19
-rw-r--r--vendor/swiftmailer/swiftmailer/README15
-rw-r--r--vendor/swiftmailer/swiftmailer/VERSION1
-rw-r--r--vendor/swiftmailer/swiftmailer/composer.json37
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/headers.rst739
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/help-resources.rst44
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/including-the-files.rst46
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/index.rst16
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/installing.rst89
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/introduction.rst135
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/japanese.rst22
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/messages.rst1058
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/overview.rst159
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/plugins.rst385
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/sending.rst571
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/uml/Encoders.grafflebin0 -> 3503 bytes
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/uml/Mime.grafflebin0 -> 5575 bytes
-rw-r--r--vendor/swiftmailer/swiftmailer/doc/uml/Transports.grafflebin0 -> 3061 bytes
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift.php80
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php71
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php181
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php182
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php231
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php42
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php67
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php97
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php84
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php176
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php26
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php124
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php89
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php293
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php267
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php63
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php373
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php27
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php69
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php28
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php58
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php300
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php92
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php62
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php38
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php83
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php18
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php63
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php129
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php31
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php156
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php27
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php46
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php208
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php57
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php75
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php29
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.php105
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php206
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php321
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php51
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php115
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php127
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php47
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php114
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php55
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php110
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php289
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php149
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php34
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php104
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php123
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php162
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php134
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php98
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php64
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Grammar.php176
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php93
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php55
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php78
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderSet.php169
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php501
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php125
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php180
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php351
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php133
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php258
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php143
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php112
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php223
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php117
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php212
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php34
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php193
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php414
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php655
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php846
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php59
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php36
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php46
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php141
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php164
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php31
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php204
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php69
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php36
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php142
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php72
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php58
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php74
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php31
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.php27
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php273
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php213
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php61
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php59
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php39
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php200
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php100
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php27
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php27
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php23
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php20
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php33
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php712
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php524
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php190
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php436
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php58
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php53
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php47
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php35
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php170
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php70
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php29
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php54
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php499
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php81
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php51
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php725
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php50
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php70
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php263
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php35
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php86
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php411
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php88
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php67
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php183
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php297
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php93
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php160
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php39
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php36
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.php117
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php334
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php29
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/classes/Swift/Validate.php43
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php23
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php9
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php123
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php76
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/mime_types.php1007
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/preferences.php25
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/swift_init.php28
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/swift_required.php30
-rw-r--r--vendor/swiftmailer/swiftmailer/lib/swift_required_pear.php30
-rwxr-xr-xvendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.php193
-rw-r--r--vendor/swiftmailer/swiftmailer/phpunit.xml.dist39
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php62
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/StreamCollector.php11
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php46
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php34
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt11
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt22
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt45
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt3
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv15
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub6
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt1
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.pngbin0 -> 3194 bytes
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zipbin0 -> 202 bytes
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl1
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt21
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh40
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key27
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default37
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/AttachmentAcceptanceTest.php12
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php162
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php179
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php12
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php54
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php50
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php30
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php173
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php173
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php55
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php123
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php56
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php88
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php88
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php160
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php136
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php127
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php1249
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php15
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php131
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php33
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php26
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php67
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php40
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php39
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bootstrap.php21
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php42
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php20
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php38
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php21
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php75
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php73
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php192
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php38
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php110
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php38
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php36
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php20
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php71
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php19
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php67
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default63
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/AttachmentSmokeTest.php33
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php23
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php31
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php40
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php201
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php43
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php52
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php358
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php176
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php173
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php400
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php141
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php34
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php32
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php38
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php97
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php142
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php30
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php41
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php240
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php73
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php42
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php145
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php129
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php1092
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php318
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php323
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php171
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php516
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php55
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php13
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php221
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php69
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php189
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php327
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php398
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php77
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php355
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php231
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php166
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php737
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php827
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php9
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/AntiFloodPluginTest.php93
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php128
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php267
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php188
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php65
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php24
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php101
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php183
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php86
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php64
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php54
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php102
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php225
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php45
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php554
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php129
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php36
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php59
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php558
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php1249
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php64
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php64
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php213
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php67
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php165
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php529
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php297
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php518
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php749
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php533
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php151
-rw-r--r--vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php43
-rw-r--r--vendor/symfony/event-dispatcher/.gitignore3
-rw-r--r--vendor/symfony/event-dispatcher/CHANGELOG.md23
-rw-r--r--vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php183
-rw-r--r--vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php375
-rw-r--r--vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php34
-rw-r--r--vendor/symfony/event-dispatcher/Debug/WrappedListener.php71
-rw-r--r--vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php100
-rw-r--r--vendor/symfony/event-dispatcher/Event.php120
-rw-r--r--vendor/symfony/event-dispatcher/EventDispatcher.php198
-rw-r--r--vendor/symfony/event-dispatcher/EventDispatcherInterface.php81
-rw-r--r--vendor/symfony/event-dispatcher/EventSubscriberInterface.php46
-rw-r--r--vendor/symfony/event-dispatcher/GenericEvent.php175
-rw-r--r--vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php91
-rw-r--r--vendor/symfony/event-dispatcher/LICENSE19
-rw-r--r--vendor/symfony/event-dispatcher/README.md15
-rw-r--r--vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php398
-rw-r--r--vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php277
-rw-r--r--vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php254
-rw-r--r--vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php154
-rw-r--r--vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php22
-rw-r--r--vendor/symfony/event-dispatcher/Tests/EventTest.php97
-rw-r--r--vendor/symfony/event-dispatcher/Tests/GenericEventTest.php136
-rw-r--r--vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php106
-rw-r--r--vendor/symfony/event-dispatcher/composer.json44
-rw-r--r--vendor/symfony/event-dispatcher/phpunit.xml.dist31
1585 files changed, 239864 insertions, 0 deletions
diff --git a/vendor/.htaccess b/vendor/.htaccess
new file mode 100644
index 0000000..ce1eaea
--- /dev/null
+++ b/vendor/.htaccess
@@ -0,0 +1,9 @@
+# Apache 2.4
+<IfModule mod_authz_core.c>
+ Require all denied
+</IfModule>
+
+# Apache 2.2
+<IfModule !mod_authz_core.c>
+ Deny from all
+</IfModule>
diff --git a/vendor/adodb/adodb-php/.gitattributes b/vendor/adodb/adodb-php/.gitattributes
new file mode 100644
index 0000000..2cb098d
--- /dev/null
+++ b/vendor/adodb/adodb-php/.gitattributes
@@ -0,0 +1,17 @@
+# Default behavior
+* text=auto
+
+# Text files to be normalized to lf line endings
+*.php text
+*.htm* text
+*.sql text
+*.txt text
+*.xml text
+*.xsl text
+
+# Windows-only files
+*.bat eol=crlf
+
+# Binary files
+*.gif binary
+
diff --git a/vendor/adodb/adodb-php/.gitignore b/vendor/adodb/adodb-php/.gitignore
new file mode 100644
index 0000000..0d777b2
--- /dev/null
+++ b/vendor/adodb/adodb-php/.gitignore
@@ -0,0 +1,10 @@
+# IDE/Editor temporary files
+*~
+*.swp
+*.bak
+*.tmp
+.project
+.settings/
+/nbproject/
+.idea/
+
diff --git a/vendor/adodb/adodb-php/.mailmap b/vendor/adodb/adodb-php/.mailmap
new file mode 100644
index 0000000..1f2f7e7
--- /dev/null
+++ b/vendor/adodb/adodb-php/.mailmap
@@ -0,0 +1,4 @@
+Andreas Fernandez <a.fernandez@scripting-base.de> <andreas.fernandez@aspedia.de>
+Mike Benoit <mikeb@timetrex.com> MikeB <ipso@snappymail.ca>
+Mike Benoit <mikeb@timetrex.com> mike.benoit
+
diff --git a/vendor/adodb/adodb-php/LICENSE.md b/vendor/adodb/adodb-php/LICENSE.md
new file mode 100644
index 0000000..26956d3
--- /dev/null
+++ b/vendor/adodb/adodb-php/LICENSE.md
@@ -0,0 +1,499 @@
+ADOdb License
+=============
+
+The ADOdb Library is dual-licensed, released under both the
+[BSD 3-clause](#bsd-3-clause-license) and the
+[GNU Lesser General Public License (LGPL) v2.1](#gnu-lesser-general-public-license)
+or, at your option, any later version.
+
+In plain English, you do not need to distribute your application in source code form,
+nor do you need to distribute ADOdb source code, provided you follow the rest of
+terms of the BSD license.
+
+For more information about ADOdb, visit http://adodb.org/
+
+BSD 3-Clause License
+--------------------
+
+(c) 2000-2013 John Lim (jlim@natsoft.com)
+(c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+### DISCLAIMER
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+GNU LESSER GENERAL PUBLIC LICENSE
+---------------------------------
+
+_Version 2.1, February 1999_
+_Copyright © 1991, 1999 Free Software Foundation, Inc._
+_51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+_This is the first released version of the Lesser GPL. It also counts
+as the successor of the GNU Library Public License, version 2, hence
+the version number 2.1._
+
+### Preamble
+
+The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: **(1)** we copyright the
+library, and **(2)** we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+We call this license the “Lesser” General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+“work based on the library” and a “work that uses the library”. The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+**0.** This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called “this License”).
+Each licensee is addressed as “you”.
+
+A “library” means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+The “Library”, below, refers to any such software library or work
+which has been distributed under these terms. A “work based on the
+Library” means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term “modification”.)
+
+“Source code” for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+**1.** You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+**2.** You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+* **a)** The modified work must itself be a software library.
+* **b)** You must cause the files modified to carry prominent notices
+stating that you changed the files and the date of any change.
+* **c)** You must cause the whole of the work to be licensed at no
+charge to all third parties under the terms of this License.
+* **d)** If a facility in the modified Library refers to a function or a
+table of data to be supplied by an application program that uses
+the facility, other than as an argument passed when the facility
+is invoked, then you must make a good faith effort to ensure that,
+in the event an application does not supply such function or
+table, the facility still operates, and performs whatever part of
+its purpose remains meaningful.
+(For example, a function in a library to compute square roots has
+a purpose that is entirely well-defined independent of the
+application. Therefore, Subsection 2d requires that any
+application-supplied function or table used by this function must
+be optional: if the application does not supply it, the square
+root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+**3.** You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+**4.** You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+**5.** A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a “work that uses the Library”. Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+However, linking a “work that uses the Library” with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a “work that uses the
+library”. The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+When a “work that uses the Library” uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+**6.** As an exception to the Sections above, you may also combine or
+link a “work that uses the Library” with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+* **a)** Accompany the work with the complete corresponding
+machine-readable source code for the Library including whatever
+changes were used in the work (which must be distributed under
+Sections 1 and 2 above); and, if the work is an executable linked
+with the Library, with the complete machine-readable “work that
+uses the Library”, as object code and/or source code, so that the
+user can modify the Library and then relink to produce a modified
+executable containing the modified Library. (It is understood
+that the user who changes the contents of definitions files in the
+Library will not necessarily be able to recompile the application
+to use the modified definitions.)
+* **b)** Use a suitable shared library mechanism for linking with the
+Library. A suitable mechanism is one that (1) uses at run time a
+copy of the library already present on the user's computer system,
+rather than copying library functions into the executable, and (2)
+will operate properly with a modified version of the library, if
+the user installs one, as long as the modified version is
+interface-compatible with the version that the work was made with.
+* **c)** Accompany the work with a written offer, valid for at
+least three years, to give the same user the materials
+specified in Subsection 6a, above, for a charge no more
+than the cost of performing this distribution.
+* **d)** If distribution of the work is made by offering access to copy
+from a designated place, offer equivalent access to copy the above
+specified materials from the same place.
+* **e)** Verify that the user has already received a copy of these
+materials or that you have already sent this user a copy.
+
+For an executable, the required form of the “work that uses the
+Library” must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+**7.** You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+* **a)** Accompany the combined library with a copy of the same work
+based on the Library, uncombined with any other library
+facilities. This must be distributed under the terms of the
+Sections above.
+* **b)** Give prominent notice with the combined library of the fact
+that part of it is a work based on the Library, and explaining
+where to find the accompanying uncombined form of the same work.
+
+**8.** You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+**9.** You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+**10.** Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+**11.** If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+**12.** If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+**13.** The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+“any later version”, you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+**14.** If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+### NO WARRANTY
+
+**15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY “AS IS” WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+**16.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+_END OF TERMS AND CONDITIONS_
diff --git a/vendor/adodb/adodb-php/README.md b/vendor/adodb/adodb-php/README.md
new file mode 100644
index 0000000..273c24c
--- /dev/null
+++ b/vendor/adodb/adodb-php/README.md
@@ -0,0 +1,103 @@
+ADOdb Library for PHP5
+======================
+
+[![Join chat on Gitter](https://img.shields.io/gitter/room/form-data/form-data.svg)](https://gitter.im/adodb/adodb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Download ADOdb](https://img.shields.io/sourceforge/dm/adodb.svg)](https://sourceforge.net/projects/adodb/files/latest/download)
+
+(c) 2000-2013 John Lim (jlim@natsoft.com)
+(c) 2014 Damien Regad, Mark Newnham and the
+ [ADOdb community](https://github.com/ADOdb/ADOdb/graphs/contributors)
+
+The ADOdb Library is dual-licensed, released under both the
+[BSD 3-Clause](https://github.com/ADOdb/ADOdb/blob/master/LICENSE.md#bsd-3-clause-license)
+and the
+[GNU Lesser General Public Licence (LGPL) v2.1](https://github.com/ADOdb/ADOdb/blob/master/LICENSE.md#gnu-lesser-general-public-license)
+or, at your option, any later version.
+This means you can use it in proprietary products;
+see [License](https://github.com/ADOdb/ADOdb/blob/master/LICENSE.md) for details.
+
+Home page: http://adodb.org/
+
+
+Introduction
+============
+
+PHP's database access functions are not standardized. This creates a
+need for a database class library to hide the differences between the
+different databases (encapsulate the differences) so we can easily
+switch databases.
+
+The library currently supports MySQL, Interbase, Sybase, PostgreSQL, Oracle,
+Microsoft SQL server, Foxpro ODBC, Access ODBC, Informix, DB2,
+Sybase SQL Anywhere, generic ODBC and Microsoft's ADO.
+
+We hope more people will contribute drivers to support other databases.
+
+
+Installation
+============
+
+Unpack all the files into a directory accessible by your web server.
+
+To test, try modifying some of the tutorial examples.
+Make sure you customize the connection settings correctly.
+
+You can debug using:
+
+``` php
+<?php
+include('adodb/adodb.inc.php');
+
+$db = ADONewConnection($driver); # eg. 'mysql' or 'oci8'
+$db->debug = true;
+$db->Connect($server, $user, $password, $database);
+$rs = $db->Execute('select * from some_small_table');
+print "<pre>";
+print_r($rs->GetRows());
+print "</pre>";
+```
+
+
+Documentation and Examples
+==========================
+
+Refer to the [ADOdb website](http://adodb.org/) for library documentation and examples. The documentation can also be [downloaded for offline viewing](https://sourceforge.net/projects/adodb/files/Documentation/).
+
+- [Main documentation](http://adodb.org/dokuwiki/doku.php?id=v5:userguide:userguide_index): Query, update and insert records using a portable API.
+- [Data dictionary](http://adodb.org/dokuwiki/doku.php?id=v5:dictionary:dictionary_index) describes how to create database tables and indexes in a portable manner.
+- [Database performance monitoring](http://adodb.org/dokuwiki/doku.php?id=v5:performance:performance_index) allows you to perform health checks, tune and monitor your database.
+- [Database-backed sessions](http://adodb.org/dokuwiki/doku.php?id=v5:session:session_index).
+
+There is also a [tutorial](http://adodb.org/dokuwiki/doku.php?id=v5:userguide:mysql_tutorial) that contrasts ADOdb code with PHP native MySQL code.
+
+
+Files
+=====
+
+- `adodb.inc.php` is the library's main file. You only need to include this file.
+- `adodb-*.inc.php` are the database specific driver code.
+- `adodb-session.php` is the PHP4 session handling code.
+- `test.php` contains a list of test commands to exercise the class library.
+- `testdatabases.inc.php` contains the list of databases to apply the tests on.
+- `Benchmark.php` is a simple benchmark to test the throughput of a SELECT
+statement for databases described in testdatabases.inc.php. The benchmark
+tables are created in test.php.
+
+
+Support
+=======
+
+To discuss with the ADOdb development team and users, connect to our
+[Gitter chatroom](https://gitter.im/adodb/adodb) using your Github credentials.
+
+Please report bugs, issues and feature requests on Github:
+
+https://github.com/ADOdb/ADOdb/issues
+
+You may also find legacy issues in
+
+- the old [ADOdb forums](http://phplens.com/lens/lensforum/topics.php?id=4) on phplens.com
+- the [SourceForge tickets section](http://sourceforge.net/p/adodb/_list/tickets)
+
+However, please note that they are not actively monitored and should
+only be used as reference.
diff --git a/vendor/adodb/adodb-php/adodb-active-record.inc.php b/vendor/adodb/adodb-php/adodb-active-record.inc.php
new file mode 100644
index 0000000..bbba78d
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-active-record.inc.php
@@ -0,0 +1,1142 @@
+<?php
+/*
+
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Latest version is available at http://adodb.org/
+
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Active Record implementation. Superset of Zend Framework's.
+
+ Version 0.92
+
+ See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
+ for info on Ruby on Rails Active Record implementation
+*/
+
+
+global $_ADODB_ACTIVE_DBS;
+global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
+global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
+global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
+
+// array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
+$_ADODB_ACTIVE_DBS = array();
+$ACTIVE_RECORD_SAFETY = true;
+$ADODB_ACTIVE_DEFVALS = false;
+$ADODB_ACTIVE_CACHESECS = 0;
+
+class ADODB_Active_DB {
+ var $db; // ADOConnection
+ var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
+}
+
+class ADODB_Active_Table {
+ var $name; // table name
+ var $flds; // assoc array of adofieldobjs, indexed by fieldname
+ var $keys; // assoc array of primary keys, indexed by fieldname
+ var $_created; // only used when stored as a cached file
+ var $_belongsTo = array();
+ var $_hasMany = array();
+}
+
+// $db = database connection
+// $index = name of index - can be associative, for an example see
+// http://phplens.com/lens/lensforum/msgs.php?id=17790
+// returns index into $_ADODB_ACTIVE_DBS
+function ADODB_SetDatabaseAdapter(&$db, $index=false)
+{
+ global $_ADODB_ACTIVE_DBS;
+
+ foreach($_ADODB_ACTIVE_DBS as $k => $d) {
+ if (PHP_VERSION >= 5) {
+ if ($d->db === $db) {
+ return $k;
+ }
+ } else {
+ if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) {
+ return $k;
+ }
+ }
+ }
+
+ $obj = new ADODB_Active_DB();
+ $obj->db = $db;
+ $obj->tables = array();
+
+ if ($index == false) {
+ $index = sizeof($_ADODB_ACTIVE_DBS);
+ }
+
+ $_ADODB_ACTIVE_DBS[$index] = $obj;
+
+ return sizeof($_ADODB_ACTIVE_DBS)-1;
+}
+
+
+class ADODB_Active_Record {
+ static $_changeNames = true; // dynamically pluralize table names
+ static $_quoteNames = false;
+
+ static $_foreignSuffix = '_id'; //
+ var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
+ var $_table; // tablename, if set in class definition then use it as table name
+ var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
+ var $_where; // where clause set in Load()
+ var $_saved = false; // indicates whether data is already inserted.
+ var $_lasterr = false; // last error message
+ var $_original = false; // the original values loaded or inserted, refreshed on update
+
+ var $foreignName; // CFR: class name when in a relationship
+
+ var $lockMode = ' for update '; // you might want to change to
+
+ static function UseDefaultValues($bool=null)
+ {
+ global $ADODB_ACTIVE_DEFVALS;
+ if (isset($bool)) {
+ $ADODB_ACTIVE_DEFVALS = $bool;
+ }
+ return $ADODB_ACTIVE_DEFVALS;
+ }
+
+ // should be static
+ static function SetDatabaseAdapter(&$db, $index=false)
+ {
+ return ADODB_SetDatabaseAdapter($db, $index);
+ }
+
+
+ public function __set($name, $value)
+ {
+ $name = str_replace(' ', '_', $name);
+ $this->$name = $value;
+ }
+
+ // php5 constructor
+ function __construct($table = false, $pkeyarr=false, $db=false)
+ {
+ global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
+
+ if ($db == false && is_object($pkeyarr)) {
+ $db = $pkeyarr;
+ $pkeyarr = false;
+ }
+
+ if (!$table) {
+ if (!empty($this->_table)) {
+ $table = $this->_table;
+ }
+ else $table = $this->_pluralize(get_class($this));
+ }
+ $this->foreignName = strtolower(get_class($this)); // CFR: default foreign name
+ if ($db) {
+ $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
+ } else if (!isset($this->_dbat)) {
+ if (sizeof($_ADODB_ACTIVE_DBS) == 0) {
+ $this->Error(
+ "No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
+ 'ADODB_Active_Record::__constructor'
+ );
+ }
+ end($_ADODB_ACTIVE_DBS);
+ $this->_dbat = key($_ADODB_ACTIVE_DBS);
+ }
+
+ $this->_table = $table;
+ $this->_tableat = $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
+
+ $this->UpdateActiveTable($pkeyarr);
+ }
+
+ function __wakeup()
+ {
+ $class = get_class($this);
+ new $class;
+ }
+
+ function _pluralize($table)
+ {
+ if (!ADODB_Active_Record::$_changeNames) {
+ return $table;
+ }
+
+ $ut = strtoupper($table);
+ $len = strlen($table);
+ $lastc = $ut[$len-1];
+ $lastc2 = substr($ut,$len-2);
+ switch ($lastc) {
+ case 'S':
+ return $table.'es';
+ case 'Y':
+ return substr($table,0,$len-1).'ies';
+ case 'X':
+ return $table.'es';
+ case 'H':
+ if ($lastc2 == 'CH' || $lastc2 == 'SH') {
+ return $table.'es';
+ }
+ default:
+ return $table.'s';
+ }
+ }
+
+ // CFR Lamest singular inflector ever - @todo Make it real!
+ // Note: There is an assumption here...and it is that the argument's length >= 4
+ function _singularize($tables)
+ {
+
+ if (!ADODB_Active_Record::$_changeNames) {
+ return $table;
+ }
+
+ $ut = strtoupper($tables);
+ $len = strlen($tables);
+ if($ut[$len-1] != 'S') {
+ return $tables; // I know...forget oxen
+ }
+ if($ut[$len-2] != 'E') {
+ return substr($tables, 0, $len-1);
+ }
+ switch($ut[$len-3]) {
+ case 'S':
+ case 'X':
+ return substr($tables, 0, $len-2);
+ case 'I':
+ return substr($tables, 0, $len-3) . 'y';
+ case 'H';
+ if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') {
+ return substr($tables, 0, $len-2);
+ }
+ default:
+ return substr($tables, 0, $len-1); // ?
+ }
+ }
+
+ function hasMany($foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
+ {
+ $ar = new $foreignClass($foreignRef);
+ $ar->foreignName = $foreignRef;
+ $ar->UpdateActiveTable();
+ $ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix;
+ $table =& $this->TableInfo();
+ $table->_hasMany[$foreignRef] = $ar;
+ # $this->$foreignRef = $this->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
+ }
+
+ // use when you don't want ADOdb to auto-pluralize tablename
+ static function TableHasMany($table, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
+ {
+ $ar = new ADODB_Active_Record($table);
+ $ar->hasMany($foreignRef, $foreignKey, $foreignClass);
+ }
+
+ // use when you don't want ADOdb to auto-pluralize tablename
+ static function TableKeyHasMany($table, $tablePKey, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
+ {
+ if (!is_array($tablePKey)) {
+ $tablePKey = array($tablePKey);
+ }
+ $ar = new ADODB_Active_Record($table,$tablePKey);
+ $ar->hasMany($foreignRef, $foreignKey, $foreignClass);
+ }
+
+
+ // use when you want ADOdb to auto-pluralize tablename for you. Note that the class must already be defined.
+ // e.g. class Person will generate relationship for table Persons
+ static function ClassHasMany($parentclass, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
+ {
+ $ar = new $parentclass();
+ $ar->hasMany($foreignRef, $foreignKey, $foreignClass);
+ }
+
+
+ function belongsTo($foreignRef,$foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
+ {
+ global $inflector;
+
+ $ar = new $parentClass($this->_pluralize($foreignRef));
+ $ar->foreignName = $foreignRef;
+ $ar->parentKey = $parentKey;
+ $ar->UpdateActiveTable();
+ $ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix;
+
+ $table =& $this->TableInfo();
+ $table->_belongsTo[$foreignRef] = $ar;
+ # $this->$foreignRef = $this->_belongsTo[$foreignRef];
+ }
+
+ static function ClassBelongsTo($class, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
+ {
+ $ar = new $class();
+ $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
+ }
+
+ static function TableBelongsTo($table, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
+ {
+ $ar = new ADOdb_Active_Record($table);
+ $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
+ }
+
+ static function TableKeyBelongsTo($table, $tablePKey, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
+ {
+ if (!is_array($tablePKey)) {
+ $tablePKey = array($tablePKey);
+ }
+ $ar = new ADOdb_Active_Record($table, $tablePKey);
+ $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
+ }
+
+
+ /**
+ * __get Access properties - used for lazy loading
+ *
+ * @param mixed $name
+ * @access protected
+ * @return mixed
+ */
+ function __get($name)
+ {
+ return $this->LoadRelations($name, '', -1, -1);
+ }
+
+ /**
+ * @param string $name
+ * @param string $whereOrderBy : eg. ' AND field1 = value ORDER BY field2'
+ * @param offset
+ * @param limit
+ * @return mixed
+ */
+ function LoadRelations($name, $whereOrderBy='', $offset=-1,$limit=-1)
+ {
+ $extras = array();
+ $table = $this->TableInfo();
+ if ($limit >= 0) {
+ $extras['limit'] = $limit;
+ }
+ if ($offset >= 0) {
+ $extras['offset'] = $offset;
+ }
+
+ if (strlen($whereOrderBy)) {
+ if (!preg_match('/^[ \n\r]*AND/i', $whereOrderBy)) {
+ if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i', $whereOrderBy)) {
+ $whereOrderBy = 'AND ' . $whereOrderBy;
+ }
+ }
+ }
+
+ if(!empty($table->_belongsTo[$name])) {
+ $obj = $table->_belongsTo[$name];
+ $columnName = $obj->foreignKey;
+ if(empty($this->$columnName)) {
+ $this->$name = null;
+ }
+ else {
+ if ($obj->parentKey) {
+ $key = $obj->parentKey;
+ }
+ else {
+ $key = reset($table->keys);
+ }
+
+ $arrayOfOne = $obj->Find($key.'='.$this->$columnName.' '.$whereOrderBy,false,false,$extras);
+ if ($arrayOfOne) {
+ $this->$name = $arrayOfOne[0];
+ return $arrayOfOne[0];
+ }
+ }
+ }
+ if(!empty($table->_hasMany[$name])) {
+ $obj = $table->_hasMany[$name];
+ $key = reset($table->keys);
+ $id = @$this->$key;
+ if (!is_numeric($id)) {
+ $db = $this->DB();
+ $id = $db->qstr($id);
+ }
+ $objs = $obj->Find($obj->foreignKey.'='.$id. ' '.$whereOrderBy,false,false,$extras);
+ if (!$objs) {
+ $objs = array();
+ }
+ $this->$name = $objs;
+ return $objs;
+ }
+
+ return array();
+ }
+ //////////////////////////////////
+
+ // update metadata
+ function UpdateActiveTable($pkeys=false,$forceUpdate=false)
+ {
+ global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
+ global $ADODB_ACTIVE_DEFVALS,$ADODB_FETCH_MODE;
+
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+
+ $table = $this->_table;
+ $tables = $activedb->tables;
+ $tableat = $this->_tableat;
+ if (!$forceUpdate && !empty($tables[$tableat])) {
+
+ $acttab = $tables[$tableat];
+ foreach($acttab->flds as $name => $fld) {
+ if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
+ $this->$name = $fld->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ }
+ return;
+ }
+ $db = $activedb->db;
+ $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
+ if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
+ $fp = fopen($fname,'r');
+ @flock($fp, LOCK_SH);
+ $acttab = unserialize(fread($fp,100000));
+ fclose($fp);
+ if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
+ // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
+ // ideally, you should cache at least 32 secs
+
+ foreach($acttab->flds as $name => $fld) {
+ if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
+ $this->$name = $fld->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ }
+
+ $activedb->tables[$table] = $acttab;
+
+ //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
+ return;
+ } else if ($db->debug) {
+ ADOConnection::outp("Refreshing cached active record file: $fname");
+ }
+ }
+ $activetab = new ADODB_Active_Table();
+ $activetab->name = $table;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($db->fetchMode !== false) {
+ $savem = $db->SetFetchMode(false);
+ }
+
+ $cols = $db->MetaColumns($table);
+
+ if (isset($savem)) {
+ $db->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$cols) {
+ $this->Error("Invalid table name: $table",'UpdateActiveTable');
+ return false;
+ }
+ $fld = reset($cols);
+ if (!$pkeys) {
+ if (isset($fld->primary_key)) {
+ $pkeys = array();
+ foreach($cols as $name => $fld) {
+ if (!empty($fld->primary_key)) {
+ $pkeys[] = $name;
+ }
+ }
+ } else
+ $pkeys = $this->GetPrimaryKeys($db, $table);
+ }
+ if (empty($pkeys)) {
+ $this->Error("No primary key found for table $table",'UpdateActiveTable');
+ return false;
+ }
+
+ $attr = array();
+ $keys = array();
+
+ switch($ADODB_ASSOC_CASE) {
+ case 0:
+ foreach($cols as $name => $fldobj) {
+ $name = strtolower($name);
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+ foreach($pkeys as $k => $name) {
+ $keys[strtolower($name)] = strtolower($name);
+ }
+ break;
+
+ case 1:
+ foreach($cols as $name => $fldobj) {
+ $name = strtoupper($name);
+
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+
+ foreach($pkeys as $k => $name) {
+ $keys[strtoupper($name)] = strtoupper($name);
+ }
+ break;
+ default:
+ foreach($cols as $name => $fldobj) {
+ $name = ($fldobj->name);
+
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+ foreach($pkeys as $k => $name) {
+ $keys[$name] = $cols[$name]->name;
+ }
+ break;
+ }
+
+ $activetab->keys = $keys;
+ $activetab->flds = $attr;
+
+ if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
+ $activetab->_created = time();
+ $s = serialize($activetab);
+ if (!function_exists('adodb_write_file')) {
+ include(ADODB_DIR.'/adodb-csvlib.inc.php');
+ }
+ adodb_write_file($fname,$s);
+ }
+ if (isset($activedb->tables[$table])) {
+ $oldtab = $activedb->tables[$table];
+
+ if ($oldtab) {
+ $activetab->_belongsTo = $oldtab->_belongsTo;
+ $activetab->_hasMany = $oldtab->_hasMany;
+ }
+ }
+ $activedb->tables[$table] = $activetab;
+ }
+
+ function GetPrimaryKeys(&$db, $table)
+ {
+ return $db->MetaPrimaryKeys($table);
+ }
+
+ // error handler for both PHP4+5.
+ function Error($err,$fn)
+ {
+ global $_ADODB_ACTIVE_DBS;
+
+ $fn = get_class($this).'::'.$fn;
+ $this->_lasterr = $fn.': '.$err;
+
+ if ($this->_dbat < 0) {
+ $db = false;
+ }
+ else {
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $db = $activedb->db;
+ }
+
+ if (function_exists('adodb_throw')) {
+ if (!$db) {
+ adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
+ }
+ else {
+ adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
+ }
+ } else {
+ if (!$db || $db->debug) {
+ ADOConnection::outp($this->_lasterr);
+ }
+ }
+
+ }
+
+ // return last error message
+ function ErrorMsg()
+ {
+ if (!function_exists('adodb_throw')) {
+ if ($this->_dbat < 0) {
+ $db = false;
+ }
+ else {
+ $db = $this->DB();
+ }
+
+ // last error could be database error too
+ if ($db && $db->ErrorMsg()) {
+ return $db->ErrorMsg();
+ }
+ }
+ return $this->_lasterr;
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_dbat < 0) {
+ return -9999; // no database connection...
+ }
+ $db = $this->DB();
+
+ return (int) $db->ErrorNo();
+ }
+
+
+ // retrieve ADOConnection from _ADODB_Active_DBs
+ function DB()
+ {
+ global $_ADODB_ACTIVE_DBS;
+
+ if ($this->_dbat < 0) {
+ $false = false;
+ $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
+ return $false;
+ }
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $db = $activedb->db;
+ return $db;
+ }
+
+ // retrieve ADODB_Active_Table
+ function &TableInfo()
+ {
+ global $_ADODB_ACTIVE_DBS;
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $table = $activedb->tables[$this->_tableat];
+ return $table;
+ }
+
+
+ // I have an ON INSERT trigger on a table that sets other columns in the table.
+ // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
+ function Reload()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+ $where = $this->GenWhere($db, $table);
+ return($this->Load($where));
+ }
+
+
+ // set a numeric array (using natural table field ordering) as object properties
+ function Set(&$row)
+ {
+ global $ACTIVE_RECORD_SAFETY;
+
+ $db = $this->DB();
+
+ if (!$row) {
+ $this->_saved = false;
+ return false;
+ }
+
+ $this->_saved = true;
+
+ $table = $this->TableInfo();
+ if ($ACTIVE_RECORD_SAFETY && sizeof($table->flds) != sizeof($row)) {
+ # <AP>
+ $bad_size = TRUE;
+ if (sizeof($row) == 2 * sizeof($table->flds)) {
+ // Only keep string keys
+ $keys = array_filter(array_keys($row), 'is_string');
+ if (sizeof($keys) == sizeof($table->flds)) {
+ $bad_size = FALSE;
+ }
+ }
+ if ($bad_size) {
+ $this->Error("Table structure of $this->_table has changed","Load");
+ return false;
+ }
+ # </AP>
+ }
+ else
+ $keys = array_keys($row);
+
+ # <AP>
+ reset($keys);
+ $this->_original = array();
+ foreach($table->flds as $name=>$fld) {
+ $value = $row[current($keys)];
+ $this->$name = $value;
+ $this->_original[] = $value;
+ next($keys);
+ }
+
+ # </AP>
+ return true;
+ }
+
+ // get last inserted id for INSERT
+ function LastInsertID(&$db,$fieldname)
+ {
+ if ($db->hasInsertID) {
+ $val = $db->Insert_ID($this->_table,$fieldname);
+ }
+ else {
+ $val = false;
+ }
+
+ if (is_null($val) || $val === false) {
+ // this might not work reliably in multi-user environment
+ return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
+ }
+ return $val;
+ }
+
+ // quote data in where clause
+ function doquote(&$db, $val,$t)
+ {
+ switch($t) {
+ case 'L':
+ if (strpos($db->databaseType,'postgres') !== false) {
+ return $db->qstr($val);
+ }
+ case 'D':
+ case 'T':
+ if (empty($val)) {
+ return 'null';
+ }
+ case 'B':
+ case 'N':
+ case 'C':
+ case 'X':
+ if (is_null($val)) {
+ return 'null';
+ }
+
+ if (strlen($val)>0 &&
+ (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")
+ ) {
+ return $db->qstr($val);
+ break;
+ }
+ default:
+ return $val;
+ break;
+ }
+ }
+
+ // generate where clause for an UPDATE/SELECT
+ function GenWhere(&$db, &$table)
+ {
+ $keys = $table->keys;
+ $parr = array();
+
+ foreach($keys as $k) {
+ $f = $table->flds[$k];
+ if ($f) {
+ $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
+ }
+ }
+ return implode(' and ', $parr);
+ }
+
+
+ function _QName($n,$db=false)
+ {
+ if (!ADODB_Active_Record::$_quoteNames) {
+ return $n;
+ }
+ if (!$db) {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ }
+ return $db->nameQuote.$n.$db->nameQuote;
+ }
+
+ //------------------------------------------------------------ Public functions below
+
+ function Load($where=null,$bindarr=false, $lock = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $this->_where = $where;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($db->fetchMode !== false) {
+ $savem = $db->SetFetchMode(false);
+ }
+
+ $qry = "select * from ".$this->_table;
+
+ if($where) {
+ $qry .= ' WHERE '.$where;
+ }
+ if ($lock) {
+ $qry .= $this->lockMode;
+ }
+
+ $row = $db->GetRow($qry,$bindarr);
+
+ if (isset($savem)) {
+ $db->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $this->Set($row);
+ }
+
+ function LoadLocked($where=null, $bindarr=false)
+ {
+ $this->Load($where,$bindarr,true);
+ }
+
+ # useful for multiple record inserts
+ # see http://phplens.com/lens/lensforum/msgs.php?id=17795
+ function Reset()
+ {
+ $this->_where=null;
+ $this->_saved = false;
+ $this->_lasterr = false;
+ $this->_original = false;
+ $vars=get_object_vars($this);
+ foreach($vars as $k=>$v){
+ if(substr($k,0,1)!=='_'){
+ $this->{$k}=null;
+ }
+ }
+ $this->foreignName=strtolower(get_class($this));
+ return true;
+ }
+
+ // false on error
+ function Save()
+ {
+ if ($this->_saved) {
+ $ok = $this->Update();
+ }
+ else {
+ $ok = $this->Insert();
+ }
+
+ return $ok;
+ }
+
+
+ // false on error
+ function Insert()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $cnt = 0;
+ $table = $this->TableInfo();
+
+ $valarr = array();
+ $names = array();
+ $valstr = array();
+
+ foreach($table->flds as $name=>$fld) {
+ $val = $this->$name;
+ if(!is_array($val) || !is_null($val) || !array_key_exists($name, $table->keys)) {
+ $valarr[] = $val;
+ $names[] = $this->_QName($name,$db);
+ $valstr[] = $db->Param($cnt);
+ $cnt += 1;
+ }
+ }
+
+ if (empty($names)){
+ foreach($table->flds as $name=>$fld) {
+ $valarr[] = null;
+ $names[] = $name;
+ $valstr[] = $db->Param($cnt);
+ $cnt += 1;
+ }
+ }
+ $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
+ $ok = $db->Execute($sql,$valarr);
+
+ if ($ok) {
+ $this->_saved = true;
+ $autoinc = false;
+ foreach($table->keys as $k) {
+ if (is_null($this->$k)) {
+ $autoinc = true;
+ break;
+ }
+ }
+ if ($autoinc && sizeof($table->keys) == 1) {
+ $k = reset($table->keys);
+ $this->$k = $this->LastInsertID($db,$k);
+ }
+ }
+
+ $this->_original = $valarr;
+ return !empty($ok);
+ }
+
+ function Delete()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $where = $this->GenWhere($db,$table);
+ $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
+ $ok = $db->Execute($sql);
+
+ return $ok ? true : false;
+ }
+
+ // returns an array of active record objects
+ function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
+ {
+ $db = $this->DB();
+ if (!$db || empty($this->_table)) {
+ return false;
+ }
+ $arr = $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr,$extra);
+ return $arr;
+ }
+
+ // returns 0 on error, 1 on update, 2 on insert
+ function Replace()
+ {
+ global $ADODB_ASSOC_CASE;
+
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $pkey = $table->keys;
+
+ foreach($table->flds as $name=>$fld) {
+ $val = $this->$name;
+ /*
+ if (is_null($val)) {
+ if (isset($fld->not_null) && $fld->not_null) {
+ if (isset($fld->default_value) && strlen($fld->default_value)) {
+ continue;
+ }
+ else {
+ $this->Error("Cannot update null into $name","Replace");
+ return false;
+ }
+ }
+ }*/
+ if (is_null($val) && !empty($fld->auto_increment)) {
+ continue;
+ }
+
+ if (is_array($val)) {
+ continue;
+ }
+
+ $t = $db->MetaType($fld->type);
+ $arr[$name] = $this->doquote($db,$val,$t);
+ $valarr[] = $val;
+ }
+
+ if (!is_array($pkey)) {
+ $pkey = array($pkey);
+ }
+
+ if ($ADODB_ASSOC_CASE == 0) {
+ foreach($pkey as $k => $v)
+ $pkey[$k] = strtolower($v);
+ }
+ elseif ($ADODB_ASSOC_CASE == 1) {
+ foreach($pkey as $k => $v) {
+ $pkey[$k] = strtoupper($v);
+ }
+ }
+
+ $ok = $db->Replace($this->_table,$arr,$pkey);
+ if ($ok) {
+ $this->_saved = true; // 1= update 2=insert
+ if ($ok == 2) {
+ $autoinc = false;
+ foreach($table->keys as $k) {
+ if (is_null($this->$k)) {
+ $autoinc = true;
+ break;
+ }
+ }
+ if ($autoinc && sizeof($table->keys) == 1) {
+ $k = reset($table->keys);
+ $this->$k = $this->LastInsertID($db,$k);
+ }
+ }
+
+ $this->_original = $valarr;
+ }
+ return $ok;
+ }
+
+ // returns 0 on error, 1 on update, -1 if no change in data (no update)
+ function Update()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $where = $this->GenWhere($db, $table);
+
+ if (!$where) {
+ $this->error("Where missing for table $table", "Update");
+ return false;
+ }
+ $valarr = array();
+ $neworig = array();
+ $pairs = array();
+ $i = -1;
+ $cnt = 0;
+ foreach($table->flds as $name=>$fld) {
+ $i += 1;
+ $val = $this->$name;
+ $neworig[] = $val;
+
+ if (isset($table->keys[$name]) || is_array($val)) {
+ continue;
+ }
+
+ if (is_null($val)) {
+ if (isset($fld->not_null) && $fld->not_null) {
+ if (isset($fld->default_value) && strlen($fld->default_value)) {
+ continue;
+ }
+ else {
+ $this->Error("Cannot set field $name to NULL","Update");
+ return false;
+ }
+ }
+ }
+
+ if (isset($this->_original[$i]) && strcmp($val,$this->_original[$i]) == 0) {
+ continue;
+ }
+
+ if (is_null($this->_original[$i]) && is_null($val)) {
+ continue;
+ }
+
+ $valarr[] = $val;
+ $pairs[] = $this->_QName($name,$db).'='.$db->Param($cnt);
+ $cnt += 1;
+ }
+
+
+ if (!$cnt) {
+ return -1;
+ }
+
+ $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
+ $ok = $db->Execute($sql,$valarr);
+ if ($ok) {
+ $this->_original = $neworig;
+ return 1;
+ }
+ return 0;
+ }
+
+ function GetAttributeNames()
+ {
+ $table = $this->TableInfo();
+ if (!$table) {
+ return false;
+ }
+ return array_keys($table->flds);
+ }
+
+};
+
+function adodb_GetActiveRecordsClass(&$db, $class, $table,$whereOrderBy,$bindarr, $primkeyArr,
+ $extra)
+{
+global $_ADODB_ACTIVE_DBS;
+
+
+ $save = $db->SetFetchMode(ADODB_FETCH_NUM);
+ $qry = "select * from ".$table;
+
+ if (!empty($whereOrderBy)) {
+ $qry .= ' WHERE '.$whereOrderBy;
+ }
+ if(isset($extra['limit'])) {
+ $rows = false;
+ if(isset($extra['offset'])) {
+ $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset'],$bindarr);
+ } else {
+ $rs = $db->SelectLimit($qry, $extra['limit'],-1,$bindarr);
+ }
+ if ($rs) {
+ while (!$rs->EOF) {
+ $rows[] = $rs->fields;
+ $rs->MoveNext();
+ }
+ }
+ } else
+ $rows = $db->GetAll($qry,$bindarr);
+
+ $db->SetFetchMode($save);
+
+ $false = false;
+
+ if ($rows === false) {
+ return $false;
+ }
+
+
+ if (!class_exists($class)) {
+ $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
+ return $false;
+ }
+ $arr = array();
+ // arrRef will be the structure that knows about our objects.
+ // It is an associative array.
+ // We will, however, return arr, preserving regular 0.. order so that
+ // obj[0] can be used by app developpers.
+ $arrRef = array();
+ $bTos = array(); // Will store belongTo's indices if any
+ foreach($rows as $row) {
+
+ $obj = new $class($table,$primkeyArr,$db);
+ if ($obj->ErrorNo()){
+ $db->_errorMsg = $obj->ErrorMsg();
+ return $false;
+ }
+ $obj->Set($row);
+ $arr[] = $obj;
+ } // foreach($rows as $row)
+
+ return $arr;
+}
diff --git a/vendor/adodb/adodb-php/adodb-active-recordx.inc.php b/vendor/adodb/adodb-php/adodb-active-recordx.inc.php
new file mode 100644
index 0000000..0d85a74
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-active-recordx.inc.php
@@ -0,0 +1,1497 @@
+<?php
+/*
+
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Latest version is available at http://adodb.org/
+
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Active Record implementation. Superset of Zend Framework's.
+
+ This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft chris#voilaweb.com
+
+ Version 0.9
+
+ See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
+ for info on Ruby on Rails Active Record implementation
+*/
+
+
+ // CFR: Active Records Definitions
+define('ADODB_JOIN_AR', 0x01);
+define('ADODB_WORK_AR', 0x02);
+define('ADODB_LAZY_AR', 0x03);
+
+
+global $_ADODB_ACTIVE_DBS;
+global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
+global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
+global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
+
+// array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
+$_ADODB_ACTIVE_DBS = array();
+$ACTIVE_RECORD_SAFETY = true; // CFR: disabled while playing with relations
+$ADODB_ACTIVE_DEFVALS = false;
+
+class ADODB_Active_DB {
+ var $db; // ADOConnection
+ var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
+}
+
+class ADODB_Active_Table {
+ var $name; // table name
+ var $flds; // assoc array of adofieldobjs, indexed by fieldname
+ var $keys; // assoc array of primary keys, indexed by fieldname
+ var $_created; // only used when stored as a cached file
+ var $_belongsTo = array();
+ var $_hasMany = array();
+ var $_colsCount; // total columns count, including relations
+
+ function updateColsCount()
+ {
+ $this->_colsCount = sizeof($this->flds);
+ foreach($this->_belongsTo as $foreignTable)
+ $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
+ foreach($this->_hasMany as $foreignTable)
+ $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
+ }
+}
+
+// returns index into $_ADODB_ACTIVE_DBS
+function ADODB_SetDatabaseAdapter(&$db)
+{
+ global $_ADODB_ACTIVE_DBS;
+
+ foreach($_ADODB_ACTIVE_DBS as $k => $d) {
+ if (PHP_VERSION >= 5) {
+ if ($d->db === $db) {
+ return $k;
+ }
+ } else {
+ if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) {
+ return $k;
+ }
+ }
+ }
+
+ $obj = new ADODB_Active_DB();
+ $obj->db = $db;
+ $obj->tables = array();
+
+ $_ADODB_ACTIVE_DBS[] = $obj;
+
+ return sizeof($_ADODB_ACTIVE_DBS)-1;
+}
+
+
+class ADODB_Active_Record {
+ static $_changeNames = true; // dynamically pluralize table names
+ static $_foreignSuffix = '_id'; //
+ var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
+ var $_table; // tablename, if set in class definition then use it as table name
+ var $_sTable; // singularized table name
+ var $_pTable; // pluralized table name
+ var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
+ var $_where; // where clause set in Load()
+ var $_saved = false; // indicates whether data is already inserted.
+ var $_lasterr = false; // last error message
+ var $_original = false; // the original values loaded or inserted, refreshed on update
+
+ var $foreignName; // CFR: class name when in a relationship
+
+ static function UseDefaultValues($bool=null)
+ {
+ global $ADODB_ACTIVE_DEFVALS;
+ if (isset($bool)) {
+ $ADODB_ACTIVE_DEFVALS = $bool;
+ }
+ return $ADODB_ACTIVE_DEFVALS;
+ }
+
+ // should be static
+ static function SetDatabaseAdapter(&$db)
+ {
+ return ADODB_SetDatabaseAdapter($db);
+ }
+
+
+ public function __set($name, $value)
+ {
+ $name = str_replace(' ', '_', $name);
+ $this->$name = $value;
+ }
+
+ // php5 constructor
+ // Note: if $table is defined, then we will use it as our table name
+ // Otherwise we will use our classname...
+ // In our database, table names are pluralized (because there can be
+ // more than one row!)
+ // Similarly, if $table is defined here, it has to be plural form.
+ //
+ // $options is an array that allows us to tweak the constructor's behaviour
+ // if $options['refresh'] is true, we re-scan our metadata information
+ // if $options['new'] is true, we forget all relations
+ function __construct($table = false, $pkeyarr=false, $db=false, $options=array())
+ {
+ global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
+
+ if ($db == false && is_object($pkeyarr)) {
+ $db = $pkeyarr;
+ $pkeyarr = false;
+ }
+
+ if($table) {
+ // table argument exists. It is expected to be
+ // already plural form.
+ $this->_pTable = $table;
+ $this->_sTable = $this->_singularize($this->_pTable);
+ }
+ else {
+ // We will use current classname as table name.
+ // We need to pluralize it for the real table name.
+ $this->_sTable = strtolower(get_class($this));
+ $this->_pTable = $this->_pluralize($this->_sTable);
+ }
+ $this->_table = &$this->_pTable;
+
+ $this->foreignName = $this->_sTable; // CFR: default foreign name (singular)
+
+ if ($db) {
+ $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
+ } else
+ $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
+
+
+ if ($this->_dbat < 0) {
+ $this->Error(
+ "No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
+ 'ADODB_Active_Record::__constructor'
+ );
+ }
+
+ $this->_tableat = $this->_table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
+
+ // CFR: Just added this option because UpdateActiveTable() can refresh its information
+ // but there was no way to ask it to do that.
+ $forceUpdate = (isset($options['refresh']) && true === $options['refresh']);
+ $this->UpdateActiveTable($pkeyarr, $forceUpdate);
+ if(isset($options['new']) && true === $options['new']) {
+ $table =& $this->TableInfo();
+ unset($table->_hasMany);
+ unset($table->_belongsTo);
+ $table->_hasMany = array();
+ $table->_belongsTo = array();
+ }
+ }
+
+ function __wakeup()
+ {
+ $class = get_class($this);
+ new $class;
+ }
+
+ // CFR: Constants found in Rails
+ static $IrregularP = array(
+ 'PERSON' => 'people',
+ 'MAN' => 'men',
+ 'WOMAN' => 'women',
+ 'CHILD' => 'children',
+ 'COW' => 'kine',
+ );
+
+ static $IrregularS = array(
+ 'PEOPLE' => 'PERSON',
+ 'MEN' => 'man',
+ 'WOMEN' => 'woman',
+ 'CHILDREN' => 'child',
+ 'KINE' => 'cow',
+ );
+
+ static $WeIsI = array(
+ 'EQUIPMENT' => true,
+ 'INFORMATION' => true,
+ 'RICE' => true,
+ 'MONEY' => true,
+ 'SPECIES' => true,
+ 'SERIES' => true,
+ 'FISH' => true,
+ 'SHEEP' => true,
+ );
+
+ function _pluralize($table)
+ {
+ if (!ADODB_Active_Record::$_changeNames) {
+ return $table;
+ }
+ $ut = strtoupper($table);
+ if(isset(self::$WeIsI[$ut])) {
+ return $table;
+ }
+ if(isset(self::$IrregularP[$ut])) {
+ return self::$IrregularP[$ut];
+ }
+ $len = strlen($table);
+ $lastc = $ut[$len-1];
+ $lastc2 = substr($ut,$len-2);
+ switch ($lastc) {
+ case 'S':
+ return $table.'es';
+ case 'Y':
+ return substr($table,0,$len-1).'ies';
+ case 'X':
+ return $table.'es';
+ case 'H':
+ if ($lastc2 == 'CH' || $lastc2 == 'SH') {
+ return $table.'es';
+ }
+ default:
+ return $table.'s';
+ }
+ }
+
+ // CFR Lamest singular inflector ever - @todo Make it real!
+ // Note: There is an assumption here...and it is that the argument's length >= 4
+ function _singularize($table)
+ {
+
+ if (!ADODB_Active_Record::$_changeNames) {
+ return $table;
+ }
+ $ut = strtoupper($table);
+ if(isset(self::$WeIsI[$ut])) {
+ return $table;
+ }
+ if(isset(self::$IrregularS[$ut])) {
+ return self::$IrregularS[$ut];
+ }
+ $len = strlen($table);
+ if($ut[$len-1] != 'S') {
+ return $table; // I know...forget oxen
+ }
+ if($ut[$len-2] != 'E') {
+ return substr($table, 0, $len-1);
+ }
+ switch($ut[$len-3]) {
+ case 'S':
+ case 'X':
+ return substr($table, 0, $len-2);
+ case 'I':
+ return substr($table, 0, $len-3) . 'y';
+ case 'H';
+ if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') {
+ return substr($table, 0, $len-2);
+ }
+ default:
+ return substr($table, 0, $len-1); // ?
+ }
+ }
+
+ /*
+ * ar->foreignName will contain the name of the tables associated with this table because
+ * these other tables' rows may also be referenced by this table using theirname_id or the provided
+ * foreign keys (this index name is stored in ar->foreignKey)
+ *
+ * this-table.id = other-table-#1.this-table_id
+ * = other-table-#2.this-table_id
+ */
+ function hasMany($foreignRef,$foreignKey=false)
+ {
+ $ar = new ADODB_Active_Record($foreignRef);
+ $ar->foreignName = $foreignRef;
+ $ar->UpdateActiveTable();
+ $ar->foreignKey = ($foreignKey) ? $foreignKey : strtolower(get_class($this)) . self::$_foreignSuffix;
+
+ $table =& $this->TableInfo();
+ if(!isset($table->_hasMany[$foreignRef])) {
+ $table->_hasMany[$foreignRef] = $ar;
+ $table->updateColsCount();
+ }
+# @todo Can I make this guy be lazy?
+ $this->$foreignRef = $table->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
+ }
+
+ /**
+ * ar->foreignName will contain the name of the tables associated with this table because
+ * this table's rows may also be referenced by those tables using thistable_id or the provided
+ * foreign keys (this index name is stored in ar->foreignKey)
+ *
+ * this-table.other-table_id = other-table.id
+ */
+ function belongsTo($foreignRef,$foreignKey=false)
+ {
+ global $inflector;
+
+ $ar = new ADODB_Active_Record($this->_pluralize($foreignRef));
+ $ar->foreignName = $foreignRef;
+ $ar->UpdateActiveTable();
+ $ar->foreignKey = ($foreignKey) ? $foreignKey : $ar->foreignName . self::$_foreignSuffix;
+
+ $table =& $this->TableInfo();
+ if(!isset($table->_belongsTo[$foreignRef])) {
+ $table->_belongsTo[$foreignRef] = $ar;
+ $table->updateColsCount();
+ }
+ $this->$foreignRef = $table->_belongsTo[$foreignRef];
+ }
+
+ /**
+ * __get Access properties - used for lazy loading
+ *
+ * @param mixed $name
+ * @access protected
+ * @return void
+ */
+ function __get($name)
+ {
+ return $this->LoadRelations($name, '', -1. -1);
+ }
+
+ function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
+ {
+ $extras = array();
+ if($offset >= 0) {
+ $extras['offset'] = $offset;
+ }
+ if($limit >= 0) {
+ $extras['limit'] = $limit;
+ }
+ $table =& $this->TableInfo();
+
+ if (strlen($whereOrderBy)) {
+ if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy)) {
+ if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy)) {
+ $whereOrderBy = 'AND '.$whereOrderBy;
+ }
+ }
+ }
+
+ if(!empty($table->_belongsTo[$name])) {
+ $obj = $table->_belongsTo[$name];
+ $columnName = $obj->foreignKey;
+ if(empty($this->$columnName)) {
+ $this->$name = null;
+ }
+ else {
+ if(($k = reset($obj->TableInfo()->keys))) {
+ $belongsToId = $k;
+ }
+ else {
+ $belongsToId = 'id';
+ }
+
+ $arrayOfOne =
+ $obj->Find(
+ $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
+ $this->$name = $arrayOfOne[0];
+ }
+ return $this->$name;
+ }
+ if(!empty($table->_hasMany[$name])) {
+ $obj = $table->_hasMany[$name];
+ if(($k = reset($table->keys))) {
+ $hasManyId = $k;
+ }
+ else {
+ $hasManyId = 'id';
+ }
+
+ $this->$name =
+ $obj->Find(
+ $obj->foreignKey.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
+ return $this->$name;
+ }
+ }
+ //////////////////////////////////
+
+ // update metadata
+ function UpdateActiveTable($pkeys=false,$forceUpdate=false)
+ {
+ global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
+ global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE;
+
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+
+ $table = $this->_table;
+ $tables = $activedb->tables;
+ $tableat = $this->_tableat;
+ if (!$forceUpdate && !empty($tables[$tableat])) {
+
+ $tobj = $tables[$tableat];
+ foreach($tobj->flds as $name => $fld) {
+ if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
+ $this->$name = $fld->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ }
+ return;
+ }
+
+ $db = $activedb->db;
+ $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
+ if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
+ $fp = fopen($fname,'r');
+ @flock($fp, LOCK_SH);
+ $acttab = unserialize(fread($fp,100000));
+ fclose($fp);
+ if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
+ // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
+ // ideally, you should cache at least 32 secs
+ $activedb->tables[$table] = $acttab;
+
+ //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
+ return;
+ } else if ($db->debug) {
+ ADOConnection::outp("Refreshing cached active record file: $fname");
+ }
+ }
+ $activetab = new ADODB_Active_Table();
+ $activetab->name = $table;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($db->fetchMode !== false) {
+ $savem = $db->SetFetchMode(false);
+ }
+
+ $cols = $db->MetaColumns($table);
+
+ if (isset($savem)) {
+ $db->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$cols) {
+ $this->Error("Invalid table name: $table",'UpdateActiveTable');
+ return false;
+ }
+ $fld = reset($cols);
+ if (!$pkeys) {
+ if (isset($fld->primary_key)) {
+ $pkeys = array();
+ foreach($cols as $name => $fld) {
+ if (!empty($fld->primary_key)) {
+ $pkeys[] = $name;
+ }
+ }
+ } else {
+ $pkeys = $this->GetPrimaryKeys($db, $table);
+ }
+ }
+ if (empty($pkeys)) {
+ $this->Error("No primary key found for table $table",'UpdateActiveTable');
+ return false;
+ }
+
+ $attr = array();
+ $keys = array();
+
+ switch($ADODB_ASSOC_CASE) {
+ case 0:
+ foreach($cols as $name => $fldobj) {
+ $name = strtolower($name);
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+ foreach($pkeys as $k => $name) {
+ $keys[strtolower($name)] = strtolower($name);
+ }
+ break;
+
+ case 1:
+ foreach($cols as $name => $fldobj) {
+ $name = strtoupper($name);
+
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+
+ foreach($pkeys as $k => $name) {
+ $keys[strtoupper($name)] = strtoupper($name);
+ }
+ break;
+ default:
+ foreach($cols as $name => $fldobj) {
+ $name = ($fldobj->name);
+
+ if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
+ $this->$name = $fldobj->default_value;
+ }
+ else {
+ $this->$name = null;
+ }
+ $attr[$name] = $fldobj;
+ }
+ foreach($pkeys as $k => $name) {
+ $keys[$name] = $cols[$name]->name;
+ }
+ break;
+ }
+
+ $activetab->keys = $keys;
+ $activetab->flds = $attr;
+ $activetab->updateColsCount();
+
+ if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
+ $activetab->_created = time();
+ $s = serialize($activetab);
+ if (!function_exists('adodb_write_file')) {
+ include(ADODB_DIR.'/adodb-csvlib.inc.php');
+ }
+ adodb_write_file($fname,$s);
+ }
+ if (isset($activedb->tables[$table])) {
+ $oldtab = $activedb->tables[$table];
+
+ if ($oldtab) {
+ $activetab->_belongsTo = $oldtab->_belongsTo;
+ $activetab->_hasMany = $oldtab->_hasMany;
+ }
+ }
+ $activedb->tables[$table] = $activetab;
+ }
+
+ function GetPrimaryKeys(&$db, $table)
+ {
+ return $db->MetaPrimaryKeys($table);
+ }
+
+ // error handler for both PHP4+5.
+ function Error($err,$fn)
+ {
+ global $_ADODB_ACTIVE_DBS;
+
+ $fn = get_class($this).'::'.$fn;
+ $this->_lasterr = $fn.': '.$err;
+
+ if ($this->_dbat < 0) {
+ $db = false;
+ }
+ else {
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $db = $activedb->db;
+ }
+
+ if (function_exists('adodb_throw')) {
+ if (!$db) {
+ adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
+ }
+ else {
+ adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
+ }
+ } else {
+ if (!$db || $db->debug) {
+ ADOConnection::outp($this->_lasterr);
+ }
+ }
+
+ }
+
+ // return last error message
+ function ErrorMsg()
+ {
+ if (!function_exists('adodb_throw')) {
+ if ($this->_dbat < 0) {
+ $db = false;
+ }
+ else {
+ $db = $this->DB();
+ }
+
+ // last error could be database error too
+ if ($db && $db->ErrorMsg()) {
+ return $db->ErrorMsg();
+ }
+ }
+ return $this->_lasterr;
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_dbat < 0) {
+ return -9999; // no database connection...
+ }
+ $db = $this->DB();
+
+ return (int) $db->ErrorNo();
+ }
+
+
+ // retrieve ADOConnection from _ADODB_Active_DBs
+ function DB()
+ {
+ global $_ADODB_ACTIVE_DBS;
+
+ if ($this->_dbat < 0) {
+ $false = false;
+ $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
+ return $false;
+ }
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $db = $activedb->db;
+ return $db;
+ }
+
+ // retrieve ADODB_Active_Table
+ function &TableInfo()
+ {
+ global $_ADODB_ACTIVE_DBS;
+
+ $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
+ $table = $activedb->tables[$this->_tableat];
+ return $table;
+ }
+
+
+ // I have an ON INSERT trigger on a table that sets other columns in the table.
+ // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
+ function Reload()
+ {
+ $db =& $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table =& $this->TableInfo();
+ $where = $this->GenWhere($db, $table);
+ return($this->Load($where));
+ }
+
+
+ // set a numeric array (using natural table field ordering) as object properties
+ function Set(&$row)
+ {
+ global $ACTIVE_RECORD_SAFETY;
+
+ $db = $this->DB();
+
+ if (!$row) {
+ $this->_saved = false;
+ return false;
+ }
+
+ $this->_saved = true;
+
+ $table = $this->TableInfo();
+ $sizeofFlds = sizeof($table->flds);
+ $sizeofRow = sizeof($row);
+ if ($ACTIVE_RECORD_SAFETY && $table->_colsCount != $sizeofRow && $sizeofFlds != $sizeofRow) {
+ # <AP>
+ $bad_size = TRUE;
+ if($sizeofRow == 2 * $table->_colsCount || $sizeofRow == 2 * $sizeofFlds) {
+ // Only keep string keys
+ $keys = array_filter(array_keys($row), 'is_string');
+ if (sizeof($keys) == sizeof($table->flds)) {
+ $bad_size = FALSE;
+ }
+ }
+ if ($bad_size) {
+ $this->Error("Table structure of $this->_table has changed","Load");
+ return false;
+ }
+ # </AP>
+ }
+ else {
+ $keys = array_keys($row);
+ }
+
+ # <AP>
+ reset($keys);
+ $this->_original = array();
+ foreach($table->flds as $name=>$fld) {
+ $value = $row[current($keys)];
+ $this->$name = $value;
+ $this->_original[] = $value;
+ if(!next($keys)) {
+ break;
+ }
+ }
+ $table =& $this->TableInfo();
+ foreach($table->_belongsTo as $foreignTable) {
+ $ft = $foreignTable->TableInfo();
+ $propertyName = $ft->name;
+ foreach($ft->flds as $name=>$fld) {
+ $value = $row[current($keys)];
+ $foreignTable->$name = $value;
+ $foreignTable->_original[] = $value;
+ if(!next($keys)) {
+ break;
+ }
+ }
+ }
+ foreach($table->_hasMany as $foreignTable) {
+ $ft = $foreignTable->TableInfo();
+ foreach($ft->flds as $name=>$fld) {
+ $value = $row[current($keys)];
+ $foreignTable->$name = $value;
+ $foreignTable->_original[] = $value;
+ if(!next($keys)) {
+ break;
+ }
+ }
+ }
+ # </AP>
+
+ return true;
+ }
+
+ // get last inserted id for INSERT
+ function LastInsertID(&$db,$fieldname)
+ {
+ if ($db->hasInsertID) {
+ $val = $db->Insert_ID($this->_table,$fieldname);
+ }
+ else {
+ $val = false;
+ }
+
+ if (is_null($val) || $val === false) {
+ // this might not work reliably in multi-user environment
+ return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
+ }
+ return $val;
+ }
+
+ // quote data in where clause
+ function doquote(&$db, $val,$t)
+ {
+ switch($t) {
+ case 'D':
+ case 'T':
+ if (empty($val)) {
+ return 'null';
+ }
+ case 'C':
+ case 'X':
+ if (is_null($val)) {
+ return 'null';
+ }
+ if (strlen($val)>0 &&
+ (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")
+ ) {
+ return $db->qstr($val);
+ break;
+ }
+ default:
+ return $val;
+ break;
+ }
+ }
+
+ // generate where clause for an UPDATE/SELECT
+ function GenWhere(&$db, &$table)
+ {
+ $keys = $table->keys;
+ $parr = array();
+
+ foreach($keys as $k) {
+ $f = $table->flds[$k];
+ if ($f) {
+ $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
+ }
+ }
+ return implode(' and ', $parr);
+ }
+
+
+ //------------------------------------------------------------ Public functions below
+
+ function Load($where=null,$bindarr=false)
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $this->_where = $where;
+
+ $save = $db->SetFetchMode(ADODB_FETCH_NUM);
+ $qry = "select * from ".$this->_table;
+ $table =& $this->TableInfo();
+
+ if(($k = reset($table->keys))) {
+ $hasManyId = $k;
+ }
+ else {
+ $hasManyId = 'id';
+ }
+
+ foreach($table->_belongsTo as $foreignTable) {
+ if(($k = reset($foreignTable->TableInfo()->keys))) {
+ $belongsToId = $k;
+ }
+ else {
+ $belongsToId = 'id';
+ }
+ $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
+ $this->_table.'.'.$foreignTable->foreignKey.'='.
+ $foreignTable->_table.'.'.$belongsToId;
+ }
+ foreach($table->_hasMany as $foreignTable)
+ {
+ $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
+ $this->_table.'.'.$hasManyId.'='.
+ $foreignTable->_table.'.'.$foreignTable->foreignKey;
+ }
+ if($where) {
+ $qry .= ' WHERE '.$where;
+ }
+
+ // Simple case: no relations. Load row and return.
+ if((count($table->_hasMany) + count($table->_belongsTo)) < 1) {
+ $row = $db->GetRow($qry,$bindarr);
+ if(!$row) {
+ return false;
+ }
+ $db->SetFetchMode($save);
+ return $this->Set($row);
+ }
+
+ // More complex case when relations have to be collated
+ $rows = $db->GetAll($qry,$bindarr);
+ if(!$rows) {
+ return false;
+ }
+ $db->SetFetchMode($save);
+ if(count($rows) < 1) {
+ return false;
+ }
+ $class = get_class($this);
+ $isFirstRow = true;
+
+ if(($k = reset($this->TableInfo()->keys))) {
+ $myId = $k;
+ }
+ else {
+ $myId = 'id';
+ }
+ $index = 0; $found = false;
+ /** @todo Improve by storing once and for all in table metadata */
+ /** @todo Also re-use info for hasManyId */
+ foreach($this->TableInfo()->flds as $fld) {
+ if($fld->name == $myId) {
+ $found = true;
+ break;
+ }
+ $index++;
+ }
+ if(!$found) {
+ $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
+ }
+
+ foreach($rows as $row) {
+ $rowId = intval($row[$index]);
+ if($rowId > 0) {
+ if($isFirstRow) {
+ $isFirstRow = false;
+ if(!$this->Set($row)) {
+ return false;
+ }
+ }
+ $obj = new $class($table,false,$db);
+ $obj->Set($row);
+ // TODO Copy/paste code below: bad!
+ if(count($table->_hasMany) > 0) {
+ foreach($table->_hasMany as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ if(!is_array($this->$foreignName)) {
+ $foreignObj = $this->$foreignName;
+ $this->$foreignName = array(clone($foreignObj));
+ }
+ else {
+ $foreignObj = $obj->$foreignName;
+ array_push($this->$foreignName, clone($foreignObj));
+ }
+ }
+ }
+ }
+ if(count($table->_belongsTo) > 0) {
+ foreach($table->_belongsTo as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ if(!is_array($this->$foreignName)) {
+ $foreignObj = $this->$foreignName;
+ $this->$foreignName = array(clone($foreignObj));
+ }
+ else {
+ $foreignObj = $obj->$foreignName;
+ array_push($this->$foreignName, clone($foreignObj));
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ // false on error
+ function Save()
+ {
+ if ($this->_saved) {
+ $ok = $this->Update();
+ }
+ else {
+ $ok = $this->Insert();
+ }
+
+ return $ok;
+ }
+
+ // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted.
+ // Sample use case: an 'undo' command object (after a delete())
+ function Dirty()
+ {
+ $this->_saved = false;
+ }
+
+ // false on error
+ function Insert()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $cnt = 0;
+ $table = $this->TableInfo();
+
+ $valarr = array();
+ $names = array();
+ $valstr = array();
+
+ foreach($table->flds as $name=>$fld) {
+ $val = $this->$name;
+ if(!is_null($val) || !array_key_exists($name, $table->keys)) {
+ $valarr[] = $val;
+ $names[] = $name;
+ $valstr[] = $db->Param($cnt);
+ $cnt += 1;
+ }
+ }
+
+ if (empty($names)){
+ foreach($table->flds as $name=>$fld) {
+ $valarr[] = null;
+ $names[] = $name;
+ $valstr[] = $db->Param($cnt);
+ $cnt += 1;
+ }
+ }
+ $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
+ $ok = $db->Execute($sql,$valarr);
+
+ if ($ok) {
+ $this->_saved = true;
+ $autoinc = false;
+ foreach($table->keys as $k) {
+ if (is_null($this->$k)) {
+ $autoinc = true;
+ break;
+ }
+ }
+ if ($autoinc && sizeof($table->keys) == 1) {
+ $k = reset($table->keys);
+ $this->$k = $this->LastInsertID($db,$k);
+ }
+ }
+
+ $this->_original = $valarr;
+ return !empty($ok);
+ }
+
+ function Delete()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $where = $this->GenWhere($db,$table);
+ $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
+ $ok = $db->Execute($sql);
+
+ return $ok ? true : false;
+ }
+
+ // returns an array of active record objects
+ function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
+ {
+ $db = $this->DB();
+ if (!$db || empty($this->_table)) {
+ return false;
+ }
+ $table =& $this->TableInfo();
+ $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
+ array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
+ return $arr;
+ }
+
+ // CFR: In introduced this method to ensure that inner workings are not disturbed by
+ // subclasses...for instance when GetActiveRecordsClass invokes Find()
+ // Why am I not invoking parent::Find?
+ // Shockingly because I want to preserve PHP4 compatibility.
+ function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
+ {
+ $db = $this->DB();
+ if (!$db || empty($this->_table)) {
+ return false;
+ }
+ $table =& $this->TableInfo();
+ $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
+ array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
+ return $arr;
+ }
+
+ // returns 0 on error, 1 on update, 2 on insert
+ function Replace()
+ {
+ global $ADODB_ASSOC_CASE;
+
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $pkey = $table->keys;
+
+ foreach($table->flds as $name=>$fld) {
+ $val = $this->$name;
+ /*
+ if (is_null($val)) {
+ if (isset($fld->not_null) && $fld->not_null) {
+ if (isset($fld->default_value) && strlen($fld->default_value)) {
+ continue;
+ }
+ else {
+ $this->Error("Cannot update null into $name","Replace");
+ return false;
+ }
+ }
+ }*/
+ if (is_null($val) && !empty($fld->auto_increment)) {
+ continue;
+ }
+ $t = $db->MetaType($fld->type);
+ $arr[$name] = $this->doquote($db,$val,$t);
+ $valarr[] = $val;
+ }
+
+ if (!is_array($pkey)) {
+ $pkey = array($pkey);
+ }
+
+
+ switch ($ADODB_ASSOC_CASE == 0) {
+ case ADODB_ASSOC_CASE_LOWER:
+ foreach($pkey as $k => $v) {
+ $pkey[$k] = strtolower($v);
+ }
+ break;
+ case ADODB_ASSOC_CASE_UPPER:
+ foreach($pkey as $k => $v) {
+ $pkey[$k] = strtoupper($v);
+ }
+ break;
+ }
+
+ $ok = $db->Replace($this->_table,$arr,$pkey);
+ if ($ok) {
+ $this->_saved = true; // 1= update 2=insert
+ if ($ok == 2) {
+ $autoinc = false;
+ foreach($table->keys as $k) {
+ if (is_null($this->$k)) {
+ $autoinc = true;
+ break;
+ }
+ }
+ if ($autoinc && sizeof($table->keys) == 1) {
+ $k = reset($table->keys);
+ $this->$k = $this->LastInsertID($db,$k);
+ }
+ }
+
+ $this->_original = $valarr;
+ }
+ return $ok;
+ }
+
+ // returns 0 on error, 1 on update, -1 if no change in data (no update)
+ function Update()
+ {
+ $db = $this->DB();
+ if (!$db) {
+ return false;
+ }
+ $table = $this->TableInfo();
+
+ $where = $this->GenWhere($db, $table);
+
+ if (!$where) {
+ $this->error("Where missing for table $table", "Update");
+ return false;
+ }
+ $valarr = array();
+ $neworig = array();
+ $pairs = array();
+ $i = -1;
+ $cnt = 0;
+ foreach($table->flds as $name=>$fld) {
+ $i += 1;
+ $val = $this->$name;
+ $neworig[] = $val;
+
+ if (isset($table->keys[$name])) {
+ continue;
+ }
+
+ if (is_null($val)) {
+ if (isset($fld->not_null) && $fld->not_null) {
+ if (isset($fld->default_value) && strlen($fld->default_value)) {
+ continue;
+ }
+ else {
+ $this->Error("Cannot set field $name to NULL","Update");
+ return false;
+ }
+ }
+ }
+
+ if (isset($this->_original[$i]) && $val === $this->_original[$i]) {
+ continue;
+ }
+ $valarr[] = $val;
+ $pairs[] = $name.'='.$db->Param($cnt);
+ $cnt += 1;
+ }
+
+
+ if (!$cnt) {
+ return -1;
+ }
+ $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
+ $ok = $db->Execute($sql,$valarr);
+ if ($ok) {
+ $this->_original = $neworig;
+ return 1;
+ }
+ return 0;
+ }
+
+ function GetAttributeNames()
+ {
+ $table = $this->TableInfo();
+ if (!$table) {
+ return false;
+ }
+ return array_keys($table->flds);
+ }
+
+};
+
+function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
+ $extra, $relations)
+{
+ global $_ADODB_ACTIVE_DBS;
+
+ if (empty($extra['loading'])) {
+ $extra['loading'] = ADODB_LAZY_AR;
+ }
+ $save = $db->SetFetchMode(ADODB_FETCH_NUM);
+ $table = &$tableObj->_table;
+ $tableInfo =& $tableObj->TableInfo();
+ if(($k = reset($tableInfo->keys))) {
+ $myId = $k;
+ }
+ else {
+ $myId = 'id';
+ }
+ $index = 0; $found = false;
+ /** @todo Improve by storing once and for all in table metadata */
+ /** @todo Also re-use info for hasManyId */
+ foreach($tableInfo->flds as $fld)
+ {
+ if($fld->name == $myId) {
+ $found = true;
+ break;
+ }
+ $index++;
+ }
+ if(!$found) {
+ $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
+ }
+
+ $qry = "select * from ".$table;
+ if(ADODB_JOIN_AR == $extra['loading']) {
+ if(!empty($relations['belongsTo'])) {
+ foreach($relations['belongsTo'] as $foreignTable) {
+ if(($k = reset($foreignTable->TableInfo()->keys))) {
+ $belongsToId = $k;
+ }
+ else {
+ $belongsToId = 'id';
+ }
+
+ $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
+ $table.'.'.$foreignTable->foreignKey.'='.
+ $foreignTable->_table.'.'.$belongsToId;
+ }
+ }
+ if(!empty($relations['hasMany'])) {
+ if(empty($relations['foreignName'])) {
+ $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass');
+ }
+ if(($k = reset($tableInfo->keys))) {
+ $hasManyId = $k;
+ }
+ else {
+ $hasManyId = 'id';
+ }
+
+ foreach($relations['hasMany'] as $foreignTable) {
+ $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
+ $table.'.'.$hasManyId.'='.
+ $foreignTable->_table.'.'.$foreignTable->foreignKey;
+ }
+ }
+ }
+ if (!empty($whereOrderBy)) {
+ $qry .= ' WHERE '.$whereOrderBy;
+ }
+ if(isset($extra['limit'])) {
+ $rows = false;
+ if(isset($extra['offset'])) {
+ $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']);
+ } else {
+ $rs = $db->SelectLimit($qry, $extra['limit']);
+ }
+ if ($rs) {
+ while (!$rs->EOF) {
+ $rows[] = $rs->fields;
+ $rs->MoveNext();
+ }
+ }
+ } else
+ $rows = $db->GetAll($qry,$bindarr);
+
+ $db->SetFetchMode($save);
+
+ $false = false;
+
+ if ($rows === false) {
+ return $false;
+ }
+
+
+ if (!isset($_ADODB_ACTIVE_DBS)) {
+ include(ADODB_DIR.'/adodb-active-record.inc.php');
+ }
+ if (!class_exists($class)) {
+ $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
+ return $false;
+ }
+ $uniqArr = array(); // CFR Keep track of records for relations
+ $arr = array();
+ // arrRef will be the structure that knows about our objects.
+ // It is an associative array.
+ // We will, however, return arr, preserving regular 0.. order so that
+ // obj[0] can be used by app developpers.
+ $arrRef = array();
+ $bTos = array(); // Will store belongTo's indices if any
+ foreach($rows as $row) {
+
+ $obj = new $class($table,$primkeyArr,$db);
+ if ($obj->ErrorNo()){
+ $db->_errorMsg = $obj->ErrorMsg();
+ return $false;
+ }
+ $obj->Set($row);
+ // CFR: FIXME: Insane assumption here:
+ // If the first column returned is an integer, then it's a 'id' field
+ // And to make things a bit worse, I use intval() rather than is_int() because, in fact,
+ // $row[0] is not an integer.
+ //
+ // So, what does this whole block do?
+ // When relationships are found, we perform JOINs. This is fast. But not accurate:
+ // instead of returning n objects with their n' associated cousins,
+ // we get n*n' objects. This code fixes this.
+ // Note: to-many relationships mess around with the 'limit' parameter
+ $rowId = intval($row[$index]);
+
+ if(ADODB_WORK_AR == $extra['loading']) {
+ $arrRef[$rowId] = $obj;
+ $arr[] = &$arrRef[$rowId];
+ if(!isset($indices)) {
+ $indices = $rowId;
+ }
+ else {
+ $indices .= ','.$rowId;
+ }
+ if(!empty($relations['belongsTo'])) {
+ foreach($relations['belongsTo'] as $foreignTable) {
+ $foreignTableRef = $foreignTable->foreignKey;
+ // First array: list of foreign ids we are looking for
+ if(empty($bTos[$foreignTableRef])) {
+ $bTos[$foreignTableRef] = array();
+ }
+ // Second array: list of ids found
+ if(empty($obj->$foreignTableRef)) {
+ continue;
+ }
+ if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef])) {
+ $bTos[$foreignTableRef][$obj->$foreignTableRef] = array();
+ }
+ $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj;
+ }
+ }
+ continue;
+ }
+
+ if($rowId>0) {
+ if(ADODB_JOIN_AR == $extra['loading']) {
+ $isNewObj = !isset($uniqArr['_'.$row[0]]);
+ if($isNewObj) {
+ $uniqArr['_'.$row[0]] = $obj;
+ }
+
+ // TODO Copy/paste code below: bad!
+ if(!empty($relations['hasMany'])) {
+ foreach($relations['hasMany'] as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ $masterObj = &$uniqArr['_'.$row[0]];
+ // Assumption: this property exists in every object since they are instances of the same class
+ if(!is_array($masterObj->$foreignName)) {
+ // Pluck!
+ $foreignObj = $masterObj->$foreignName;
+ $masterObj->$foreignName = array(clone($foreignObj));
+ }
+ else {
+ // Pluck pluck!
+ $foreignObj = $obj->$foreignName;
+ array_push($masterObj->$foreignName, clone($foreignObj));
+ }
+ }
+ }
+ }
+ if(!empty($relations['belongsTo'])) {
+ foreach($relations['belongsTo'] as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ $masterObj = &$uniqArr['_'.$row[0]];
+ // Assumption: this property exists in every object since they are instances of the same class
+ if(!is_array($masterObj->$foreignName)) {
+ // Pluck!
+ $foreignObj = $masterObj->$foreignName;
+ $masterObj->$foreignName = array(clone($foreignObj));
+ }
+ else {
+ // Pluck pluck!
+ $foreignObj = $obj->$foreignName;
+ array_push($masterObj->$foreignName, clone($foreignObj));
+ }
+ }
+ }
+ }
+ if(!$isNewObj) {
+ unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array
+ }
+ }
+ else if(ADODB_LAZY_AR == $extra['loading']) {
+ // Lazy loading: we need to give AdoDb a hint that we have not really loaded
+ // anything, all the while keeping enough information on what we wish to load.
+ // Let's do this by keeping the relevant info in our relationship arrays
+ // but get rid of the actual properties.
+ // We will then use PHP's __get to load these properties on-demand.
+ if(!empty($relations['hasMany'])) {
+ foreach($relations['hasMany'] as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ unset($obj->$foreignName);
+ }
+ }
+ }
+ if(!empty($relations['belongsTo'])) {
+ foreach($relations['belongsTo'] as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ if(!empty($obj->$foreignName)) {
+ unset($obj->$foreignName);
+ }
+ }
+ }
+ }
+ }
+
+ if(isset($obj)) {
+ $arr[] = $obj;
+ }
+ }
+
+ if(ADODB_WORK_AR == $extra['loading']) {
+ // The best of both worlds?
+ // Here, the number of queries is constant: 1 + n*relationship.
+ // The second query will allow us to perform a good join
+ // while preserving LIMIT etc.
+ if(!empty($relations['hasMany'])) {
+ foreach($relations['hasMany'] as $foreignTable) {
+ $foreignName = $foreignTable->foreignName;
+ $className = ucfirst($foreignTable->_singularize($foreignName));
+ $obj = new $className();
+ $dbClassRef = $foreignTable->foreignKey;
+ $objs = $obj->packageFind($dbClassRef.' IN ('.$indices.')');
+ foreach($objs as $obj) {
+ if(!is_array($arrRef[$obj->$dbClassRef]->$foreignName)) {
+ $arrRef[$obj->$dbClassRef]->$foreignName = array();
+ }
+ array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj);
+ }
+ }
+
+ }
+ if(!empty($relations['belongsTo'])) {
+ foreach($relations['belongsTo'] as $foreignTable) {
+ $foreignTableRef = $foreignTable->foreignKey;
+ if(empty($bTos[$foreignTableRef])) {
+ continue;
+ }
+ if(($k = reset($foreignTable->TableInfo()->keys))) {
+ $belongsToId = $k;
+ }
+ else {
+ $belongsToId = 'id';
+ }
+ $origObjsArr = $bTos[$foreignTableRef];
+ $bTosString = implode(',', array_keys($bTos[$foreignTableRef]));
+ $foreignName = $foreignTable->foreignName;
+ $className = ucfirst($foreignTable->_singularize($foreignName));
+ $obj = new $className();
+ $objs = $obj->packageFind($belongsToId.' IN ('.$bTosString.')');
+ foreach($objs as $obj)
+ {
+ foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj)
+ {
+ $origObj->$foreignName = $obj;
+ }
+ }
+ }
+ }
+ }
+
+ return $arr;
+}
diff --git a/vendor/adodb/adodb-php/adodb-csvlib.inc.php b/vendor/adodb/adodb-php/adodb-csvlib.inc.php
new file mode 100644
index 0000000..90b8219
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-csvlib.inc.php
@@ -0,0 +1,315 @@
+<?php
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+global $ADODB_INCLUDED_CSV;
+$ADODB_INCLUDED_CSV = 1;
+
+/*
+
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for CSV serialization. This is used by the csv/proxy driver and is the
+ CacheExecute() serialization format.
+*/
+
+ /**
+ * convert a recordset into special format
+ *
+ * @param rs the recordset
+ *
+ * @return the CSV formated data
+ */
+ function _rs2serialize(&$rs,$conn=false,$sql='')
+ {
+ $max = ($rs) ? $rs->FieldCount() : 0;
+
+ if ($sql) $sql = urlencode($sql);
+ // metadata setup
+
+ if ($max <= 0 || $rs->dataProvider == 'empty') { // is insert/update/delete
+ if (is_object($conn)) {
+ $sql .= ','.$conn->Affected_Rows();
+ $sql .= ','.$conn->Insert_ID();
+ } else
+ $sql .= ',,';
+
+ $text = "====-1,0,$sql\n";
+ return $text;
+ }
+ $tt = ($rs->timeCreated) ? $rs->timeCreated : time();
+
+ ## changed format from ====0 to ====1
+ $line = "====1,$tt,$sql\n";
+
+ if ($rs->databaseType == 'array') {
+ $rows = $rs->_array;
+ } else {
+ $rows = array();
+ while (!$rs->EOF) {
+ $rows[] = $rs->fields;
+ $rs->MoveNext();
+ }
+ }
+
+ for($i=0; $i < $max; $i++) {
+ $o = $rs->FetchField($i);
+ $flds[] = $o;
+ }
+
+ $savefetch = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
+ $class = $rs->connection->arrayClass;
+ $rs2 = new $class();
+ $rs2->timeCreated = $rs->timeCreated; # memcache fix
+ $rs2->sql = $rs->sql;
+ $rs2->oldProvider = $rs->dataProvider;
+ $rs2->InitArrayFields($rows,$flds);
+ $rs2->fetchMode = $savefetch;
+ return $line.serialize($rs2);
+ }
+
+
+/**
+* Open CSV file and convert it into Data.
+*
+* @param url file/ftp/http url
+* @param err returns the error message
+* @param timeout dispose if recordset has been alive for $timeout secs
+*
+* @return recordset, or false if error occured. If no
+* error occurred in sql INSERT/UPDATE/DELETE,
+* empty recordset is returned
+*/
+ function csv2rs($url,&$err,$timeout=0, $rsclass='ADORecordSet_array')
+ {
+ $false = false;
+ $err = false;
+ $fp = @fopen($url,'rb');
+ if (!$fp) {
+ $err = $url.' file/URL not found';
+ return $false;
+ }
+ @flock($fp, LOCK_SH);
+ $arr = array();
+ $ttl = 0;
+
+ if ($meta = fgetcsv($fp, 32000, ",")) {
+ // check if error message
+ if (strncmp($meta[0],'****',4) === 0) {
+ $err = trim(substr($meta[0],4,1024));
+ fclose($fp);
+ return $false;
+ }
+ // check for meta data
+ // $meta[0] is -1 means return an empty recordset
+ // $meta[1] contains a time
+
+ if (strncmp($meta[0], '====',4) === 0) {
+
+ if ($meta[0] == "====-1") {
+ if (sizeof($meta) < 5) {
+ $err = "Corrupt first line for format -1";
+ fclose($fp);
+ return $false;
+ }
+ fclose($fp);
+
+ if ($timeout > 0) {
+ $err = " Illegal Timeout $timeout ";
+ return $false;
+ }
+
+ $rs = new $rsclass($val=true);
+ $rs->fields = array();
+ $rs->timeCreated = $meta[1];
+ $rs->EOF = true;
+ $rs->_numOfFields = 0;
+ $rs->sql = urldecode($meta[2]);
+ $rs->affectedrows = (integer)$meta[3];
+ $rs->insertid = $meta[4];
+ return $rs;
+ }
+ # Under high volume loads, we want only 1 thread/process to _write_file
+ # so that we don't have 50 processes queueing to write the same data.
+ # We use probabilistic timeout, ahead of time.
+ #
+ # -4 sec before timeout, give processes 1/32 chance of timing out
+ # -2 sec before timeout, give processes 1/16 chance of timing out
+ # -1 sec after timeout give processes 1/4 chance of timing out
+ # +0 sec after timeout, give processes 100% chance of timing out
+ if (sizeof($meta) > 1) {
+ if($timeout >0){
+ $tdiff = (integer)( $meta[1]+$timeout - time());
+ if ($tdiff <= 2) {
+ switch($tdiff) {
+ case 4:
+ case 3:
+ if ((rand() & 31) == 0) {
+ fclose($fp);
+ $err = "Timeout 3";
+ return $false;
+ }
+ break;
+ case 2:
+ if ((rand() & 15) == 0) {
+ fclose($fp);
+ $err = "Timeout 2";
+ return $false;
+ }
+ break;
+ case 1:
+ if ((rand() & 3) == 0) {
+ fclose($fp);
+ $err = "Timeout 1";
+ return $false;
+ }
+ break;
+ default:
+ fclose($fp);
+ $err = "Timeout 0";
+ return $false;
+ } // switch
+
+ } // if check flush cache
+ }// (timeout>0)
+ $ttl = $meta[1];
+ }
+ //================================================
+ // new cache format - use serialize extensively...
+ if ($meta[0] === '====1') {
+ // slurp in the data
+ $MAXSIZE = 128000;
+
+ $text = fread($fp,$MAXSIZE);
+ if (strlen($text)) {
+ while ($txt = fread($fp,$MAXSIZE)) {
+ $text .= $txt;
+ }
+ }
+ fclose($fp);
+ $rs = unserialize($text);
+ if (is_object($rs)) $rs->timeCreated = $ttl;
+ else {
+ $err = "Unable to unserialize recordset";
+ //echo htmlspecialchars($text),' !--END--!<p>';
+ }
+ return $rs;
+ }
+
+ $meta = false;
+ $meta = fgetcsv($fp, 32000, ",");
+ if (!$meta) {
+ fclose($fp);
+ $err = "Unexpected EOF 1";
+ return $false;
+ }
+ }
+
+ // Get Column definitions
+ $flds = array();
+ foreach($meta as $o) {
+ $o2 = explode(':',$o);
+ if (sizeof($o2)!=3) {
+ $arr[] = $meta;
+ $flds = false;
+ break;
+ }
+ $fld = new ADOFieldObject();
+ $fld->name = urldecode($o2[0]);
+ $fld->type = $o2[1];
+ $fld->max_length = $o2[2];
+ $flds[] = $fld;
+ }
+ } else {
+ fclose($fp);
+ $err = "Recordset had unexpected EOF 2";
+ return $false;
+ }
+
+ // slurp in the data
+ $MAXSIZE = 128000;
+
+ $text = '';
+ while ($txt = fread($fp,$MAXSIZE)) {
+ $text .= $txt;
+ }
+
+ fclose($fp);
+ @$arr = unserialize($text);
+ //var_dump($arr);
+ if (!is_array($arr)) {
+ $err = "Recordset had unexpected EOF (in serialized recordset)";
+ if (get_magic_quotes_runtime()) $err .= ". Magic Quotes Runtime should be disabled!";
+ return $false;
+ }
+ $rs = new $rsclass();
+ $rs->timeCreated = $ttl;
+ $rs->InitArrayFields($arr,$flds);
+ return $rs;
+ }
+
+
+ /**
+ * Save a file $filename and its $contents (normally for caching) with file locking
+ * Returns true if ok, false if fopen/fwrite error, 0 if rename error (eg. file is locked)
+ */
+ function adodb_write_file($filename, $contents,$debug=false)
+ {
+ # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows
+ # So to simulate locking, we assume that rename is an atomic operation.
+ # First we delete $filename, then we create a $tempfile write to it and
+ # rename to the desired $filename. If the rename works, then we successfully
+ # modified the file exclusively.
+ # What a stupid need - having to simulate locking.
+ # Risks:
+ # 1. $tempfile name is not unique -- very very low
+ # 2. unlink($filename) fails -- ok, rename will fail
+ # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs
+ # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and cache updated
+ if (strncmp(PHP_OS,'WIN',3) === 0) {
+ // skip the decimal place
+ $mtime = substr(str_replace(' ','_',microtime()),2);
+ // getmypid() actually returns 0 on Win98 - never mind!
+ $tmpname = $filename.uniqid($mtime).getmypid();
+ if (!($fd = @fopen($tmpname,'w'))) return false;
+ if (fwrite($fd,$contents)) $ok = true;
+ else $ok = false;
+ fclose($fd);
+
+ if ($ok) {
+ @chmod($tmpname,0644);
+ // the tricky moment
+ @unlink($filename);
+ if (!@rename($tmpname,$filename)) {
+ @unlink($tmpname);
+ $ok = 0;
+ }
+ if (!$ok) {
+ if ($debug) ADOConnection::outp( " Rename $tmpname ".($ok? 'ok' : 'failed'));
+ }
+ }
+ return $ok;
+ }
+ if (!($fd = @fopen($filename, 'a'))) return false;
+ if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) {
+ if (fwrite( $fd, $contents )) $ok = true;
+ else $ok = false;
+ fclose($fd);
+ @chmod($filename,0644);
+ }else {
+ fclose($fd);
+ if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename<br>\n");
+ $ok = false;
+ }
+
+ return $ok;
+ }
diff --git a/vendor/adodb/adodb-php/adodb-datadict.inc.php b/vendor/adodb/adodb-php/adodb-datadict.inc.php
new file mode 100644
index 0000000..c62d298
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-datadict.inc.php
@@ -0,0 +1,1033 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+ DOCUMENTATION:
+
+ See adodb/tests/test-datadict.php for docs and examples.
+*/
+
+/*
+ Test script for parser
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+function Lens_ParseTest()
+{
+$str = "`zcol ACOL` NUMBER(32,2) DEFAULT 'The \"cow\" (and Jim''s dog) jumps over the moon' PRIMARY, INTI INT AUTO DEFAULT 0, zcol2\"afs ds";
+print "<p>$str</p>";
+$a= Lens_ParseArgs($str);
+print "<pre>";
+print_r($a);
+print "</pre>";
+}
+
+
+if (!function_exists('ctype_alnum')) {
+ function ctype_alnum($text) {
+ return preg_match('/^[a-z0-9]*$/i', $text);
+ }
+}
+
+//Lens_ParseTest();
+
+/**
+ Parse arguments, treat "text" (text) and 'text' as quotation marks.
+ To escape, use "" or '' or ))
+
+ Will read in "abc def" sans quotes, as: abc def
+ Same with 'abc def'.
+ However if `abc def`, then will read in as `abc def`
+
+ @param endstmtchar Character that indicates end of statement
+ @param tokenchars Include the following characters in tokens apart from A-Z and 0-9
+ @returns 2 dimensional array containing parsed tokens.
+*/
+function Lens_ParseArgs($args,$endstmtchar=',',$tokenchars='_.-')
+{
+ $pos = 0;
+ $intoken = false;
+ $stmtno = 0;
+ $endquote = false;
+ $tokens = array();
+ $tokens[$stmtno] = array();
+ $max = strlen($args);
+ $quoted = false;
+ $tokarr = array();
+
+ while ($pos < $max) {
+ $ch = substr($args,$pos,1);
+ switch($ch) {
+ case ' ':
+ case "\t":
+ case "\n":
+ case "\r":
+ if (!$quoted) {
+ if ($intoken) {
+ $intoken = false;
+ $tokens[$stmtno][] = implode('',$tokarr);
+ }
+ break;
+ }
+
+ $tokarr[] = $ch;
+ break;
+
+ case '`':
+ if ($intoken) $tokarr[] = $ch;
+ case '(':
+ case ')':
+ case '"':
+ case "'":
+
+ if ($intoken) {
+ if (empty($endquote)) {
+ $tokens[$stmtno][] = implode('',$tokarr);
+ if ($ch == '(') $endquote = ')';
+ else $endquote = $ch;
+ $quoted = true;
+ $intoken = true;
+ $tokarr = array();
+ } else if ($endquote == $ch) {
+ $ch2 = substr($args,$pos+1,1);
+ if ($ch2 == $endquote) {
+ $pos += 1;
+ $tokarr[] = $ch2;
+ } else {
+ $quoted = false;
+ $intoken = false;
+ $tokens[$stmtno][] = implode('',$tokarr);
+ $endquote = '';
+ }
+ } else
+ $tokarr[] = $ch;
+
+ }else {
+
+ if ($ch == '(') $endquote = ')';
+ else $endquote = $ch;
+ $quoted = true;
+ $intoken = true;
+ $tokarr = array();
+ if ($ch == '`') $tokarr[] = '`';
+ }
+ break;
+
+ default:
+
+ if (!$intoken) {
+ if ($ch == $endstmtchar) {
+ $stmtno += 1;
+ $tokens[$stmtno] = array();
+ break;
+ }
+
+ $intoken = true;
+ $quoted = false;
+ $endquote = false;
+ $tokarr = array();
+
+ }
+
+ if ($quoted) $tokarr[] = $ch;
+ else if (ctype_alnum($ch) || strpos($tokenchars,$ch) !== false) $tokarr[] = $ch;
+ else {
+ if ($ch == $endstmtchar) {
+ $tokens[$stmtno][] = implode('',$tokarr);
+ $stmtno += 1;
+ $tokens[$stmtno] = array();
+ $intoken = false;
+ $tokarr = array();
+ break;
+ }
+ $tokens[$stmtno][] = implode('',$tokarr);
+ $tokens[$stmtno][] = $ch;
+ $intoken = false;
+ }
+ }
+ $pos += 1;
+ }
+ if ($intoken) $tokens[$stmtno][] = implode('',$tokarr);
+
+ return $tokens;
+}
+
+
+class ADODB_DataDict {
+ var $connection;
+ var $debug = false;
+ var $dropTable = 'DROP TABLE %s';
+ var $renameTable = 'RENAME TABLE %s TO %s';
+ var $dropIndex = 'DROP INDEX %s';
+ var $addCol = ' ADD';
+ var $alterCol = ' ALTER COLUMN';
+ var $dropCol = ' DROP COLUMN';
+ var $renameColumn = 'ALTER TABLE %s RENAME COLUMN %s TO %s'; // table, old-column, new-column, column-definitions (not used by default)
+ var $nameRegex = '\w';
+ var $nameRegexBrackets = 'a-zA-Z0-9_\(\)';
+ var $schema = false;
+ var $serverInfo = array();
+ var $autoIncrement = false;
+ var $dataProvider;
+ var $invalidResizeTypes4 = array('CLOB','BLOB','TEXT','DATE','TIME'); // for changetablesql
+ var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob
+ /// in other words, we use a text area for editting.
+
+ function GetCommentSQL($table,$col)
+ {
+ return false;
+ }
+
+ function SetCommentSQL($table,$col,$cmt)
+ {
+ return false;
+ }
+
+ function MetaTables()
+ {
+ if (!$this->connection->IsConnected()) return array();
+ return $this->connection->MetaTables();
+ }
+
+ function MetaColumns($tab, $upper=true, $schema=false)
+ {
+ if (!$this->connection->IsConnected()) return array();
+ return $this->connection->MetaColumns($this->TableName($tab), $upper, $schema);
+ }
+
+ function MetaPrimaryKeys($tab,$owner=false,$intkey=false)
+ {
+ if (!$this->connection->IsConnected()) return array();
+ return $this->connection->MetaPrimaryKeys($this->TableName($tab), $owner, $intkey);
+ }
+
+ function MetaIndexes($table, $primary = false, $owner = false)
+ {
+ if (!$this->connection->IsConnected()) return array();
+ return $this->connection->MetaIndexes($this->TableName($table), $primary, $owner);
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ static $typeMap = array(
+ 'VARCHAR' => 'C',
+ 'VARCHAR2' => 'C',
+ 'CHAR' => 'C',
+ 'C' => 'C',
+ 'STRING' => 'C',
+ 'NCHAR' => 'C',
+ 'NVARCHAR' => 'C',
+ 'VARYING' => 'C',
+ 'BPCHAR' => 'C',
+ 'CHARACTER' => 'C',
+ 'INTERVAL' => 'C', # Postgres
+ 'MACADDR' => 'C', # postgres
+ 'VAR_STRING' => 'C', # mysql
+ ##
+ 'LONGCHAR' => 'X',
+ 'TEXT' => 'X',
+ 'NTEXT' => 'X',
+ 'M' => 'X',
+ 'X' => 'X',
+ 'CLOB' => 'X',
+ 'NCLOB' => 'X',
+ 'LVARCHAR' => 'X',
+ ##
+ 'BLOB' => 'B',
+ 'IMAGE' => 'B',
+ 'BINARY' => 'B',
+ 'VARBINARY' => 'B',
+ 'LONGBINARY' => 'B',
+ 'B' => 'B',
+ ##
+ 'YEAR' => 'D', // mysql
+ 'DATE' => 'D',
+ 'D' => 'D',
+ ##
+ 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server
+ ##
+ 'TIME' => 'T',
+ 'TIMESTAMP' => 'T',
+ 'DATETIME' => 'T',
+ 'TIMESTAMPTZ' => 'T',
+ 'SMALLDATETIME' => 'T',
+ 'T' => 'T',
+ 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql
+ ##
+ 'BOOL' => 'L',
+ 'BOOLEAN' => 'L',
+ 'BIT' => 'L',
+ 'L' => 'L',
+ ##
+ 'COUNTER' => 'R',
+ 'R' => 'R',
+ 'SERIAL' => 'R', // ifx
+ 'INT IDENTITY' => 'R',
+ ##
+ 'INT' => 'I',
+ 'INT2' => 'I',
+ 'INT4' => 'I',
+ 'INT8' => 'I',
+ 'INTEGER' => 'I',
+ 'INTEGER UNSIGNED' => 'I',
+ 'SHORT' => 'I',
+ 'TINYINT' => 'I',
+ 'SMALLINT' => 'I',
+ 'I' => 'I',
+ ##
+ 'LONG' => 'N', // interbase is numeric, oci8 is blob
+ 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
+ 'DECIMAL' => 'N',
+ 'DEC' => 'N',
+ 'REAL' => 'N',
+ 'DOUBLE' => 'N',
+ 'DOUBLE PRECISION' => 'N',
+ 'SMALLFLOAT' => 'N',
+ 'FLOAT' => 'N',
+ 'NUMBER' => 'N',
+ 'NUM' => 'N',
+ 'NUMERIC' => 'N',
+ 'MONEY' => 'N',
+
+ ## informix 9.2
+ 'SQLINT' => 'I',
+ 'SQLSERIAL' => 'I',
+ 'SQLSMINT' => 'I',
+ 'SQLSMFLOAT' => 'N',
+ 'SQLFLOAT' => 'N',
+ 'SQLMONEY' => 'N',
+ 'SQLDECIMAL' => 'N',
+ 'SQLDATE' => 'D',
+ 'SQLVCHAR' => 'C',
+ 'SQLCHAR' => 'C',
+ 'SQLDTIME' => 'T',
+ 'SQLINTERVAL' => 'N',
+ 'SQLBYTES' => 'B',
+ 'SQLTEXT' => 'X',
+ ## informix 10
+ "SQLINT8" => 'I8',
+ "SQLSERIAL8" => 'I8',
+ "SQLNCHAR" => 'C',
+ "SQLNVCHAR" => 'C',
+ "SQLLVARCHAR" => 'X',
+ "SQLBOOL" => 'L'
+ );
+
+ if (!$this->connection->IsConnected()) {
+ $t = strtoupper($t);
+ if (isset($typeMap[$t])) return $typeMap[$t];
+ return 'N';
+ }
+ return $this->connection->MetaType($t,$len,$fieldobj);
+ }
+
+ function NameQuote($name = NULL,$allowBrackets=false)
+ {
+ if (!is_string($name)) {
+ return FALSE;
+ }
+
+ $name = trim($name);
+
+ if ( !is_object($this->connection) ) {
+ return $name;
+ }
+
+ $quote = $this->connection->nameQuote;
+
+ // if name is of the form `name`, quote it
+ if ( preg_match('/^`(.+)`$/', $name, $matches) ) {
+ return $quote . $matches[1] . $quote;
+ }
+
+ // if name contains special characters, quote it
+ $regex = ($allowBrackets) ? $this->nameRegexBrackets : $this->nameRegex;
+
+ if ( !preg_match('/^[' . $regex . ']+$/', $name) ) {
+ return $quote . $name . $quote;
+ }
+
+ return $name;
+ }
+
+ function TableName($name)
+ {
+ if ( $this->schema ) {
+ return $this->NameQuote($this->schema) .'.'. $this->NameQuote($name);
+ }
+ return $this->NameQuote($name);
+ }
+
+ // Executes the sql array returned by GetTableSQL and GetIndexSQL
+ function ExecuteSQLArray($sql, $continueOnError = true)
+ {
+ $rez = 2;
+ $conn = $this->connection;
+ $saved = $conn->debug;
+ foreach($sql as $line) {
+
+ if ($this->debug) $conn->debug = true;
+ $ok = $conn->Execute($line);
+ $conn->debug = $saved;
+ if (!$ok) {
+ if ($this->debug) ADOConnection::outp($conn->ErrorMsg());
+ if (!$continueOnError) return 0;
+ $rez = 1;
+ }
+ }
+ return $rez;
+ }
+
+ /**
+ Returns the actual type given a character code.
+
+ C: varchar
+ X: CLOB (character large object) or largest varchar size if CLOB is not supported
+ C2: Multibyte varchar
+ X2: Multibyte CLOB
+
+ B: BLOB (binary large object)
+
+ D: Date
+ T: Date-time
+ L: Integer field suitable for storing booleans (0 or 1)
+ I: Integer
+ F: Floating point number
+ N: Numeric or decimal number
+ */
+
+ function ActualType($meta)
+ {
+ return $meta;
+ }
+
+ function CreateDatabase($dbname,$options=false)
+ {
+ $options = $this->_Options($options);
+ $sql = array();
+
+ $s = 'CREATE DATABASE ' . $this->NameQuote($dbname);
+ if (isset($options[$this->upperName]))
+ $s .= ' '.$options[$this->upperName];
+
+ $sql[] = $s;
+ return $sql;
+ }
+
+ /*
+ Generates the SQL to create index. Returns an array of sql strings.
+ */
+ function CreateIndexSQL($idxname, $tabname, $flds, $idxoptions = false)
+ {
+ if (!is_array($flds)) {
+ $flds = explode(',',$flds);
+ }
+
+ foreach($flds as $key => $fld) {
+ # some indexes can use partial fields, eg. index first 32 chars of "name" with NAME(32)
+ $flds[$key] = $this->NameQuote($fld,$allowBrackets=true);
+ }
+
+ return $this->_IndexSQL($this->NameQuote($idxname), $this->TableName($tabname), $flds, $this->_Options($idxoptions));
+ }
+
+ function DropIndexSQL ($idxname, $tabname = NULL)
+ {
+ return array(sprintf($this->dropIndex, $this->NameQuote($idxname), $this->TableName($tabname)));
+ }
+
+ function SetSchema($schema)
+ {
+ $this->schema = $schema;
+ }
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+ $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' ';
+ foreach($lines as $v) {
+ $sql[] = $alter . $v;
+ }
+ if (is_array($idxs)) {
+ foreach($idxs as $idx => $idxdef) {
+ $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
+ $sql = array_merge($sql, $sql_idxs);
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * Change the definition of one column
+ *
+ * As some DBM's can't do that on there own, you need to supply the complete defintion of the new table,
+ * to allow, recreating the table and copying the content over to the new table
+ * @param string $tabname table-name
+ * @param string $flds column-name and type for the changed column
+ * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default ''
+ * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default ''
+ * @return array with SQL strings
+ */
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+ $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
+ foreach($lines as $v) {
+ $sql[] = $alter . $v;
+ }
+ if (is_array($idxs)) {
+ foreach($idxs as $idx => $idxdef) {
+ $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
+ $sql = array_merge($sql, $sql_idxs);
+ }
+
+ }
+ return $sql;
+ }
+
+ /**
+ * Rename one column
+ *
+ * Some DBM's can only do this together with changeing the type of the column (even if that stays the same, eg. mysql)
+ * @param string $tabname table-name
+ * @param string $oldcolumn column-name to be renamed
+ * @param string $newcolumn new column-name
+ * @param string $flds='' complete column-defintion-string like for AddColumnSQL, only used by mysql atm., default=''
+ * @return array with SQL strings
+ */
+ function RenameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='')
+ {
+ $tabname = $this->TableName ($tabname);
+ if ($flds) {
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+ $first = current($lines);
+ list(,$column_def) = preg_split("/[\t ]+/",$first,2);
+ }
+ return array(sprintf($this->renameColumn,$tabname,$this->NameQuote($oldcolumn),$this->NameQuote($newcolumn),$column_def));
+ }
+
+ /**
+ * Drop one column
+ *
+ * Some DBM's can't do that on there own, you need to supply the complete defintion of the new table,
+ * to allow, recreating the table and copying the content over to the new table
+ * @param string $tabname table-name
+ * @param string $flds column-name and type for the changed column
+ * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default ''
+ * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default ''
+ * @return array with SQL strings
+ */
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ if (!is_array($flds)) $flds = explode(',',$flds);
+ $sql = array();
+ $alter = 'ALTER TABLE ' . $tabname . $this->dropCol . ' ';
+ foreach($flds as $v) {
+ $sql[] = $alter . $this->NameQuote($v);
+ }
+ return $sql;
+ }
+
+ function DropTableSQL($tabname)
+ {
+ return array (sprintf($this->dropTable, $this->TableName($tabname)));
+ }
+
+ function RenameTableSQL($tabname,$newname)
+ {
+ return array (sprintf($this->renameTable, $this->TableName($tabname),$this->TableName($newname)));
+ }
+
+ /**
+ Generate the SQL to create table. Returns an array of sql strings.
+ */
+ function CreateTableSQL($tabname, $flds, $tableoptions=array())
+ {
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds, true);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+
+ $taboptions = $this->_Options($tableoptions);
+ $tabname = $this->TableName ($tabname);
+ $sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions);
+
+ // ggiunta - 2006/10/12 - KLUDGE:
+ // if we are on autoincrement, and table options includes REPLACE, the
+ // autoincrement sequence has already been dropped on table creation sql, so
+ // we avoid passing REPLACE to trigger creation code. This prevents
+ // creating sql that double-drops the sequence
+ if ($this->autoIncrement && isset($taboptions['REPLACE']))
+ unset($taboptions['REPLACE']);
+ $tsql = $this->_Triggers($tabname,$taboptions);
+ foreach($tsql as $s) $sql[] = $s;
+
+ if (is_array($idxs)) {
+ foreach($idxs as $idx => $idxdef) {
+ $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
+ $sql = array_merge($sql, $sql_idxs);
+ }
+ }
+
+ return $sql;
+ }
+
+
+
+ function _GenFields($flds,$widespacing=false)
+ {
+ if (is_string($flds)) {
+ $padding = ' ';
+ $txt = $flds.$padding;
+ $flds = array();
+ $flds0 = Lens_ParseArgs($txt,',');
+ $hasparam = false;
+ foreach($flds0 as $f0) {
+ $f1 = array();
+ foreach($f0 as $token) {
+ switch (strtoupper($token)) {
+ case 'INDEX':
+ $f1['INDEX'] = '';
+ // fall through intentionally
+ case 'CONSTRAINT':
+ case 'DEFAULT':
+ $hasparam = $token;
+ break;
+ default:
+ if ($hasparam) $f1[$hasparam] = $token;
+ else $f1[] = $token;
+ $hasparam = false;
+ break;
+ }
+ }
+ // 'index' token without a name means single column index: name it after column
+ if (array_key_exists('INDEX', $f1) && $f1['INDEX'] == '') {
+ $f1['INDEX'] = isset($f0['NAME']) ? $f0['NAME'] : $f0[0];
+ // check if column name used to create an index name was quoted
+ if (($f1['INDEX'][0] == '"' || $f1['INDEX'][0] == "'" || $f1['INDEX'][0] == "`") &&
+ ($f1['INDEX'][0] == substr($f1['INDEX'], -1))) {
+ $f1['INDEX'] = $f1['INDEX'][0].'idx_'.substr($f1['INDEX'], 1, -1).$f1['INDEX'][0];
+ }
+ else
+ $f1['INDEX'] = 'idx_'.$f1['INDEX'];
+ }
+ // reset it, so we don't get next field 1st token as INDEX...
+ $hasparam = false;
+
+ $flds[] = $f1;
+
+ }
+ }
+ $this->autoIncrement = false;
+ $lines = array();
+ $pkey = array();
+ $idxs = array();
+ foreach($flds as $fld) {
+ $fld = _array_change_key_case($fld);
+
+ $fname = false;
+ $fdefault = false;
+ $fautoinc = false;
+ $ftype = false;
+ $fsize = false;
+ $fprec = false;
+ $fprimary = false;
+ $fnoquote = false;
+ $fdefts = false;
+ $fdefdate = false;
+ $fconstraint = false;
+ $fnotnull = false;
+ $funsigned = false;
+ $findex = '';
+ $funiqueindex = false;
+
+ //-----------------
+ // Parse attributes
+ foreach($fld as $attr => $v) {
+ if ($attr == 2 && is_numeric($v)) $attr = 'SIZE';
+ else if (is_numeric($attr) && $attr > 1 && !is_numeric($v)) $attr = strtoupper($v);
+
+ switch($attr) {
+ case '0':
+ case 'NAME': $fname = $v; break;
+ case '1':
+ case 'TYPE': $ty = $v; $ftype = $this->ActualType(strtoupper($v)); break;
+
+ case 'SIZE':
+ $dotat = strpos($v,'.'); if ($dotat === false) $dotat = strpos($v,',');
+ if ($dotat === false) $fsize = $v;
+ else {
+ $fsize = substr($v,0,$dotat);
+ $fprec = substr($v,$dotat+1);
+ }
+ break;
+ case 'UNSIGNED': $funsigned = true; break;
+ case 'AUTOINCREMENT':
+ case 'AUTO': $fautoinc = true; $fnotnull = true; break;
+ case 'KEY':
+ // a primary key col can be non unique in itself (if key spans many cols...)
+ case 'PRIMARY': $fprimary = $v; $fnotnull = true; /*$funiqueindex = true;*/ break;
+ case 'DEF':
+ case 'DEFAULT': $fdefault = $v; break;
+ case 'NOTNULL': $fnotnull = $v; break;
+ case 'NOQUOTE': $fnoquote = $v; break;
+ case 'DEFDATE': $fdefdate = $v; break;
+ case 'DEFTIMESTAMP': $fdefts = $v; break;
+ case 'CONSTRAINT': $fconstraint = $v; break;
+ // let INDEX keyword create a 'very standard' index on column
+ case 'INDEX': $findex = $v; break;
+ case 'UNIQUE': $funiqueindex = true; break;
+ } //switch
+ } // foreach $fld
+
+ //--------------------
+ // VALIDATE FIELD INFO
+ if (!strlen($fname)) {
+ if ($this->debug) ADOConnection::outp("Undefined NAME");
+ return false;
+ }
+
+ $fid = strtoupper(preg_replace('/^`(.+)`$/', '$1', $fname));
+ $fname = $this->NameQuote($fname);
+
+ if (!strlen($ftype)) {
+ if ($this->debug) ADOConnection::outp("Undefined TYPE for field '$fname'");
+ return false;
+ } else {
+ $ftype = strtoupper($ftype);
+ }
+
+ $ftype = $this->_GetSize($ftype, $ty, $fsize, $fprec);
+
+ if ($ty == 'X' || $ty == 'X2' || $ty == 'B') $fnotnull = false; // some blob types do not accept nulls
+
+ if ($fprimary) $pkey[] = $fname;
+
+ // some databases do not allow blobs to have defaults
+ if ($ty == 'X') $fdefault = false;
+
+ // build list of indexes
+ if ($findex != '') {
+ if (array_key_exists($findex, $idxs)) {
+ $idxs[$findex]['cols'][] = ($fname);
+ if (in_array('UNIQUE', $idxs[$findex]['opts']) != $funiqueindex) {
+ if ($this->debug) ADOConnection::outp("Index $findex defined once UNIQUE and once not");
+ }
+ if ($funiqueindex && !in_array('UNIQUE', $idxs[$findex]['opts']))
+ $idxs[$findex]['opts'][] = 'UNIQUE';
+ }
+ else
+ {
+ $idxs[$findex] = array();
+ $idxs[$findex]['cols'] = array($fname);
+ if ($funiqueindex)
+ $idxs[$findex]['opts'] = array('UNIQUE');
+ else
+ $idxs[$findex]['opts'] = array();
+ }
+ }
+
+ //--------------------
+ // CONSTRUCT FIELD SQL
+ if ($fdefts) {
+ if (substr($this->connection->databaseType,0,5) == 'mysql') {
+ $ftype = 'TIMESTAMP';
+ } else {
+ $fdefault = $this->connection->sysTimeStamp;
+ }
+ } else if ($fdefdate) {
+ if (substr($this->connection->databaseType,0,5) == 'mysql') {
+ $ftype = 'TIMESTAMP';
+ } else {
+ $fdefault = $this->connection->sysDate;
+ }
+ } else if ($fdefault !== false && !$fnoquote) {
+ if ($ty == 'C' or $ty == 'X' or
+ ( substr($fdefault,0,1) != "'" && !is_numeric($fdefault))) {
+
+ if (($ty == 'D' || $ty == 'T') && strtolower($fdefault) != 'null') {
+ // convert default date into database-aware code
+ if ($ty == 'T')
+ {
+ $fdefault = $this->connection->DBTimeStamp($fdefault);
+ }
+ else
+ {
+ $fdefault = $this->connection->DBDate($fdefault);
+ }
+ }
+ else
+ if (strlen($fdefault) != 1 && substr($fdefault,0,1) == ' ' && substr($fdefault,strlen($fdefault)-1) == ' ')
+ $fdefault = trim($fdefault);
+ else if (strtolower($fdefault) != 'null')
+ $fdefault = $this->connection->qstr($fdefault);
+ }
+ }
+ $suffix = $this->_CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned);
+
+ // add index creation
+ if ($widespacing) $fname = str_pad($fname,24);
+
+ // check for field names appearing twice
+ if (array_key_exists($fid, $lines)) {
+ ADOConnection::outp("Field '$fname' defined twice");
+ }
+
+ $lines[$fid] = $fname.' '.$ftype.$suffix;
+
+ if ($fautoinc) $this->autoIncrement = true;
+ } // foreach $flds
+
+ return array($lines,$pkey,$idxs);
+ }
+
+ /**
+ GENERATE THE SIZE PART OF THE DATATYPE
+ $ftype is the actual type
+ $ty is the type defined originally in the DDL
+ */
+ function _GetSize($ftype, $ty, $fsize, $fprec)
+ {
+ if (strlen($fsize) && $ty != 'X' && $ty != 'B' && strpos($ftype,'(') === false) {
+ $ftype .= "(".$fsize;
+ if (strlen($fprec)) $ftype .= ",".$fprec;
+ $ftype .= ')';
+ }
+ return $ftype;
+ }
+
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
+
+ $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' ';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s .= '(' . $flds . ')';
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+ function _DropAutoIncrement($tabname)
+ {
+ return false;
+ }
+
+ function _TableSQL($tabname,$lines,$pkey,$tableoptions)
+ {
+ $sql = array();
+
+ if (isset($tableoptions['REPLACE']) || isset ($tableoptions['DROP'])) {
+ $sql[] = sprintf($this->dropTable,$tabname);
+ if ($this->autoIncrement) {
+ $sInc = $this->_DropAutoIncrement($tabname);
+ if ($sInc) $sql[] = $sInc;
+ }
+ if ( isset ($tableoptions['DROP']) ) {
+ return $sql;
+ }
+ }
+ $s = "CREATE TABLE $tabname (\n";
+ $s .= implode(",\n", $lines);
+ if (sizeof($pkey)>0) {
+ $s .= ",\n PRIMARY KEY (";
+ $s .= implode(", ",$pkey).")";
+ }
+ if (isset($tableoptions['CONSTRAINTS']))
+ $s .= "\n".$tableoptions['CONSTRAINTS'];
+
+ if (isset($tableoptions[$this->upperName.'_CONSTRAINTS']))
+ $s .= "\n".$tableoptions[$this->upperName.'_CONSTRAINTS'];
+
+ $s .= "\n)";
+ if (isset($tableoptions[$this->upperName])) $s .= $tableoptions[$this->upperName];
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+ /**
+ GENERATE TRIGGERS IF NEEDED
+ used when table has auto-incrementing field that is emulated using triggers
+ */
+ function _Triggers($tabname,$taboptions)
+ {
+ return array();
+ }
+
+ /**
+ Sanitize options, so that array elements with no keys are promoted to keys
+ */
+ function _Options($opts)
+ {
+ if (!is_array($opts)) return array();
+ $newopts = array();
+ foreach($opts as $k => $v) {
+ if (is_numeric($k)) $newopts[strtoupper($v)] = $v;
+ else $newopts[strtoupper($k)] = $v;
+ }
+ return $newopts;
+ }
+
+
+ function _getSizePrec($size)
+ {
+ $fsize = false;
+ $fprec = false;
+ $dotat = strpos($size,'.');
+ if ($dotat === false) $dotat = strpos($size,',');
+ if ($dotat === false) $fsize = $size;
+ else {
+ $fsize = substr($size,0,$dotat);
+ $fprec = substr($size,$dotat+1);
+ }
+ return array($fsize, $fprec);
+ }
+
+ /**
+ "Florian Buzin [ easywe ]" <florian.buzin#easywe.de>
+
+ This function changes/adds new fields to your table. You don't
+ have to know if the col is new or not. It will check on its own.
+ */
+ function ChangeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($this->connection->fetchMode !== false) $savem = $this->connection->SetFetchMode(false);
+
+ // check table exists
+ $save_handler = $this->connection->raiseErrorFn;
+ $this->connection->raiseErrorFn = '';
+ $cols = $this->MetaColumns($tablename);
+ $this->connection->raiseErrorFn = $save_handler;
+
+ if (isset($savem)) $this->connection->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ( empty($cols)) {
+ return $this->CreateTableSQL($tablename, $flds, $tableoptions);
+ }
+
+ if (is_array($flds)) {
+ // Cycle through the update fields, comparing
+ // existing fields to fields to update.
+ // if the Metatype and size is exactly the
+ // same, ignore - by Mark Newham
+ $holdflds = array();
+ foreach($flds as $k=>$v) {
+ if ( isset($cols[$k]) && is_object($cols[$k]) ) {
+ // If already not allowing nulls, then don't change
+ $obj = $cols[$k];
+ if (isset($obj->not_null) && $obj->not_null)
+ $v = str_replace('NOT NULL','',$v);
+ if (isset($obj->auto_increment) && $obj->auto_increment && empty($v['AUTOINCREMENT']))
+ $v = str_replace('AUTOINCREMENT','',$v);
+
+ $c = $cols[$k];
+ $ml = $c->max_length;
+ $mt = $this->MetaType($c->type,$ml);
+
+ if (isset($c->scale)) $sc = $c->scale;
+ else $sc = 99; // always force change if scale not known.
+
+ if ($sc == -1) $sc = false;
+ list($fsize, $fprec) = $this->_getSizePrec($v['SIZE']);
+
+ if ($ml == -1) $ml = '';
+ if ($mt == 'X') $ml = $v['SIZE'];
+ if (($mt != $v['TYPE']) || ($ml != $fsize || $sc != $fprec) || (isset($v['AUTOINCREMENT']) && $v['AUTOINCREMENT'] != $obj->auto_increment)) {
+ $holdflds[$k] = $v;
+ }
+ } else {
+ $holdflds[$k] = $v;
+ }
+ }
+ $flds = $holdflds;
+ }
+
+
+ // already exists, alter table instead
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+ $alter = 'ALTER TABLE ' . $this->TableName($tablename);
+ $sql = array();
+
+ foreach ( $lines as $id => $v ) {
+ if ( isset($cols[$id]) && is_object($cols[$id]) ) {
+
+ $flds = Lens_ParseArgs($v,',');
+
+ // We are trying to change the size of the field, if not allowed, simply ignore the request.
+ // $flds[1] holds the type, $flds[2] holds the size -postnuke addition
+ if ($flds && in_array(strtoupper(substr($flds[0][1],0,4)),$this->invalidResizeTypes4)
+ && (isset($flds[0][2]) && is_numeric($flds[0][2]))) {
+ if ($this->debug) ADOConnection::outp(sprintf("<h3>%s cannot be changed to %s currently</h3>", $flds[0][0], $flds[0][1]));
+ #echo "<h3>$this->alterCol cannot be changed to $flds currently</h3>";
+ continue;
+ }
+ $sql[] = $alter . $this->alterCol . ' ' . $v;
+ } else {
+ $sql[] = $alter . $this->addCol . ' ' . $v;
+ }
+ }
+
+ if ($dropOldFlds) {
+ foreach ( $cols as $id => $v )
+ if ( !isset($lines[$id]) )
+ $sql[] = $alter . $this->dropCol . ' ' . $v->name;
+ }
+ return $sql;
+ }
+} // class
diff --git a/vendor/adodb/adodb-php/adodb-error.inc.php b/vendor/adodb/adodb-php/adodb-error.inc.php
new file mode 100644
index 0000000..8de414a
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-error.inc.php
@@ -0,0 +1,265 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * The following code is adapted from the PEAR DB error handling code.
+ * Portions (c)1997-2002 The PHP Group.
+ */
+
+
+if (!defined("DB_ERROR")) define("DB_ERROR",-1);
+
+if (!defined("DB_ERROR_SYNTAX")) {
+ define("DB_ERROR_SYNTAX", -2);
+ define("DB_ERROR_CONSTRAINT", -3);
+ define("DB_ERROR_NOT_FOUND", -4);
+ define("DB_ERROR_ALREADY_EXISTS", -5);
+ define("DB_ERROR_UNSUPPORTED", -6);
+ define("DB_ERROR_MISMATCH", -7);
+ define("DB_ERROR_INVALID", -8);
+ define("DB_ERROR_NOT_CAPABLE", -9);
+ define("DB_ERROR_TRUNCATED", -10);
+ define("DB_ERROR_INVALID_NUMBER", -11);
+ define("DB_ERROR_INVALID_DATE", -12);
+ define("DB_ERROR_DIVZERO", -13);
+ define("DB_ERROR_NODBSELECTED", -14);
+ define("DB_ERROR_CANNOT_CREATE", -15);
+ define("DB_ERROR_CANNOT_DELETE", -16);
+ define("DB_ERROR_CANNOT_DROP", -17);
+ define("DB_ERROR_NOSUCHTABLE", -18);
+ define("DB_ERROR_NOSUCHFIELD", -19);
+ define("DB_ERROR_NEED_MORE_DATA", -20);
+ define("DB_ERROR_NOT_LOCKED", -21);
+ define("DB_ERROR_VALUE_COUNT_ON_ROW", -22);
+ define("DB_ERROR_INVALID_DSN", -23);
+ define("DB_ERROR_CONNECT_FAILED", -24);
+ define("DB_ERROR_EXTENSION_NOT_FOUND",-25);
+ define("DB_ERROR_NOSUCHDB", -25);
+ define("DB_ERROR_ACCESS_VIOLATION", -26);
+ define("DB_ERROR_DEADLOCK", -27);
+ define("DB_ERROR_STATEMENT_TIMEOUT", -28);
+ define("DB_ERROR_SERIALIZATION_FAILURE", -29);
+}
+
+function adodb_errormsg($value)
+{
+global $ADODB_LANG,$ADODB_LANG_ARRAY;
+
+ if (empty($ADODB_LANG)) $ADODB_LANG = 'en';
+ if (isset($ADODB_LANG_ARRAY['LANG']) && $ADODB_LANG_ARRAY['LANG'] == $ADODB_LANG) ;
+ else {
+ include_once(ADODB_DIR."/lang/adodb-$ADODB_LANG.inc.php");
+ }
+ return isset($ADODB_LANG_ARRAY[$value]) ? $ADODB_LANG_ARRAY[$value] : $ADODB_LANG_ARRAY[DB_ERROR];
+}
+
+function adodb_error($provider,$dbType,$errno)
+{
+ //var_dump($errno);
+ if (is_numeric($errno) && $errno == 0) return 0;
+ switch($provider) {
+ case 'mysql': $map = adodb_error_mysql(); break;
+
+ case 'oracle':
+ case 'oci8': $map = adodb_error_oci8(); break;
+
+ case 'ibase': $map = adodb_error_ibase(); break;
+
+ case 'odbc': $map = adodb_error_odbc(); break;
+
+ case 'mssql':
+ case 'sybase': $map = adodb_error_mssql(); break;
+
+ case 'informix': $map = adodb_error_ifx(); break;
+
+ case 'postgres': return adodb_error_pg($errno); break;
+
+ case 'sqlite': return $map = adodb_error_sqlite(); break;
+ default:
+ return DB_ERROR;
+ }
+ //print_r($map);
+ //var_dump($errno);
+ if (isset($map[$errno])) return $map[$errno];
+ return DB_ERROR;
+}
+
+//**************************************************************************************
+
+function adodb_error_pg($errormsg)
+{
+ if (is_numeric($errormsg)) return (integer) $errormsg;
+ // Postgres has no lock-wait timeout. The best we could do would be to set a statement timeout.
+ static $error_regexps = array(
+ '(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$' => DB_ERROR_NOSUCHTABLE,
+ 'Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*|duplicate key.*violates unique constraint' => DB_ERROR_ALREADY_EXISTS,
+ 'database ".+" does not exist$' => DB_ERROR_NOSUCHDB,
+ '(divide|division) by zero$' => DB_ERROR_DIVZERO,
+ 'pg_atoi: error in .*: can\'t parse ' => DB_ERROR_INVALID_NUMBER,
+ 'ttribute [\"\'].*[\"\'] not found|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']' => DB_ERROR_NOSUCHFIELD,
+ '(parser: parse|syntax) error at or near \"' => DB_ERROR_SYNTAX,
+ 'referential integrity violation' => DB_ERROR_CONSTRAINT,
+ 'deadlock detected$' => DB_ERROR_DEADLOCK,
+ 'canceling statement due to statement timeout$' => DB_ERROR_STATEMENT_TIMEOUT,
+ 'could not serialize access due to' => DB_ERROR_SERIALIZATION_FAILURE
+ );
+ reset($error_regexps);
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match("/$regexp/mi", $errormsg)) {
+ return $code;
+ }
+ }
+ // Fall back to DB_ERROR if there was no mapping.
+ return DB_ERROR;
+}
+
+function adodb_error_odbc()
+{
+static $MAP = array(
+ '01004' => DB_ERROR_TRUNCATED,
+ '07001' => DB_ERROR_MISMATCH,
+ '21S01' => DB_ERROR_MISMATCH,
+ '21S02' => DB_ERROR_MISMATCH,
+ '22003' => DB_ERROR_INVALID_NUMBER,
+ '22008' => DB_ERROR_INVALID_DATE,
+ '22012' => DB_ERROR_DIVZERO,
+ '23000' => DB_ERROR_CONSTRAINT,
+ '24000' => DB_ERROR_INVALID,
+ '34000' => DB_ERROR_INVALID,
+ '37000' => DB_ERROR_SYNTAX,
+ '42000' => DB_ERROR_SYNTAX,
+ 'IM001' => DB_ERROR_UNSUPPORTED,
+ 'S0000' => DB_ERROR_NOSUCHTABLE,
+ 'S0001' => DB_ERROR_NOT_FOUND,
+ 'S0002' => DB_ERROR_NOSUCHTABLE,
+ 'S0011' => DB_ERROR_ALREADY_EXISTS,
+ 'S0012' => DB_ERROR_NOT_FOUND,
+ 'S0021' => DB_ERROR_ALREADY_EXISTS,
+ 'S0022' => DB_ERROR_NOT_FOUND,
+ 'S1000' => DB_ERROR_NOSUCHTABLE,
+ 'S1009' => DB_ERROR_INVALID,
+ 'S1090' => DB_ERROR_INVALID,
+ 'S1C00' => DB_ERROR_NOT_CAPABLE
+ );
+ return $MAP;
+}
+
+function adodb_error_ibase()
+{
+static $MAP = array(
+ -104 => DB_ERROR_SYNTAX,
+ -150 => DB_ERROR_ACCESS_VIOLATION,
+ -151 => DB_ERROR_ACCESS_VIOLATION,
+ -155 => DB_ERROR_NOSUCHTABLE,
+ -157 => DB_ERROR_NOSUCHFIELD,
+ -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ -170 => DB_ERROR_MISMATCH,
+ -171 => DB_ERROR_MISMATCH,
+ -172 => DB_ERROR_INVALID,
+ -204 => DB_ERROR_INVALID,
+ -205 => DB_ERROR_NOSUCHFIELD,
+ -206 => DB_ERROR_NOSUCHFIELD,
+ -208 => DB_ERROR_INVALID,
+ -219 => DB_ERROR_NOSUCHTABLE,
+ -297 => DB_ERROR_CONSTRAINT,
+ -530 => DB_ERROR_CONSTRAINT,
+ -803 => DB_ERROR_CONSTRAINT,
+ -551 => DB_ERROR_ACCESS_VIOLATION,
+ -552 => DB_ERROR_ACCESS_VIOLATION,
+ -922 => DB_ERROR_NOSUCHDB,
+ -923 => DB_ERROR_CONNECT_FAILED,
+ -924 => DB_ERROR_CONNECT_FAILED
+ );
+
+ return $MAP;
+}
+
+function adodb_error_ifx()
+{
+static $MAP = array(
+ '-201' => DB_ERROR_SYNTAX,
+ '-206' => DB_ERROR_NOSUCHTABLE,
+ '-217' => DB_ERROR_NOSUCHFIELD,
+ '-329' => DB_ERROR_NODBSELECTED,
+ '-1204' => DB_ERROR_INVALID_DATE,
+ '-1205' => DB_ERROR_INVALID_DATE,
+ '-1206' => DB_ERROR_INVALID_DATE,
+ '-1209' => DB_ERROR_INVALID_DATE,
+ '-1210' => DB_ERROR_INVALID_DATE,
+ '-1212' => DB_ERROR_INVALID_DATE
+ );
+
+ return $MAP;
+}
+
+function adodb_error_oci8()
+{
+static $MAP = array(
+ 1 => DB_ERROR_ALREADY_EXISTS,
+ 900 => DB_ERROR_SYNTAX,
+ 904 => DB_ERROR_NOSUCHFIELD,
+ 923 => DB_ERROR_SYNTAX,
+ 942 => DB_ERROR_NOSUCHTABLE,
+ 955 => DB_ERROR_ALREADY_EXISTS,
+ 1476 => DB_ERROR_DIVZERO,
+ 1722 => DB_ERROR_INVALID_NUMBER,
+ 2289 => DB_ERROR_NOSUCHTABLE,
+ 2291 => DB_ERROR_CONSTRAINT,
+ 2449 => DB_ERROR_CONSTRAINT
+ );
+
+ return $MAP;
+}
+
+function adodb_error_mssql()
+{
+static $MAP = array(
+ 208 => DB_ERROR_NOSUCHTABLE,
+ 2601 => DB_ERROR_ALREADY_EXISTS
+ );
+
+ return $MAP;
+}
+
+function adodb_error_sqlite()
+{
+static $MAP = array(
+ 1 => DB_ERROR_SYNTAX
+ );
+
+ return $MAP;
+}
+
+function adodb_error_mysql()
+{
+static $MAP = array(
+ 1004 => DB_ERROR_CANNOT_CREATE,
+ 1005 => DB_ERROR_CANNOT_CREATE,
+ 1006 => DB_ERROR_CANNOT_CREATE,
+ 1007 => DB_ERROR_ALREADY_EXISTS,
+ 1008 => DB_ERROR_CANNOT_DROP,
+ 1045 => DB_ERROR_ACCESS_VIOLATION,
+ 1046 => DB_ERROR_NODBSELECTED,
+ 1049 => DB_ERROR_NOSUCHDB,
+ 1050 => DB_ERROR_ALREADY_EXISTS,
+ 1051 => DB_ERROR_NOSUCHTABLE,
+ 1054 => DB_ERROR_NOSUCHFIELD,
+ 1062 => DB_ERROR_ALREADY_EXISTS,
+ 1064 => DB_ERROR_SYNTAX,
+ 1100 => DB_ERROR_NOT_LOCKED,
+ 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 1146 => DB_ERROR_NOSUCHTABLE,
+ 1048 => DB_ERROR_CONSTRAINT,
+ 2002 => DB_ERROR_CONNECT_FAILED,
+ 2005 => DB_ERROR_CONNECT_FAILED
+ );
+
+ return $MAP;
+}
diff --git a/vendor/adodb/adodb-php/adodb-errorhandler.inc.php b/vendor/adodb/adodb-php/adodb-errorhandler.inc.php
new file mode 100644
index 0000000..ce09f78
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-errorhandler.inc.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+*/
+
+
+// added Claudio Bustos clbustos#entelchile.net
+if (!defined('ADODB_ERROR_HANDLER_TYPE')) define('ADODB_ERROR_HANDLER_TYPE',E_USER_ERROR);
+
+if (!defined('ADODB_ERROR_HANDLER')) define('ADODB_ERROR_HANDLER','ADODB_Error_Handler');
+
+/**
+* Default Error Handler. This will be called with the following params
+*
+* @param $dbms the RDBMS you are connecting to
+* @param $fn the name of the calling function (in uppercase)
+* @param $errno the native error number from the database
+* @param $errmsg the native error msg from the database
+* @param $p1 $fn specific parameter - see below
+* @param $p2 $fn specific parameter - see below
+* @param $thisConn $current connection object - can be false if no connection object created
+*/
+function ADODB_Error_Handler($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection)
+{
+ if (error_reporting() == 0) return; // obey @ protocol
+ switch($fn) {
+ case 'EXECUTE':
+ $sql = $p1;
+ $inputparams = $p2;
+
+ $s = "$dbms error: [$errno: $errmsg] in $fn(\"$sql\")\n";
+ break;
+
+ case 'PCONNECT':
+ case 'CONNECT':
+ $host = $p1;
+ $database = $p2;
+
+ $s = "$dbms error: [$errno: $errmsg] in $fn($host, '****', '****', $database)\n";
+ break;
+ default:
+ $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)\n";
+ break;
+ }
+ /*
+ * Log connection error somewhere
+ * 0 message is sent to PHP's system logger, using the Operating System's system
+ * logging mechanism or a file, depending on what the error_log configuration
+ * directive is set to.
+ * 1 message is sent by email to the address in the destination parameter.
+ * This is the only message type where the fourth parameter, extra_headers is used.
+ * This message type uses the same internal function as mail() does.
+ * 2 message is sent through the PHP debugging connection.
+ * This option is only available if remote debugging has been enabled.
+ * In this case, the destination parameter specifies the host name or IP address
+ * and optionally, port number, of the socket receiving the debug information.
+ * 3 message is appended to the file destination
+ */
+ if (defined('ADODB_ERROR_LOG_TYPE')) {
+ $t = date('Y-m-d H:i:s');
+ if (defined('ADODB_ERROR_LOG_DEST'))
+ error_log("($t) $s", ADODB_ERROR_LOG_TYPE, ADODB_ERROR_LOG_DEST);
+ else
+ error_log("($t) $s", ADODB_ERROR_LOG_TYPE);
+ }
+
+
+ //print "<p>$s</p>";
+ trigger_error($s,ADODB_ERROR_HANDLER_TYPE);
+}
diff --git a/vendor/adodb/adodb-php/adodb-errorpear.inc.php b/vendor/adodb/adodb-php/adodb-errorpear.inc.php
new file mode 100644
index 0000000..4da387b
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-errorpear.inc.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+*/
+include_once('PEAR.php');
+
+if (!defined('ADODB_ERROR_HANDLER')) define('ADODB_ERROR_HANDLER','ADODB_Error_PEAR');
+
+/*
+* Enabled the following if you want to terminate scripts when an error occurs
+*/
+//PEAR::setErrorHandling (PEAR_ERROR_DIE);
+
+/*
+* Name of the PEAR_Error derived class to call.
+*/
+if (!defined('ADODB_PEAR_ERROR_CLASS')) define('ADODB_PEAR_ERROR_CLASS','PEAR_Error');
+
+/*
+* Store the last PEAR_Error object here
+*/
+global $ADODB_Last_PEAR_Error; $ADODB_Last_PEAR_Error = false;
+
+ /**
+* Error Handler with PEAR support. This will be called with the following params
+*
+* @param $dbms the RDBMS you are connecting to
+* @param $fn the name of the calling function (in uppercase)
+* @param $errno the native error number from the database
+* @param $errmsg the native error msg from the database
+* @param $p1 $fn specific parameter - see below
+* @param $P2 $fn specific parameter - see below
+ */
+function ADODB_Error_PEAR($dbms, $fn, $errno, $errmsg, $p1=false, $p2=false)
+{
+global $ADODB_Last_PEAR_Error;
+
+ if (error_reporting() == 0) return; // obey @ protocol
+ switch($fn) {
+ case 'EXECUTE':
+ $sql = $p1;
+ $inputparams = $p2;
+
+ $s = "$dbms error: [$errno: $errmsg] in $fn(\"$sql\")";
+ break;
+
+ case 'PCONNECT':
+ case 'CONNECT':
+ $host = $p1;
+ $database = $p2;
+
+ $s = "$dbms error: [$errno: $errmsg] in $fn('$host', ?, ?, '$database')";
+ break;
+
+ default:
+ $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)";
+ break;
+ }
+
+ $class = ADODB_PEAR_ERROR_CLASS;
+ $ADODB_Last_PEAR_Error = new $class($s, $errno,
+ $GLOBALS['_PEAR_default_error_mode'],
+ $GLOBALS['_PEAR_default_error_options'],
+ $errmsg);
+
+ //print "<p>!$s</p>";
+}
+
+/**
+* Returns last PEAR_Error object. This error might be for an error that
+* occured several sql statements ago.
+*/
+function ADODB_PEAR_Error()
+{
+global $ADODB_Last_PEAR_Error;
+
+ return $ADODB_Last_PEAR_Error;
+}
diff --git a/vendor/adodb/adodb-php/adodb-exceptions.inc.php b/vendor/adodb/adodb-php/adodb-exceptions.inc.php
new file mode 100644
index 0000000..4adea0d
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-exceptions.inc.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * Exception-handling code using PHP5 exceptions (try-catch-throw).
+ */
+
+
+if (!defined('ADODB_ERROR_HANDLER_TYPE')) define('ADODB_ERROR_HANDLER_TYPE',E_USER_ERROR);
+define('ADODB_ERROR_HANDLER','adodb_throw');
+
+class ADODB_Exception extends Exception {
+var $dbms;
+var $fn;
+var $sql = '';
+var $params = '';
+var $host = '';
+var $database = '';
+
+ function __construct($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection)
+ {
+ switch($fn) {
+ case 'EXECUTE':
+ $this->sql = is_array($p1) ? $p1[0] : $p1;
+ $this->params = $p2;
+ $s = "$dbms error: [$errno: $errmsg] in $fn(\"$this->sql\")";
+ break;
+
+ case 'PCONNECT':
+ case 'CONNECT':
+ $user = $thisConnection->user;
+ $s = "$dbms error: [$errno: $errmsg] in $fn($p1, '$user', '****', $p2)";
+ break;
+ default:
+ $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)";
+ break;
+ }
+
+ $this->dbms = $dbms;
+ if ($thisConnection) {
+ $this->host = $thisConnection->host;
+ $this->database = $thisConnection->database;
+ }
+ $this->fn = $fn;
+ $this->msg = $errmsg;
+
+ if (!is_numeric($errno)) $errno = -1;
+ parent::__construct($s,$errno);
+ }
+}
+
+/**
+* Default Error Handler. This will be called with the following params
+*
+* @param $dbms the RDBMS you are connecting to
+* @param $fn the name of the calling function (in uppercase)
+* @param $errno the native error number from the database
+* @param $errmsg the native error msg from the database
+* @param $p1 $fn specific parameter - see below
+* @param $P2 $fn specific parameter - see below
+*/
+
+function adodb_throw($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection)
+{
+global $ADODB_EXCEPTION;
+
+ if (error_reporting() == 0) return; // obey @ protocol
+ if (is_string($ADODB_EXCEPTION)) $errfn = $ADODB_EXCEPTION;
+ else $errfn = 'ADODB_EXCEPTION';
+ throw new $errfn($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection);
+}
diff --git a/vendor/adodb/adodb-php/adodb-iterator.inc.php b/vendor/adodb/adodb-php/adodb-iterator.inc.php
new file mode 100644
index 0000000..a223f38
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-iterator.inc.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4.
+
+ Declares the ADODB Base Class for PHP5 "ADODB_BASE_RS", and supports iteration with
+ the ADODB_Iterator class.
+
+ $rs = $db->Execute("select * from adoxyz");
+ foreach($rs as $k => $v) {
+ echo $k; print_r($v); echo "<br>";
+ }
+
+
+ Iterator code based on http://cvs.php.net/cvs.php/php-src/ext/spl/examples/cachingiterator.inc?login=2
+
+
+ Moved to adodb.inc.php to improve performance.
+ */
diff --git a/vendor/adodb/adodb-php/adodb-lib.inc.php b/vendor/adodb/adodb-php/adodb-lib.inc.php
new file mode 100644
index 0000000..372c02b
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-lib.inc.php
@@ -0,0 +1,1259 @@
+<?php
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+global $ADODB_INCLUDED_LIB;
+$ADODB_INCLUDED_LIB = 1;
+
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Less commonly used functions are placed here to reduce size of adodb.inc.php.
+*/
+
+function adodb_strip_order_by($sql)
+{
+ $rez = preg_match('/(\sORDER\s+BY\s(?:[^)](?!LIMIT))*)/is', $sql, $arr);
+ if ($arr)
+ if (strpos($arr[1], '(') !== false) {
+ $at = strpos($sql, $arr[1]);
+ $cntin = 0;
+ for ($i=$at, $max=strlen($sql); $i < $max; $i++) {
+ $ch = $sql[$i];
+ if ($ch == '(') {
+ $cntin += 1;
+ } elseif($ch == ')') {
+ $cntin -= 1;
+ if ($cntin < 0) {
+ break;
+ }
+ }
+ }
+ $sql = substr($sql,0,$at).substr($sql,$i);
+ } else {
+ $sql = str_replace($arr[1], '', $sql);
+ }
+ return $sql;
+}
+
+if (false) {
+ $sql = 'select * from (select a from b order by a(b),b(c) desc)';
+ $sql = '(select * from abc order by 1)';
+ die(adodb_strip_order_by($sql));
+}
+
+function adodb_probetypes(&$array,&$types,$probe=8)
+{
+// probe and guess the type
+ $types = array();
+ if ($probe > sizeof($array)) $max = sizeof($array);
+ else $max = $probe;
+
+
+ for ($j=0;$j < $max; $j++) {
+ $row = $array[$j];
+ if (!$row) break;
+ $i = -1;
+ foreach($row as $v) {
+ $i += 1;
+
+ if (isset($types[$i]) && $types[$i]=='C') continue;
+
+ //print " ($i ".$types[$i]. "$v) ";
+ $v = trim($v);
+
+ if (!preg_match('/^[+-]{0,1}[0-9\.]+$/',$v)) {
+ $types[$i] = 'C'; // once C, always C
+
+ continue;
+ }
+ if ($j == 0) {
+ // If empty string, we presume is character
+ // test for integer for 1st row only
+ // after that it is up to testing other rows to prove
+ // that it is not an integer
+ if (strlen($v) == 0) $types[$i] = 'C';
+ if (strpos($v,'.') !== false) $types[$i] = 'N';
+ else $types[$i] = 'I';
+ continue;
+ }
+
+ if (strpos($v,'.') !== false) $types[$i] = 'N';
+
+ }
+ }
+
+}
+
+function adodb_transpose(&$arr, &$newarr, &$hdr, &$fobjs)
+{
+ $oldX = sizeof(reset($arr));
+ $oldY = sizeof($arr);
+
+ if ($hdr) {
+ $startx = 1;
+ $hdr = array('Fields');
+ for ($y = 0; $y < $oldY; $y++) {
+ $hdr[] = $arr[$y][0];
+ }
+ } else
+ $startx = 0;
+
+ for ($x = $startx; $x < $oldX; $x++) {
+ if ($fobjs) {
+ $o = $fobjs[$x];
+ $newarr[] = array($o->name);
+ } else
+ $newarr[] = array();
+
+ for ($y = 0; $y < $oldY; $y++) {
+ $newarr[$x-$startx][] = $arr[$y][$x];
+ }
+ }
+}
+
+// Force key to upper.
+// See also http://www.php.net/manual/en/function.array-change-key-case.php
+function _array_change_key_case($an_array)
+{
+ if (is_array($an_array)) {
+ $new_array = array();
+ foreach($an_array as $key=>$value)
+ $new_array[strtoupper($key)] = $value;
+
+ return $new_array;
+ }
+
+ return $an_array;
+}
+
+function _adodb_replace(&$zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc)
+{
+ if (count($fieldArray) == 0) return 0;
+ $first = true;
+ $uSet = '';
+
+ if (!is_array($keyCol)) {
+ $keyCol = array($keyCol);
+ }
+ foreach($fieldArray as $k => $v) {
+ if ($v === null) {
+ $v = 'NULL';
+ $fieldArray[$k] = $v;
+ } else if ($autoQuote && /*!is_numeric($v) /*and strncmp($v,"'",1) !== 0 -- sql injection risk*/ strcasecmp($v,$zthis->null2null)!=0) {
+ $v = $zthis->qstr($v);
+ $fieldArray[$k] = $v;
+ }
+ if (in_array($k,$keyCol)) continue; // skip UPDATE if is key
+
+ if ($first) {
+ $first = false;
+ $uSet = "$k=$v";
+ } else
+ $uSet .= ",$k=$v";
+ }
+
+ $where = false;
+ foreach ($keyCol as $v) {
+ if (isset($fieldArray[$v])) {
+ if ($where) $where .= ' and '.$v.'='.$fieldArray[$v];
+ else $where = $v.'='.$fieldArray[$v];
+ }
+ }
+
+ if ($uSet && $where) {
+ $update = "UPDATE $table SET $uSet WHERE $where";
+
+ $rs = $zthis->Execute($update);
+
+
+ if ($rs) {
+ if ($zthis->poorAffectedRows) {
+ /*
+ The Select count(*) wipes out any errors that the update would have returned.
+ http://phplens.com/lens/lensforum/msgs.php?id=5696
+ */
+ if ($zthis->ErrorNo()<>0) return 0;
+
+ # affected_rows == 0 if update field values identical to old values
+ # for mysql - which is silly.
+
+ $cnt = $zthis->GetOne("select count(*) from $table where $where");
+ if ($cnt > 0) return 1; // record already exists
+ } else {
+ if (($zthis->Affected_Rows()>0)) return 1;
+ }
+ } else
+ return 0;
+ }
+
+ // print "<p>Error=".$this->ErrorNo().'<p>';
+ $first = true;
+ foreach($fieldArray as $k => $v) {
+ if ($has_autoinc && in_array($k,$keyCol)) continue; // skip autoinc col
+
+ if ($first) {
+ $first = false;
+ $iCols = "$k";
+ $iVals = "$v";
+ } else {
+ $iCols .= ",$k";
+ $iVals .= ",$v";
+ }
+ }
+ $insert = "INSERT INTO $table ($iCols) VALUES ($iVals)";
+ $rs = $zthis->Execute($insert);
+ return ($rs) ? 2 : 0;
+}
+
+// Requires $ADODB_FETCH_MODE = ADODB_FETCH_NUM
+function _adodb_getmenu(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
+ $size=0, $selectAttr='',$compareFields0=true)
+{
+ $hasvalue = false;
+
+ if (is_array($name))
+ {
+ /*
+ * Reserved for future use
+ */
+ }
+
+ if ($multiple or is_array($defstr)) {
+ if ($size==0) $size=5;
+ $attr = ' multiple size="'.$size.'"';
+ if (!strpos($name,'[]')) $name .= '[]';
+ } else if ($size) $attr = ' size="'.$size.'"';
+ else $attr ='';
+
+ $s = '<select name="'.$name.'"'.$attr.' '.$selectAttr.'>';
+ if ($blank1stItem)
+ {
+ if (is_string($blank1stItem)) {
+ $barr = explode(':',$blank1stItem);
+ if (sizeof($barr) == 1) $barr[] = '';
+ $s .= "\n<option value=\"".$barr[0]."\">".$barr[1]."</option>";
+ }
+ else
+ $s .= "\n<option></option>";
+ }
+ if ($zthis->FieldCount() > 1) $hasvalue=true;
+ else $compareFields0 = true;
+
+ $value = '';
+ $optgroup = null;
+ $firstgroup = true;
+ $fieldsize = $zthis->FieldCount();
+ while(!$zthis->EOF) {
+ $zval = rtrim(reset($zthis->fields));
+
+ if ($blank1stItem && $zval=="") {
+ $zthis->MoveNext();
+ continue;
+ }
+
+ $myFields = array_map('trim',array_values($zthis->fields));
+
+ if ($fieldsize > 1) {
+ if (isset($myFields[1]))
+ $zval2 = $myFields[1];
+ else
+ $zval2 = next($myFields);
+ }
+ $selected = ($compareFields0) ? $zval : $zval2;
+
+ if ($hasvalue)
+ $value = " value='".htmlspecialchars($zval2)."'";
+
+ if (is_array($defstr))
+ {
+
+ if (in_array($selected,$defstr))
+ $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>';
+ else
+ $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>';
+ }
+ else {
+ if (strcasecmp($selected,$defstr)==0)
+ $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>';
+ else
+ $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>';
+ }
+ $zthis->MoveNext();
+ } // while
+
+ return $s ."\n</select>\n";
+}
+
+// Requires $ADODB_FETCH_MODE = ADODB_FETCH_NUM
+function _adodb_getmenu_gp(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
+ $size=0, $selectAttr='',$compareFields0=true)
+{
+ $hasvalue = false;
+
+ if (is_array($name))
+ {
+ /*
+ * Reserved for future use
+ */
+ }
+
+ if ($multiple or is_array($defstr)) {
+ if ($size==0) $size=5;
+ $attr = ' multiple size="'.$size.'"';
+ if (!strpos($name,'[]')) $name .= '[]';
+ } else if ($size) $attr = ' size="'.$size.'"';
+ else $attr ='';
+
+ $s = '<select name="'.$name.'"'.$attr.' '.$selectAttr.'>';
+ if ($blank1stItem)
+ if (is_string($blank1stItem)) {
+ $barr = explode(':',$blank1stItem);
+ if (sizeof($barr) == 1) $barr[] = '';
+ $s .= "\n<option value=\"".$barr[0]."\">".$barr[1]."</option>";
+ } else $s .= "\n<option></option>";
+
+ if ($zthis->FieldCount() > 1) $hasvalue=true;
+ else $compareFields0 = true;
+
+ $value = '';
+ $optgroup = null;
+ $firstgroup = true;
+ $fieldsize = sizeof($zthis->fields);
+ while(!$zthis->EOF) {
+ $zval = rtrim(reset($zthis->fields));
+
+ if ($blank1stItem && $zval=="") {
+ $zthis->MoveNext();
+ continue;
+ }
+
+ $myFields = array_map('trim',array_values($zthis->fields));
+
+ if ($fieldsize > 1) {
+ if (isset($myFields[1]))
+ $zval2 = $myFields[1];
+ else
+ $zval2 = next($myFields);
+ }
+
+ $selected = ($compareFields0) ? $zval : $zval2;
+
+ $group = '';
+
+ if (isset($myFields[2])) {
+ $group = $myFields[2];
+ }
+
+ if ($optgroup != $group) {
+ $optgroup = $group;
+ if ($firstgroup) {
+ $firstgroup = false;
+ $s .="\n<optgroup label='". htmlspecialchars($group) ."'>";
+ } else {
+ $s .="\n</optgroup>";
+ $s .="\n<optgroup label='". htmlspecialchars($group) ."'>";
+ }
+ }
+
+ if ($hasvalue)
+ $value = " value='".htmlspecialchars($zval2)."'";
+
+ if (is_array($defstr)) {
+
+ if (in_array($selected,$defstr))
+ $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>';
+ else
+ $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>';
+ }
+ else {
+ if (strcasecmp($selected,$defstr)==0)
+ $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>';
+ else
+ $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>';
+ }
+ $zthis->MoveNext();
+ } // while
+
+ // closing last optgroup
+ if($optgroup != null) {
+ $s .= "\n</optgroup>";
+ }
+ return $s ."\n</select>\n";
+}
+
+/*
+ Count the number of records this sql statement will return by using
+ query rewriting heuristics...
+
+ Does not work with UNIONs, except with postgresql and oracle.
+
+ Usage:
+
+ $conn->Connect(...);
+ $cnt = _adodb_getcount($conn, $sql);
+
+*/
+function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0)
+{
+ $qryRecs = 0;
+
+ if (!empty($zthis->_nestedSQL) || preg_match("/^\s*SELECT\s+DISTINCT/is", $sql) ||
+ preg_match('/\s+GROUP\s+BY\s+/is',$sql) ||
+ preg_match('/\s+UNION\s+/is',$sql)) {
+
+ $rewritesql = adodb_strip_order_by($sql);
+
+ // ok, has SELECT DISTINCT or GROUP BY so see if we can use a table alias
+ // but this is only supported by oracle and postgresql...
+ if ($zthis->dataProvider == 'oci8') {
+ // Allow Oracle hints to be used for query optimization, Chris Wrye
+ if (preg_match('#/\\*+.*?\\*\\/#', $sql, $hint)) {
+ $rewritesql = "SELECT ".$hint[0]." COUNT(*) FROM (".$rewritesql.")";
+ } else
+ $rewritesql = "SELECT COUNT(*) FROM (".$rewritesql.")";
+
+ } else if (strncmp($zthis->databaseType,'postgres',8) == 0
+ || strncmp($zthis->databaseType,'mysql',5) == 0
+ || strncmp($zthis->databaseType,'mssql',5) == 0
+ || strncmp($zthis->dsnType,'sqlsrv',5) == 0
+ || strncmp($zthis->dsnType,'mssql',5) == 0
+ ){
+ $rewritesql = "SELECT COUNT(*) FROM ($rewritesql) _ADODB_ALIAS_";
+ } else {
+ $rewritesql = "SELECT COUNT(*) FROM ($rewritesql)";
+ }
+ } else {
+ // now replace SELECT ... FROM with SELECT COUNT(*) FROM
+ if ( strpos($sql, '_ADODB_COUNT') !== FALSE ) {
+ $rewritesql = preg_replace('/^\s*?SELECT\s+_ADODB_COUNT(.*)_ADODB_COUNT\s/is','SELECT COUNT(*) ',$sql);
+ } else {
+ $rewritesql = preg_replace('/^\s*SELECT\s.*\s+FROM\s/Uis','SELECT COUNT(*) FROM ',$sql);
+ }
+ // fix by alexander zhukov, alex#unipack.ru, because count(*) and 'order by' fails
+ // with mssql, access and postgresql. Also a good speedup optimization - skips sorting!
+ // also see http://phplens.com/lens/lensforum/msgs.php?id=12752
+ $rewritesql = adodb_strip_order_by($rewritesql);
+ }
+
+ if (isset($rewritesql) && $rewritesql != $sql) {
+ if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) $rewritesql .= $limitarr[0];
+
+ if ($secs2cache) {
+ // we only use half the time of secs2cache because the count can quickly
+ // become inaccurate if new records are added
+ $qryRecs = $zthis->CacheGetOne($secs2cache/2,$rewritesql,$inputarr);
+
+ } else {
+ $qryRecs = $zthis->GetOne($rewritesql,$inputarr);
+ }
+ if ($qryRecs !== false) return $qryRecs;
+ }
+ //--------------------------------------------
+ // query rewrite failed - so try slower way...
+
+
+ // strip off unneeded ORDER BY if no UNION
+ if (preg_match('/\s*UNION\s*/is', $sql)) $rewritesql = $sql;
+ else $rewritesql = $rewritesql = adodb_strip_order_by($sql);
+
+ if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) $rewritesql .= $limitarr[0];
+
+ if ($secs2cache) {
+ $rstest = $zthis->CacheExecute($secs2cache,$rewritesql,$inputarr);
+ if (!$rstest) $rstest = $zthis->CacheExecute($secs2cache,$sql,$inputarr);
+ } else {
+ $rstest = $zthis->Execute($rewritesql,$inputarr);
+ if (!$rstest) $rstest = $zthis->Execute($sql,$inputarr);
+ }
+ if ($rstest) {
+ $qryRecs = $rstest->RecordCount();
+ if ($qryRecs == -1) {
+ global $ADODB_EXTENSION;
+ // some databases will return -1 on MoveLast() - change to MoveNext()
+ if ($ADODB_EXTENSION) {
+ while(!$rstest->EOF) {
+ adodb_movenext($rstest);
+ }
+ } else {
+ while(!$rstest->EOF) {
+ $rstest->MoveNext();
+ }
+ }
+ $qryRecs = $rstest->_currentRow;
+ }
+ $rstest->Close();
+ if ($qryRecs == -1) return 0;
+ }
+ return $qryRecs;
+}
+
+/*
+ Code originally from "Cornel G" <conyg@fx.ro>
+
+ This code might not work with SQL that has UNION in it
+
+ Also if you are using CachePageExecute(), there is a strong possibility that
+ data will get out of synch. use CachePageExecute() only with tables that
+ rarely change.
+*/
+function _adodb_pageexecute_all_rows(&$zthis, $sql, $nrows, $page,
+ $inputarr=false, $secs2cache=0)
+{
+ $atfirstpage = false;
+ $atlastpage = false;
+ $lastpageno=1;
+
+ // If an invalid nrows is supplied,
+ // we assume a default value of 10 rows per page
+ if (!isset($nrows) || $nrows <= 0) $nrows = 10;
+
+ $qryRecs = false; //count records for no offset
+
+ $qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache);
+ $lastpageno = (int) ceil($qryRecs / $nrows);
+ $zthis->_maxRecordCount = $qryRecs;
+
+
+
+ // ***** Here we check whether $page is the last page or
+ // whether we are trying to retrieve
+ // a page number greater than the last page number.
+ if ($page >= $lastpageno) {
+ $page = $lastpageno;
+ $atlastpage = true;
+ }
+
+ // If page number <= 1, then we are at the first page
+ if (empty($page) || $page <= 1) {
+ $page = 1;
+ $atfirstpage = true;
+ }
+
+ // We get the data we want
+ $offset = $nrows * ($page-1);
+ if ($secs2cache > 0)
+ $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
+ else
+ $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+
+
+ // Before returning the RecordSet, we set the pagination properties we need
+ if ($rsreturn) {
+ $rsreturn->_maxRecordCount = $qryRecs;
+ $rsreturn->rowsPerPage = $nrows;
+ $rsreturn->AbsolutePage($page);
+ $rsreturn->AtFirstPage($atfirstpage);
+ $rsreturn->AtLastPage($atlastpage);
+ $rsreturn->LastPageNo($lastpageno);
+ }
+ return $rsreturn;
+}
+
+// Iván Oliva version
+function _adodb_pageexecute_no_last_page(&$zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
+{
+
+ $atfirstpage = false;
+ $atlastpage = false;
+
+ if (!isset($page) || $page <= 1) {
+ // If page number <= 1, then we are at the first page
+ $page = 1;
+ $atfirstpage = true;
+ }
+ if ($nrows <= 0) {
+ // If an invalid nrows is supplied, we assume a default value of 10 rows per page
+ $nrows = 10;
+ }
+
+ $pagecounteroffset = ($page * $nrows) - $nrows;
+
+ // To find out if there are more pages of rows, simply increase the limit or
+ // nrows by 1 and see if that number of records was returned. If it was,
+ // then we know there is at least one more page left, otherwise we are on
+ // the last page. Therefore allow non-Count() paging with single queries
+ // rather than three queries as was done before.
+ $test_nrows = $nrows + 1;
+ if ($secs2cache > 0) {
+ $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
+ } else {
+ $rsreturn = $zthis->SelectLimit($sql, $test_nrows, $pagecounteroffset, $inputarr, $secs2cache);
+ }
+
+ // Now check to see if the number of rows returned was the higher value we asked for or not.
+ if ( $rsreturn->_numOfRows == $test_nrows ) {
+ // Still at least 1 more row, so we are not on last page yet...
+ // Remove the last row from the RS.
+ $rsreturn->_numOfRows = ( $rsreturn->_numOfRows - 1 );
+ } elseif ( $rsreturn->_numOfRows == 0 && $page > 1 ) {
+ // Likely requested a page that doesn't exist, so need to find the last
+ // page and return it. Revert to original method and loop through pages
+ // until we find some data...
+ $pagecounter = $page + 1;
+ $pagecounteroffset = ($pagecounter * $nrows) - $nrows;
+
+ $rstest = $rsreturn;
+ if ($rstest) {
+ while ($rstest && $rstest->EOF && $pagecounter > 0) {
+ $atlastpage = true;
+ $pagecounter--;
+ $pagecounteroffset = $nrows * ($pagecounter - 1);
+ $rstest->Close();
+ if ($secs2cache>0) {
+ $rstest = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
+ }
+ else {
+ $rstest = $zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache);
+ }
+ }
+ if ($rstest) $rstest->Close();
+ }
+ if ($atlastpage) {
+ // If we are at the last page or beyond it, we are going to retrieve it
+ $page = $pagecounter;
+ if ($page == 1) {
+ // We have to do this again in case the last page is the same as
+ // the first page, that is, the recordset has only 1 page.
+ $atfirstpage = true;
+ }
+ }
+ // We get the data we want
+ $offset = $nrows * ($page-1);
+ if ($secs2cache > 0) {
+ $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
+ }
+ else {
+ $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+ }
+ } elseif ( $rsreturn->_numOfRows < $test_nrows ) {
+ // Rows is less than what we asked for, so must be at the last page.
+ $atlastpage = true;
+ }
+
+ // Before returning the RecordSet, we set the pagination properties we need
+ if ($rsreturn) {
+ $rsreturn->rowsPerPage = $nrows;
+ $rsreturn->AbsolutePage($page);
+ $rsreturn->AtFirstPage($atfirstpage);
+ $rsreturn->AtLastPage($atlastpage);
+ }
+ return $rsreturn;
+}
+
+function _adodb_getupdatesql(&$zthis,&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=2)
+{
+ global $ADODB_QUOTE_FIELDNAMES;
+
+ if (!$rs) {
+ printf(ADODB_BAD_RS,'GetUpdateSQL');
+ return false;
+ }
+
+ $fieldUpdatedCount = 0;
+ $arrFields = _array_change_key_case($arrFields);
+
+ $hasnumeric = isset($rs->fields[0]);
+ $setFields = '';
+
+ // Loop through all of the fields in the recordset
+ for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
+ // Get the field from the recordset
+ $field = $rs->FetchField($i);
+
+ // If the recordset field is one
+ // of the fields passed in then process.
+ $upperfname = strtoupper($field->name);
+ if (adodb_key_exists($upperfname,$arrFields,$force)) {
+
+ // If the existing field value in the recordset
+ // is different from the value passed in then
+ // go ahead and append the field name and new value to
+ // the update query.
+
+ if ($hasnumeric) $val = $rs->fields[$i];
+ else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname];
+ else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name];
+ else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)];
+ else $val = '';
+
+
+ if ($forceUpdate || strcmp($val, $arrFields[$upperfname])) {
+ // Set the counter for the number of fields that will be updated.
+ $fieldUpdatedCount++;
+
+ // Based on the datatype of the field
+ // Format the value properly for the database
+ $type = $rs->MetaType($field->type);
+
+
+ if ($type == 'null') {
+ $type = 'C';
+ }
+
+ if ((strpos($upperfname,' ') !== false) || ($ADODB_QUOTE_FIELDNAMES)) {
+ switch ($ADODB_QUOTE_FIELDNAMES) {
+ case 'LOWER':
+ $fnameq = $zthis->nameQuote.strtolower($field->name).$zthis->nameQuote;break;
+ case 'NATIVE':
+ $fnameq = $zthis->nameQuote.$field->name.$zthis->nameQuote;break;
+ case 'UPPER':
+ default:
+ $fnameq = $zthis->nameQuote.$upperfname.$zthis->nameQuote;break;
+ }
+ } else
+ $fnameq = $upperfname;
+
+ //********************************************************//
+ if (is_null($arrFields[$upperfname])
+ || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
+ || $arrFields[$upperfname] === $zthis->null2null
+ )
+ {
+ switch ($force) {
+
+ //case 0:
+ // //Ignore empty values. This is allready handled in "adodb_key_exists" function.
+ //break;
+
+ case 1:
+ //Set null
+ $setFields .= $field->name . " = null, ";
+ break;
+
+ case 2:
+ //Set empty
+ $arrFields[$upperfname] = "";
+ $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq,$arrFields, $magicq);
+ break;
+ default:
+ case 3:
+ //Set the value that was given in array, so you can give both null and empty values
+ if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
+ $setFields .= $field->name . " = null, ";
+ } else {
+ $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq,$arrFields, $magicq);
+ }
+ break;
+ }
+ //********************************************************//
+ } else {
+ //we do this so each driver can customize the sql for
+ //DB specific column types.
+ //Oracle needs BLOB types to be handled with a returning clause
+ //postgres has special needs as well
+ $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq,
+ $arrFields, $magicq);
+ }
+ }
+ }
+ }
+
+ // If there were any modified fields then build the rest of the update query.
+ if ($fieldUpdatedCount > 0 || $forceUpdate) {
+ // Get the table name from the existing query.
+ if (!empty($rs->tableName)) $tableName = $rs->tableName;
+ else {
+ preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName);
+ $tableName = $tableName[1];
+ }
+ // Get the full where clause excluding the word "WHERE" from
+ // the existing query.
+ preg_match('/\sWHERE\s(.*)/is', $rs->sql, $whereClause);
+
+ $discard = false;
+ // not a good hack, improvements?
+ if ($whereClause) {
+ #var_dump($whereClause);
+ if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard));
+ else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard));
+ else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard));
+ else preg_match('/\s.*(\) WHERE .*)/is', $whereClause[1], $discard); # see https://sourceforge.net/p/adodb/bugs/37/
+ } else
+ $whereClause = array(false,false);
+
+ if ($discard)
+ $whereClause[1] = substr($whereClause[1], 0, strlen($whereClause[1]) - strlen($discard[1]));
+
+ $sql = 'UPDATE '.$tableName.' SET '.substr($setFields, 0, -2);
+ if (strlen($whereClause[1]) > 0)
+ $sql .= ' WHERE '.$whereClause[1];
+
+ return $sql;
+
+ } else {
+ return false;
+ }
+}
+
+function adodb_key_exists($key, &$arr,$force=2)
+{
+ if ($force<=0) {
+ // the following is the old behaviour where null or empty fields are ignored
+ return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0);
+ }
+
+ if (isset($arr[$key])) return true;
+ ## null check below
+ if (ADODB_PHPVER >= 0x4010) return array_key_exists($key,$arr);
+ return false;
+}
+
+/**
+ * There is a special case of this function for the oci8 driver.
+ * The proper way to handle an insert w/ a blob in oracle requires
+ * a returning clause with bind variables and a descriptor blob.
+ *
+ *
+ */
+function _adodb_getinsertsql(&$zthis,&$rs,$arrFields,$magicq=false,$force=2)
+{
+static $cacheRS = false;
+static $cacheSig = 0;
+static $cacheCols;
+ global $ADODB_QUOTE_FIELDNAMES;
+
+ $tableName = '';
+ $values = '';
+ $fields = '';
+ $recordSet = null;
+ $arrFields = _array_change_key_case($arrFields);
+ $fieldInsertedCount = 0;
+
+ if (is_string($rs)) {
+ //ok we have a table name
+ //try and get the column info ourself.
+ $tableName = $rs;
+
+ //we need an object for the recordSet
+ //because we have to call MetaType.
+ //php can't do a $rsclass::MetaType()
+ $rsclass = $zthis->rsPrefix.$zthis->databaseType;
+ $recordSet = new $rsclass(-1,$zthis->fetchMode);
+ $recordSet->connection = $zthis;
+
+ if (is_string($cacheRS) && $cacheRS == $rs) {
+ $columns = $cacheCols;
+ } else {
+ $columns = $zthis->MetaColumns( $tableName );
+ $cacheRS = $tableName;
+ $cacheCols = $columns;
+ }
+ } else if (is_subclass_of($rs, 'adorecordset')) {
+ if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {
+ $columns = $cacheCols;
+ } else {
+ for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
+ $columns[] = $rs->FetchField($i);
+ $cacheRS = $cacheSig;
+ $cacheCols = $columns;
+ $rs->insertSig = $cacheSig++;
+ }
+ $recordSet = $rs;
+
+ } else {
+ printf(ADODB_BAD_RS,'GetInsertSQL');
+ return false;
+ }
+
+ // Loop through all of the fields in the recordset
+ foreach( $columns as $field ) {
+ $upperfname = strtoupper($field->name);
+ if (adodb_key_exists($upperfname,$arrFields,$force)) {
+ $bad = false;
+ if ((strpos($upperfname,' ') !== false) || ($ADODB_QUOTE_FIELDNAMES)) {
+ switch ($ADODB_QUOTE_FIELDNAMES) {
+ case 'LOWER':
+ $fnameq = $zthis->nameQuote.strtolower($field->name).$zthis->nameQuote;break;
+ case 'NATIVE':
+ $fnameq = $zthis->nameQuote.$field->name.$zthis->nameQuote;break;
+ case 'UPPER':
+ default:
+ $fnameq = $zthis->nameQuote.$upperfname.$zthis->nameQuote;break;
+ }
+ } else
+ $fnameq = $upperfname;
+
+ $type = $recordSet->MetaType($field->type);
+
+ /********************************************************/
+ if (is_null($arrFields[$upperfname])
+ || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
+ || $arrFields[$upperfname] === $zthis->null2null
+ )
+ {
+ switch ($force) {
+
+ case 0: // we must always set null if missing
+ $bad = true;
+ break;
+
+ case 1:
+ $values .= "null, ";
+ break;
+
+ case 2:
+ //Set empty
+ $arrFields[$upperfname] = "";
+ $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq,$arrFields, $magicq);
+ break;
+
+ default:
+ case 3:
+ //Set the value that was given in array, so you can give both null and empty values
+ if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
+ $values .= "null, ";
+ } else {
+ $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields, $magicq);
+ }
+ break;
+ } // switch
+
+ /*********************************************************/
+ } else {
+ //we do this so each driver can customize the sql for
+ //DB specific column types.
+ //Oracle needs BLOB types to be handled with a returning clause
+ //postgres has special needs as well
+ $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq,
+ $arrFields, $magicq);
+ }
+
+ if ($bad) continue;
+ // Set the counter for the number of fields that will be inserted.
+ $fieldInsertedCount++;
+
+
+ // Get the name of the fields to insert
+ $fields .= $fnameq . ", ";
+ }
+ }
+
+
+ // If there were any inserted fields then build the rest of the insert query.
+ if ($fieldInsertedCount <= 0) return false;
+
+ // Get the table name from the existing query.
+ if (!$tableName) {
+ if (!empty($rs->tableName)) $tableName = $rs->tableName;
+ else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName))
+ $tableName = $tableName[1];
+ else
+ return false;
+ }
+
+ // Strip off the comma and space on the end of both the fields
+ // and their values.
+ $fields = substr($fields, 0, -2);
+ $values = substr($values, 0, -2);
+
+ // Append the fields and their values to the insert query.
+ return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )';
+}
+
+
+/**
+ * This private method is used to help construct
+ * the update/sql which is generated by GetInsertSQL and GetUpdateSQL.
+ * It handles the string construction of 1 column -> sql string based on
+ * the column type. We want to do 'safe' handling of BLOBs
+ *
+ * @param string the type of sql we are trying to create
+ * 'I' or 'U'.
+ * @param string column data type from the db::MetaType() method
+ * @param string the column name
+ * @param array the column value
+ *
+ * @return string
+ *
+ */
+function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields, $magicq)
+{
+ $sql = '';
+
+ // Based on the datatype of the field
+ // Format the value properly for the database
+ switch($type) {
+ case 'B':
+ //in order to handle Blobs correctly, we need
+ //to do some magic for Oracle
+
+ //we need to create a new descriptor to handle
+ //this properly
+ if (!empty($zthis->hasReturningInto)) {
+ if ($action == 'I') {
+ $sql = 'empty_blob(), ';
+ } else {
+ $sql = $fnameq. '=empty_blob(), ';
+ }
+ //add the variable to the returning clause array
+ //so the user can build this later in
+ //case they want to add more to it
+ $zthis->_returningArray[$fname] = ':xx'.$fname.'xx';
+ } else if (empty($arrFields[$fname])){
+ if ($action == 'I') {
+ $sql = 'empty_blob(), ';
+ } else {
+ $sql = $fnameq. '=empty_blob(), ';
+ }
+ } else {
+ //this is to maintain compatibility
+ //with older adodb versions.
+ $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false);
+ }
+ break;
+
+ case "X":
+ //we need to do some more magic here for long variables
+ //to handle these correctly in oracle.
+
+ //create a safe bind var name
+ //to avoid conflicts w/ dupes.
+ if (!empty($zthis->hasReturningInto)) {
+ if ($action == 'I') {
+ $sql = ':xx'.$fname.'xx, ';
+ } else {
+ $sql = $fnameq.'=:xx'.$fname.'xx, ';
+ }
+ //add the variable to the returning clause array
+ //so the user can build this later in
+ //case they want to add more to it
+ $zthis->_returningArray[$fname] = ':xx'.$fname.'xx';
+ } else {
+ //this is to maintain compatibility
+ //with older adodb versions.
+ $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false);
+ }
+ break;
+
+ default:
+ $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false);
+ break;
+ }
+
+ return $sql;
+}
+
+function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq, $recurse=true)
+{
+
+ if ($recurse) {
+ switch($zthis->dataProvider) {
+ case 'postgres':
+ if ($type == 'L') $type = 'C';
+ break;
+ case 'oci8':
+ return _adodb_column_sql_oci8($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq);
+
+ }
+ }
+
+ switch($type) {
+ case "C":
+ case "X":
+ case 'B':
+ $val = $zthis->qstr($arrFields[$fname],$magicq);
+ break;
+
+ case "D":
+ $val = $zthis->DBDate($arrFields[$fname]);
+ break;
+
+ case "T":
+ $val = $zthis->DBTimeStamp($arrFields[$fname]);
+ break;
+
+ case "N":
+ $val = $arrFields[$fname];
+ if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val);
+ break;
+
+ case "I":
+ case "R":
+ $val = $arrFields[$fname];
+ if (!is_numeric($val)) $val = (integer) $val;
+ break;
+
+ default:
+ $val = str_replace(array("'"," ","("),"",$arrFields[$fname]); // basic sql injection defence
+ if (empty($val)) $val = '0';
+ break;
+ }
+
+ if ($action == 'I') return $val . ", ";
+
+
+ return $fnameq . "=" . $val . ", ";
+
+}
+
+
+
+function _adodb_debug_execute(&$zthis, $sql, $inputarr)
+{
+ $ss = '';
+ if ($inputarr) {
+ foreach($inputarr as $kk=>$vv) {
+ if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...';
+ if (is_null($vv)) $ss .= "($kk=>null) ";
+ else $ss .= "($kk=>'$vv') ";
+ }
+ $ss = "[ $ss ]";
+ }
+ $sqlTxt = is_array($sql) ? $sql[0] : $sql;
+ /*str_replace(', ','##1#__^LF',is_array($sql) ? $sql[0] : $sql);
+ $sqlTxt = str_replace(',',', ',$sqlTxt);
+ $sqlTxt = str_replace('##1#__^LF', ', ' ,$sqlTxt);
+ */
+ // check if running from browser or command-line
+ $inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
+
+ $dbt = $zthis->databaseType;
+ if (isset($zthis->dsnType)) $dbt .= '-'.$zthis->dsnType;
+ if ($inBrowser) {
+ if ($ss) {
+ $ss = '<code>'.htmlspecialchars($ss).'</code>';
+ }
+ if ($zthis->debug === -1)
+ ADOConnection::outp( "<br>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<br>\n",false);
+ else if ($zthis->debug !== -99)
+ ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
+ } else {
+ $ss = "\n ".$ss;
+ if ($zthis->debug !== -99)
+ ADOConnection::outp("-----<hr>\n($dbt): ".$sqlTxt." $ss\n-----<hr>\n",false);
+ }
+
+ $qID = $zthis->_query($sql,$inputarr);
+
+ /*
+ Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
+ because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
+ */
+ if ($zthis->databaseType == 'mssql') {
+ // ErrorNo is a slow function call in mssql, and not reliable in PHP 4.0.6
+
+ if($emsg = $zthis->ErrorMsg()) {
+ if ($err = $zthis->ErrorNo()) {
+ if ($zthis->debug === -99)
+ ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
+
+ ADOConnection::outp($err.': '.$emsg);
+ }
+ }
+ } else if (!$qID) {
+
+ if ($zthis->debug === -99)
+ if ($inBrowser) ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
+ else ADOConnection::outp("-----<hr>\n($dbt): ".$sqlTxt."$ss\n-----<hr>\n",false);
+
+ ADOConnection::outp($zthis->ErrorNo() .': '. $zthis->ErrorMsg());
+ }
+
+ if ($zthis->debug === 99) _adodb_backtrace(true,9999,2);
+ return $qID;
+}
+
+# pretty print the debug_backtrace function
+function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0,$ishtml=null)
+{
+ if (!function_exists('debug_backtrace')) return '';
+
+ if ($ishtml === null) $html = (isset($_SERVER['HTTP_USER_AGENT']));
+ else $html = $ishtml;
+
+ $fmt = ($html) ? "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>" : "%% line %4d, file: %s";
+
+ $MAXSTRLEN = 128;
+
+ $s = ($html) ? '<pre align=left>' : '';
+
+ if (is_array($printOrArr)) $traceArr = $printOrArr;
+ else $traceArr = debug_backtrace();
+ array_shift($traceArr);
+ array_shift($traceArr);
+ $tabs = sizeof($traceArr)-2;
+
+ foreach ($traceArr as $arr) {
+ if ($skippy) {$skippy -= 1; continue;}
+ $levels -= 1;
+ if ($levels < 0) break;
+
+ $args = array();
+ for ($i=0; $i < $tabs; $i++) $s .= ($html) ? ' &nbsp; ' : "\t";
+ $tabs -= 1;
+ if ($html) $s .= '<font face="Courier New,Courier">';
+ if (isset($arr['class'])) $s .= $arr['class'].'.';
+ if (isset($arr['args']))
+ foreach($arr['args'] as $v) {
+ if (is_null($v)) $args[] = 'null';
+ else if (is_array($v)) $args[] = 'Array['.sizeof($v).']';
+ else if (is_object($v)) $args[] = 'Object:'.get_class($v);
+ else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
+ else {
+ $v = (string) @$v;
+ $str = htmlspecialchars(str_replace(array("\r","\n"),' ',substr($v,0,$MAXSTRLEN)));
+ if (strlen($v) > $MAXSTRLEN) $str .= '...';
+ $args[] = $str;
+ }
+ }
+ $s .= $arr['function'].'('.implode(', ',$args).')';
+
+
+ $s .= @sprintf($fmt, $arr['line'],$arr['file'],basename($arr['file']));
+
+ $s .= "\n";
+ }
+ if ($html) $s .= '</pre>';
+ if ($printOrArr) print $s;
+
+ return $s;
+}
+/*
+function _adodb_find_from($sql)
+{
+
+ $sql = str_replace(array("\n","\r"), ' ', $sql);
+ $charCount = strlen($sql);
+
+ $inString = false;
+ $quote = '';
+ $parentheseCount = 0;
+ $prevChars = '';
+ $nextChars = '';
+
+
+ for($i = 0; $i < $charCount; $i++) {
+
+ $char = substr($sql,$i,1);
+ $prevChars = substr($sql,0,$i);
+ $nextChars = substr($sql,$i+1);
+
+ if((($char == "'" || $char == '"' || $char == '`') && substr($prevChars,-1,1) != '\\') && $inString === false) {
+ $quote = $char;
+ $inString = true;
+ }
+
+ elseif((($char == "'" || $char == '"' || $char == '`') && substr($prevChars,-1,1) != '\\') && $inString === true && $quote == $char) {
+ $quote = "";
+ $inString = false;
+ }
+
+ elseif($char == "(" && $inString === false)
+ $parentheseCount++;
+
+ elseif($char == ")" && $inString === false && $parentheseCount > 0)
+ $parentheseCount--;
+
+ elseif($parentheseCount <= 0 && $inString === false && $char == " " && strtoupper(substr($prevChars,-5,5)) == " FROM")
+ return $i;
+
+ }
+}
+*/
diff --git a/vendor/adodb/adodb-php/adodb-memcache.lib.inc.php b/vendor/adodb/adodb-php/adodb-memcache.lib.inc.php
new file mode 100644
index 0000000..29696c4
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-memcache.lib.inc.php
@@ -0,0 +1,190 @@
+<?php
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+global $ADODB_INCLUDED_MEMCACHE;
+$ADODB_INCLUDED_MEMCACHE = 1;
+
+global $ADODB_INCLUDED_CSV;
+if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
+
+/*
+
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+Usage:
+
+$db = NewADOConnection($driver);
+$db->memCache = true; /// should we use memCache instead of caching in files
+$db->memCacheHost = array($ip1, $ip2, $ip3);
+$db->memCachePort = 11211; /// this is default memCache port
+$db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+
+$db->Connect(...);
+$db->CacheExecute($sql);
+
+ Note the memcache class is shared by all connections, is created during the first call to Connect/PConnect.
+
+ Class instance is stored in $ADODB_CACHE
+*/
+
+ class ADODB_Cache_MemCache {
+ var $createdir = false; // create caching directory structure?
+
+ //-----------------------------
+ // memcache specific variables
+
+ var $hosts; // array of hosts
+ var $port = 11211;
+ var $compress = false; // memcache compression with zlib
+
+ var $_connected = false;
+ var $_memcache = false;
+
+ function __construct(&$obj)
+ {
+ $this->hosts = $obj->memCacheHost;
+ $this->port = $obj->memCachePort;
+ $this->compress = $obj->memCacheCompress;
+ }
+
+ // implement as lazy connection. The connection only occurs on CacheExecute call
+ function connect(&$err)
+ {
+ if (!function_exists('memcache_pconnect')) {
+ $err = 'Memcache module PECL extension not found!';
+ return false;
+ }
+
+ $memcache = new MemCache;
+
+ if (!is_array($this->hosts)) $this->hosts = array($this->hosts);
+
+ $failcnt = 0;
+ foreach($this->hosts as $host) {
+ if (!@$memcache->addServer($host,$this->port,true)) {
+ $failcnt += 1;
+ }
+ }
+ if ($failcnt == sizeof($this->hosts)) {
+ $err = 'Can\'t connect to any memcache server';
+ return false;
+ }
+ $this->_connected = true;
+ $this->_memcache = $memcache;
+ return true;
+ }
+
+ // returns true or false. true if successful save
+ function writecache($filename, $contents, $debug, $secs2cache)
+ {
+ if (!$this->_connected) {
+ $err = '';
+ if (!$this->connect($err) && $debug) ADOConnection::outp($err);
+ }
+ if (!$this->_memcache) return false;
+
+ if (!$this->_memcache->set($filename, $contents, $this->compress ? MEMCACHE_COMPRESSED : 0, $secs2cache)) {
+ if ($debug) ADOConnection::outp(" Failed to save data at the memcached server!<br>\n");
+ return false;
+ }
+
+ return true;
+ }
+
+ // returns a recordset
+ function readcache($filename, &$err, $secs2cache, $rsClass)
+ {
+ $false = false;
+ if (!$this->_connected) $this->connect($err);
+ if (!$this->_memcache) return $false;
+
+ $rs = $this->_memcache->get($filename);
+ if (!$rs) {
+ $err = 'Item with such key doesn\'t exists on the memcached server.';
+ return $false;
+ }
+
+ // hack, should actually use _csv2rs
+ $rs = explode("\n", $rs);
+ unset($rs[0]);
+ $rs = join("\n", $rs);
+ $rs = unserialize($rs);
+ if (! is_object($rs)) {
+ $err = 'Unable to unserialize $rs';
+ return $false;
+ }
+ if ($rs->timeCreated == 0) return $rs; // apparently have been reports that timeCreated was set to 0 somewhere
+
+ $tdiff = intval($rs->timeCreated+$secs2cache - time());
+ if ($tdiff <= 2) {
+ switch($tdiff) {
+ case 2:
+ if ((rand() & 15) == 0) {
+ $err = "Timeout 2";
+ return $false;
+ }
+ break;
+ case 1:
+ if ((rand() & 3) == 0) {
+ $err = "Timeout 1";
+ return $false;
+ }
+ break;
+ default:
+ $err = "Timeout 0";
+ return $false;
+ }
+ }
+ return $rs;
+ }
+
+ function flushall($debug=false)
+ {
+ if (!$this->_connected) {
+ $err = '';
+ if (!$this->connect($err) && $debug) ADOConnection::outp($err);
+ }
+ if (!$this->_memcache) return false;
+
+ $del = $this->_memcache->flush();
+
+ if ($debug)
+ if (!$del) ADOConnection::outp("flushall: failed!<br>\n");
+ else ADOConnection::outp("flushall: succeeded!<br>\n");
+
+ return $del;
+ }
+
+ function flushcache($filename, $debug=false)
+ {
+ if (!$this->_connected) {
+ $err = '';
+ if (!$this->connect($err) && $debug) ADOConnection::outp($err);
+ }
+ if (!$this->_memcache) return false;
+
+ $del = $this->_memcache->delete($filename);
+
+ if ($debug)
+ if (!$del) ADOConnection::outp("flushcache: $key entry doesn't exist on memcached server!<br>\n");
+ else ADOConnection::outp("flushcache: $key entry flushed from memcached server!<br>\n");
+
+ return $del;
+ }
+
+ // not used for memcache
+ function createdir($dir, $hash)
+ {
+ return true;
+ }
+ }
diff --git a/vendor/adodb/adodb-php/adodb-pager.inc.php b/vendor/adodb/adodb-php/adodb-pager.inc.php
new file mode 100644
index 0000000..ae07b7a
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-pager.inc.php
@@ -0,0 +1,289 @@
+<?php
+
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ This class provides recordset pagination with
+ First/Prev/Next/Last links.
+
+ Feel free to modify this class for your own use as
+ it is very basic. To learn how to use it, see the
+ example in adodb/tests/testpaging.php.
+
+ "Pablo Costa" <pablo@cbsp.com.br> implemented Render_PageLinks().
+
+ Please note, this class is entirely unsupported,
+ and no free support requests except for bug reports
+ will be entertained by the author.
+
+*/
+class ADODB_Pager {
+ var $id; // unique id for pager (defaults to 'adodb')
+ var $db; // ADODB connection object
+ var $sql; // sql used
+ var $rs; // recordset generated
+ var $curr_page; // current page number before Render() called, calculated in constructor
+ var $rows; // number of rows per page
+ var $linksPerPage=10; // number of links per page in navigation bar
+ var $showPageLinks;
+
+ var $gridAttributes = 'width=100% border=1 bgcolor=white';
+
+ // Localize text strings here
+ var $first = '<code>|&lt;</code>';
+ var $prev = '<code>&lt;&lt;</code>';
+ var $next = '<code>>></code>';
+ var $last = '<code>>|</code>';
+ var $moreLinks = '...';
+ var $startLinks = '...';
+ var $gridHeader = false;
+ var $htmlSpecialChars = true;
+ var $page = 'Page';
+ var $linkSelectedColor = 'red';
+ var $cache = 0; #secs to cache with CachePageExecute()
+
+ //----------------------------------------------
+ // constructor
+ //
+ // $db adodb connection object
+ // $sql sql statement
+ // $id optional id to identify which pager,
+ // if you have multiple on 1 page.
+ // $id should be only be [a-z0-9]*
+ //
+ function __construct(&$db,$sql,$id = 'adodb', $showPageLinks = false)
+ {
+ global $PHP_SELF;
+
+ $curr_page = $id.'_curr_page';
+ if (!empty($PHP_SELF)) $PHP_SELF = htmlspecialchars($_SERVER['PHP_SELF']); // htmlspecialchars() to prevent XSS attacks
+
+ $this->sql = $sql;
+ $this->id = $id;
+ $this->db = $db;
+ $this->showPageLinks = $showPageLinks;
+
+ $next_page = $id.'_next_page';
+
+ if (isset($_GET[$next_page])) {
+ $_SESSION[$curr_page] = (integer) $_GET[$next_page];
+ }
+ if (empty($_SESSION[$curr_page])) $_SESSION[$curr_page] = 1; ## at first page
+
+ $this->curr_page = $_SESSION[$curr_page];
+
+ }
+
+ //---------------------------
+ // Display link to first page
+ function Render_First($anchor=true)
+ {
+ global $PHP_SELF;
+ if ($anchor) {
+ ?>
+ <a href="<?php echo $PHP_SELF,'?',$this->id;?>_next_page=1"><?php echo $this->first;?></a> &nbsp;
+ <?php
+ } else {
+ print "$this->first &nbsp; ";
+ }
+ }
+
+ //--------------------------
+ // Display link to next page
+ function render_next($anchor=true)
+ {
+ global $PHP_SELF;
+
+ if ($anchor) {
+ ?>
+ <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->AbsolutePage() + 1 ?>"><?php echo $this->next;?></a> &nbsp;
+ <?php
+ } else {
+ print "$this->next &nbsp; ";
+ }
+ }
+
+ //------------------
+ // Link to last page
+ //
+ // for better performance with large recordsets, you can set
+ // $this->db->pageExecuteCountRows = false, which disables
+ // last page counting.
+ function render_last($anchor=true)
+ {
+ global $PHP_SELF;
+
+ if (!$this->db->pageExecuteCountRows) return;
+
+ if ($anchor) {
+ ?>
+ <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->LastPageNo() ?>"><?php echo $this->last;?></a> &nbsp;
+ <?php
+ } else {
+ print "$this->last &nbsp; ";
+ }
+ }
+
+ //---------------------------------------------------
+ // original code by "Pablo Costa" <pablo@cbsp.com.br>
+ function render_pagelinks()
+ {
+ global $PHP_SELF;
+ $pages = $this->rs->LastPageNo();
+ $linksperpage = $this->linksPerPage ? $this->linksPerPage : $pages;
+ for($i=1; $i <= $pages; $i+=$linksperpage)
+ {
+ if($this->rs->AbsolutePage() >= $i)
+ {
+ $start = $i;
+ }
+ }
+ $numbers = '';
+ $end = $start+$linksperpage-1;
+ $link = $this->id . "_next_page";
+ if($end > $pages) $end = $pages;
+
+
+ if ($this->startLinks && $start > 1) {
+ $pos = $start - 1;
+ $numbers .= "<a href=$PHP_SELF?$link=$pos>$this->startLinks</a> ";
+ }
+
+ for($i=$start; $i <= $end; $i++) {
+ if ($this->rs->AbsolutePage() == $i)
+ $numbers .= "<font color=$this->linkSelectedColor><b>$i</b></font> ";
+ else
+ $numbers .= "<a href=$PHP_SELF?$link=$i>$i</a> ";
+
+ }
+ if ($this->moreLinks && $end < $pages)
+ $numbers .= "<a href=$PHP_SELF?$link=$i>$this->moreLinks</a> ";
+ print $numbers . ' &nbsp; ';
+ }
+ // Link to previous page
+ function render_prev($anchor=true)
+ {
+ global $PHP_SELF;
+ if ($anchor) {
+ ?>
+ <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->AbsolutePage() - 1 ?>"><?php echo $this->prev;?></a> &nbsp;
+ <?php
+ } else {
+ print "$this->prev &nbsp; ";
+ }
+ }
+
+ //--------------------------------------------------------
+ // Simply rendering of grid. You should override this for
+ // better control over the format of the grid
+ //
+ // We use output buffering to keep code clean and readable.
+ function RenderGrid()
+ {
+ global $gSQLBlockRows; // used by rs2html to indicate how many rows to display
+ include_once(ADODB_DIR.'/tohtml.inc.php');
+ ob_start();
+ $gSQLBlockRows = $this->rows;
+ rs2html($this->rs,$this->gridAttributes,$this->gridHeader,$this->htmlSpecialChars);
+ $s = ob_get_contents();
+ ob_end_clean();
+ return $s;
+ }
+
+ //-------------------------------------------------------
+ // Navigation bar
+ //
+ // we use output buffering to keep the code easy to read.
+ function RenderNav()
+ {
+ ob_start();
+ if (!$this->rs->AtFirstPage()) {
+ $this->Render_First();
+ $this->Render_Prev();
+ } else {
+ $this->Render_First(false);
+ $this->Render_Prev(false);
+ }
+ if ($this->showPageLinks){
+ $this->Render_PageLinks();
+ }
+ if (!$this->rs->AtLastPage()) {
+ $this->Render_Next();
+ $this->Render_Last();
+ } else {
+ $this->Render_Next(false);
+ $this->Render_Last(false);
+ }
+ $s = ob_get_contents();
+ ob_end_clean();
+ return $s;
+ }
+
+ //-------------------
+ // This is the footer
+ function RenderPageCount()
+ {
+ if (!$this->db->pageExecuteCountRows) return '';
+ $lastPage = $this->rs->LastPageNo();
+ if ($lastPage == -1) $lastPage = 1; // check for empty rs.
+ if ($this->curr_page > $lastPage) $this->curr_page = 1;
+ return "<font size=-1>$this->page ".$this->curr_page."/".$lastPage."</font>";
+ }
+
+ //-----------------------------------
+ // Call this class to draw everything.
+ function Render($rows=10)
+ {
+ global $ADODB_COUNTRECS;
+
+ $this->rows = $rows;
+
+ if ($this->db->dataProvider == 'informix') $this->db->cursorType = IFX_SCROLL;
+
+ $savec = $ADODB_COUNTRECS;
+ if ($this->db->pageExecuteCountRows) $ADODB_COUNTRECS = true;
+ if ($this->cache)
+ $rs = $this->db->CachePageExecute($this->cache,$this->sql,$rows,$this->curr_page);
+ else
+ $rs = $this->db->PageExecute($this->sql,$rows,$this->curr_page);
+ $ADODB_COUNTRECS = $savec;
+
+ $this->rs = $rs;
+ if (!$rs) {
+ print "<h3>Query failed: $this->sql</h3>";
+ return;
+ }
+
+ if (!$rs->EOF && (!$rs->AtFirstPage() || !$rs->AtLastPage()))
+ $header = $this->RenderNav();
+ else
+ $header = "&nbsp;";
+
+ $grid = $this->RenderGrid();
+ $footer = $this->RenderPageCount();
+
+ $this->RenderLayout($header,$grid,$footer);
+
+ $rs->Close();
+ $this->rs = false;
+ }
+
+ //------------------------------------------------------
+ // override this to control overall layout and formating
+ function RenderLayout($header,$grid,$footer,$attributes='border=1 bgcolor=beige')
+ {
+ echo "<table ".$attributes."><tr><td>",
+ $header,
+ "</td></tr><tr><td>",
+ $grid,
+ "</td></tr><tr><td>",
+ $footer,
+ "</td></tr></table>";
+ }
+}
diff --git a/vendor/adodb/adodb-php/adodb-pear.inc.php b/vendor/adodb/adodb-php/adodb-pear.inc.php
new file mode 100644
index 0000000..7aa8ee9
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-pear.inc.php
@@ -0,0 +1,370 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * PEAR DB Emulation Layer for ADODB.
+ *
+ * The following code is modelled on PEAR DB code by Stig Bakken <ssb@fast.no> |
+ * and Tomas V.V.Cox <cox@idecnet.com>. Portions (c)1997-2002 The PHP Group.
+ */
+
+ /*
+ We support:
+
+ DB_Common
+ ---------
+ query - returns PEAR_Error on error
+ limitQuery - return PEAR_Error on error
+ prepare - does not return PEAR_Error on error
+ execute - does not return PEAR_Error on error
+ setFetchMode - supports ASSOC and ORDERED
+ errorNative
+ quote
+ nextID
+ disconnect
+
+ getOne
+ getAssoc
+ getRow
+ getCol
+ getAll
+
+ DB_Result
+ ---------
+ numRows - returns -1 if not supported
+ numCols
+ fetchInto - does not support passing of fetchmode
+ fetchRows - does not support passing of fetchmode
+ free
+ */
+
+define('ADODB_PEAR',dirname(__FILE__));
+include_once "PEAR.php";
+include_once ADODB_PEAR."/adodb-errorpear.inc.php";
+include_once ADODB_PEAR."/adodb.inc.php";
+
+if (!defined('DB_OK')) {
+define("DB_OK", 1);
+define("DB_ERROR",-1);
+
+/**
+ * This is a special constant that tells DB the user hasn't specified
+ * any particular get mode, so the default should be used.
+ */
+
+define('DB_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+
+define('DB_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+
+define('DB_FETCHMODE_ASSOC', 2);
+
+/* for compatibility */
+
+define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
+define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC);
+
+/**
+ * these are constants for the tableInfo-function
+ * they are bitwised or'ed. so if there are more constants to be defined
+ * in the future, adjust DB_TABLEINFO_FULL accordingly
+ */
+
+define('DB_TABLEINFO_ORDER', 1);
+define('DB_TABLEINFO_ORDERTABLE', 2);
+define('DB_TABLEINFO_FULL', 3);
+}
+
+/**
+ * The main "DB" class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ */
+
+class DB
+{
+ /**
+ * Create a new DB object for the specified database type
+ *
+ * @param $type string database type, for example "mysql"
+ *
+ * @return object a newly created DB object, or a DB error code on
+ * error
+ */
+
+ function factory($type)
+ {
+ include_once(ADODB_DIR."/drivers/adodb-$type.inc.php");
+ $obj = NewADOConnection($type);
+ if (!is_object($obj)) $obj = new PEAR_Error('Unknown Database Driver: '.$dsninfo['phptype'],-1);
+ return $obj;
+ }
+
+ /**
+ * Create a new DB object and connect to the specified database
+ *
+ * @param $dsn mixed "data source name", see the DB::parseDSN
+ * method for a description of the dsn format. Can also be
+ * specified as an array of the format returned by DB::parseDSN.
+ *
+ * @param $options mixed if boolean (or scalar), tells whether
+ * this connection should be persistent (for backends that support
+ * this). This parameter can also be an array of options, see
+ * DB_common::setOption for more information on connection
+ * options.
+ *
+ * @return object a newly created DB connection object, or a DB
+ * error object on error
+ *
+ * @see DB::parseDSN
+ * @see DB::isError
+ */
+ function connect($dsn, $options = false)
+ {
+ if (is_array($dsn)) {
+ $dsninfo = $dsn;
+ } else {
+ $dsninfo = DB::parseDSN($dsn);
+ }
+ switch ($dsninfo["phptype"]) {
+ case 'pgsql': $type = 'postgres7'; break;
+ case 'ifx': $type = 'informix9'; break;
+ default: $type = $dsninfo["phptype"]; break;
+ }
+
+ if (is_array($options) && isset($options["debug"]) &&
+ $options["debug"] >= 2) {
+ // expose php errors with sufficient debug level
+ @include_once("adodb-$type.inc.php");
+ } else {
+ @include_once("adodb-$type.inc.php");
+ }
+
+ @$obj = NewADOConnection($type);
+ if (!is_object($obj)) {
+ $obj = new PEAR_Error('Unknown Database Driver: '.$dsninfo['phptype'],-1);
+ return $obj;
+ }
+ if (is_array($options)) {
+ foreach($options as $k => $v) {
+ switch(strtolower($k)) {
+ case 'persist':
+ case 'persistent': $persist = $v; break;
+ #ibase
+ case 'dialect': $obj->dialect = $v; break;
+ case 'charset': $obj->charset = $v; break;
+ case 'buffers': $obj->buffers = $v; break;
+ #ado
+ case 'charpage': $obj->charPage = $v; break;
+ #mysql
+ case 'clientflags': $obj->clientFlags = $v; break;
+ }
+ }
+ } else {
+ $persist = false;
+ }
+
+ if (isset($dsninfo['socket'])) $dsninfo['hostspec'] .= ':'.$dsninfo['socket'];
+ else if (isset($dsninfo['port'])) $dsninfo['hostspec'] .= ':'.$dsninfo['port'];
+
+ if($persist) $ok = $obj->PConnect($dsninfo['hostspec'], $dsninfo['username'],$dsninfo['password'],$dsninfo['database']);
+ else $ok = $obj->Connect($dsninfo['hostspec'], $dsninfo['username'],$dsninfo['password'],$dsninfo['database']);
+
+ if (!$ok) $obj = ADODB_PEAR_Error();
+ return $obj;
+ }
+
+ /**
+ * Return the DB API version
+ *
+ * @return int the DB API version number
+ */
+ function apiVersion()
+ {
+ return 2;
+ }
+
+ /**
+ * Tell whether a result code from a DB method is an error
+ *
+ * @param $value int result code
+ *
+ * @return bool whether $value is an error
+ */
+ function isError($value)
+ {
+ if (!is_object($value)) return false;
+ $class = strtolower(get_class($value));
+ return $class == 'pear_error' || is_subclass_of($value, 'pear_error') ||
+ $class == 'db_error' || is_subclass_of($value, 'db_error');
+ }
+
+
+ /**
+ * Tell whether a result code from a DB method is a warning.
+ * Warnings differ from errors in that they are generated by DB,
+ * and are not fatal.
+ *
+ * @param $value mixed result value
+ *
+ * @return bool whether $value is a warning
+ */
+ function isWarning($value)
+ {
+ return false;
+ /*
+ return is_object($value) &&
+ (get_class( $value ) == "db_warning" ||
+ is_subclass_of($value, "db_warning"));*/
+ }
+
+ /**
+ * Parse a data source name
+ *
+ * @param $dsn string Data Source Name to be parsed
+ *
+ * @return array an associative array with the following keys:
+ *
+ * phptype: Database backend used in PHP (mysql, odbc etc.)
+ * dbsyntax: Database used with regards to SQL syntax etc.
+ * protocol: Communication protocol to use (tcp, unix etc.)
+ * hostspec: Host specification (hostname[:port])
+ * database: Database to use on the DBMS server
+ * username: User name for login
+ * password: Password for login
+ *
+ * The format of the supplied DSN is in its fullest form:
+ *
+ * phptype(dbsyntax)://username:password@protocol+hostspec/database
+ *
+ * Most variations are allowed:
+ *
+ * phptype://username:password@protocol+hostspec:110//usr/db_file.db
+ * phptype://username:password@hostspec/database_name
+ * phptype://username:password@hostspec
+ * phptype://username@hostspec
+ * phptype://hostspec/database
+ * phptype://hostspec
+ * phptype(dbsyntax)
+ * phptype
+ *
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ */
+ function parseDSN($dsn)
+ {
+ if (is_array($dsn)) {
+ return $dsn;
+ }
+
+ $parsed = array(
+ 'phptype' => false,
+ 'dbsyntax' => false,
+ 'protocol' => false,
+ 'hostspec' => false,
+ 'database' => false,
+ 'username' => false,
+ 'password' => false
+ );
+
+ // Find phptype and dbsyntax
+ if (($pos = strpos($dsn, '://')) !== false) {
+ $str = substr($dsn, 0, $pos);
+ $dsn = substr($dsn, $pos + 3);
+ } else {
+ $str = $dsn;
+ $dsn = NULL;
+ }
+
+ // Get phptype and dbsyntax
+ // $str => phptype(dbsyntax)
+ if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+ $parsed['phptype'] = $arr[1];
+ $parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2];
+ } else {
+ $parsed['phptype'] = $str;
+ $parsed['dbsyntax'] = $str;
+ }
+
+ if (empty($dsn)) {
+ return $parsed;
+ }
+
+ // Get (if found): username and password
+ // $dsn => username:password@protocol+hostspec/database
+ if (($at = strpos($dsn,'@')) !== false) {
+ $str = substr($dsn, 0, $at);
+ $dsn = substr($dsn, $at + 1);
+ if (($pos = strpos($str, ':')) !== false) {
+ $parsed['username'] = urldecode(substr($str, 0, $pos));
+ $parsed['password'] = urldecode(substr($str, $pos + 1));
+ } else {
+ $parsed['username'] = urldecode($str);
+ }
+ }
+
+ // Find protocol and hostspec
+ // $dsn => protocol+hostspec/database
+ if (($pos=strpos($dsn, '/')) !== false) {
+ $str = substr($dsn, 0, $pos);
+ $dsn = substr($dsn, $pos + 1);
+ } else {
+ $str = $dsn;
+ $dsn = NULL;
+ }
+
+ // Get protocol + hostspec
+ // $str => protocol+hostspec
+ if (($pos=strpos($str, '+')) !== false) {
+ $parsed['protocol'] = substr($str, 0, $pos);
+ $parsed['hostspec'] = urldecode(substr($str, $pos + 1));
+ } else {
+ $parsed['hostspec'] = urldecode($str);
+ }
+
+ // Get dabase if any
+ // $dsn => database
+ if (!empty($dsn)) {
+ $parsed['database'] = $dsn;
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * Load a PHP database extension if it is not loaded already.
+ *
+ * @access public
+ *
+ * @param $name the base name of the extension (without the .so or
+ * .dll suffix)
+ *
+ * @return bool true if the extension was already or successfully
+ * loaded, false if it could not be loaded
+ */
+ function assertExtension($name)
+ {
+ if (function_exists('dl') && !extension_loaded($name)) {
+ $dlext = (strncmp(PHP_OS,'WIN',3) === 0) ? '.dll' : '.so';
+ @dl($name . $dlext);
+ }
+ if (!extension_loaded($name)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/vendor/adodb/adodb-php/adodb-perf.inc.php b/vendor/adodb/adodb-php/adodb-perf.inc.php
new file mode 100644
index 0000000..1bee6b7
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-perf.inc.php
@@ -0,0 +1,1102 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning.
+
+ My apologies if you see code mixed with presentation. The presentation suits
+ my needs. If you want to separate code from presentation, be my guest. Patches
+ are welcome.
+
+*/
+
+if (!defined('ADODB_DIR')) include_once(dirname(__FILE__).'/adodb.inc.php');
+include_once(ADODB_DIR.'/tohtml.inc.php');
+
+define( 'ADODB_OPT_HIGH', 2);
+define( 'ADODB_OPT_LOW', 1);
+
+global $ADODB_PERF_MIN;
+$ADODB_PERF_MIN = 0.05; // log only if >= minimum number of secs to run
+
+
+// returns in K the memory of current process, or 0 if not known
+function adodb_getmem()
+{
+ if (function_exists('memory_get_usage'))
+ return (integer) ((memory_get_usage()+512)/1024);
+
+ $pid = getmypid();
+
+ if ( strncmp(strtoupper(PHP_OS),'WIN',3)==0) {
+ $output = array();
+
+ exec('tasklist /FI "PID eq ' . $pid. '" /FO LIST', $output);
+ return substr($output[5], strpos($output[5], ':') + 1);
+ }
+
+ /* Hopefully UNIX */
+ exec("ps --pid $pid --no-headers -o%mem,size", $output);
+ if (sizeof($output) == 0) return 0;
+
+ $memarr = explode(' ',$output[0]);
+ if (sizeof($memarr)>=2) return (integer) $memarr[1];
+
+ return 0;
+}
+
+// avoids localization problems where , is used instead of .
+function adodb_round($n,$prec)
+{
+ return number_format($n, $prec, '.', '');
+}
+
+/* obsolete: return microtime value as a float. Retained for backward compat */
+function adodb_microtime()
+{
+ return microtime(true);
+}
+
+/* sql code timing */
+function adodb_log_sql(&$connx,$sql,$inputarr)
+{
+ $perf_table = adodb_perf::table();
+ $connx->fnExecute = false;
+ $a0 = microtime(true);
+ $rs = $connx->Execute($sql,$inputarr);
+ $a1 = microtime(true);
+
+ if (!empty($connx->_logsql) && (empty($connx->_logsqlErrors) || !$rs)) {
+ global $ADODB_LOG_CONN;
+
+ if (!empty($ADODB_LOG_CONN)) {
+ $conn = $ADODB_LOG_CONN;
+ if ($conn->databaseType != $connx->databaseType)
+ $prefix = '/*dbx='.$connx->databaseType .'*/ ';
+ else
+ $prefix = '';
+ } else {
+ $conn = $connx;
+ $prefix = '';
+ }
+
+ $conn->_logsql = false; // disable logsql error simulation
+ $dbT = $conn->databaseType;
+
+ $time = $a1 - $a0;
+
+ if (!$rs) {
+ $errM = $connx->ErrorMsg();
+ $errN = $connx->ErrorNo();
+ $conn->lastInsID = 0;
+ $tracer = substr('ERROR: '.htmlspecialchars($errM),0,250);
+ } else {
+ $tracer = '';
+ $errM = '';
+ $errN = 0;
+ $dbg = $conn->debug;
+ $conn->debug = false;
+ if (!is_object($rs) || $rs->dataProvider == 'empty')
+ $conn->_affected = $conn->affected_rows(true);
+ $conn->lastInsID = @$conn->Insert_ID();
+ $conn->debug = $dbg;
+ }
+ if (isset($_SERVER['HTTP_HOST'])) {
+ $tracer .= '<br>'.$_SERVER['HTTP_HOST'];
+ if (isset($_SERVER['PHP_SELF'])) $tracer .= htmlspecialchars($_SERVER['PHP_SELF']);
+ } else
+ if (isset($_SERVER['PHP_SELF'])) $tracer .= '<br>'.htmlspecialchars($_SERVER['PHP_SELF']);
+ //$tracer .= (string) adodb_backtrace(false);
+
+ $tracer = (string) substr($tracer,0,500);
+
+ if (is_array($inputarr)) {
+ if (is_array(reset($inputarr))) $params = 'Array sizeof='.sizeof($inputarr);
+ else {
+ // Quote string parameters so we can see them in the
+ // performance stats. This helps spot disabled indexes.
+ $xar_params = $inputarr;
+ foreach ($xar_params as $xar_param_key => $xar_param) {
+ if (gettype($xar_param) == 'string')
+ $xar_params[$xar_param_key] = '"' . $xar_param . '"';
+ }
+ $params = implode(', ', $xar_params);
+ if (strlen($params) >= 3000) $params = substr($params, 0, 3000);
+ }
+ } else {
+ $params = '';
+ }
+
+ if (is_array($sql)) $sql = $sql[0];
+ if ($prefix) $sql = $prefix.$sql;
+ $arr = array('b'=>strlen($sql).'.'.crc32($sql),
+ 'c'=>substr($sql,0,3900), 'd'=>$params,'e'=>$tracer,'f'=>adodb_round($time,6));
+ //var_dump($arr);
+ $saved = $conn->debug;
+ $conn->debug = 0;
+
+ $d = $conn->sysTimeStamp;
+ if (empty($d)) $d = date("'Y-m-d H:i:s'");
+ if ($conn->dataProvider == 'oci8' && $dbT != 'oci8po') {
+ $isql = "insert into $perf_table values($d,:b,:c,:d,:e,:f)";
+ } else if ($dbT == 'mssqlnative' || $dbT == 'odbc_mssql' || $dbT == 'informix' || strncmp($dbT,'odbtp',4)==0) {
+ $timer = $arr['f'];
+ if ($dbT == 'informix') $sql2 = substr($sql2,0,230);
+
+ $sql1 = $conn->qstr($arr['b']);
+ $sql2 = $conn->qstr($arr['c']);
+ $params = $conn->qstr($arr['d']);
+ $tracer = $conn->qstr($arr['e']);
+
+ $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values($d,$sql1,$sql2,$params,$tracer,$timer)";
+ if ($dbT == 'informix') $isql = str_replace(chr(10),' ',$isql);
+ $arr = false;
+ } else {
+ if ($dbT == 'db2') $arr['f'] = (float) $arr['f'];
+ $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values( $d,?,?,?,?,?)";
+ }
+
+ global $ADODB_PERF_MIN;
+ if ($errN != 0 || $time >= $ADODB_PERF_MIN) {
+ if($conn instanceof ADODB_mysqli && $conn->_queryID) {
+ mysqli_free_result($conn->_queryID);
+ }
+ $ok = $conn->Execute($isql,$arr);
+ } else
+ $ok = true;
+
+ $conn->debug = $saved;
+
+ if ($ok) {
+ $conn->_logsql = true;
+ } else {
+ $err2 = $conn->ErrorMsg();
+ $conn->_logsql = true; // enable logsql error simulation
+ $perf = NewPerfMonitor($conn);
+ if ($perf) {
+ if ($perf->CreateLogTable()) $ok = $conn->Execute($isql,$arr);
+ } else {
+ $ok = $conn->Execute("create table $perf_table (
+ created varchar(50),
+ sql0 varchar(250),
+ sql1 varchar(4000),
+ params varchar(3000),
+ tracer varchar(500),
+ timer decimal(16,6))");
+ }
+ if (!$ok) {
+ ADOConnection::outp( "<p><b>LOGSQL Insert Failed</b>: $isql<br>$err2</p>");
+ $conn->_logsql = false;
+ }
+ }
+ $connx->_errorMsg = $errM;
+ $connx->_errorCode = $errN;
+ }
+ $connx->fnExecute = 'adodb_log_sql';
+ return $rs;
+}
+
+
+/*
+The settings data structure is an associative array that database parameter per element.
+
+Each database parameter element in the array is itself an array consisting of:
+
+0: category code, used to group related db parameters
+1: either
+ a. sql string to retrieve value, eg. "select value from v\$parameter where name='db_block_size'",
+ b. array holding sql string and field to look for, e.g. array('show variables','table_cache'),
+ c. a string prefixed by =, then a PHP method of the class is invoked,
+ e.g. to invoke $this->GetIndexValue(), set this array element to '=GetIndexValue',
+2: description of the database parameter
+*/
+
+class adodb_perf {
+ var $conn;
+ var $color = '#F0F0F0';
+ var $table = '<table border=1 bgcolor=white>';
+ var $titles = '<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Description</b></td></tr>';
+ var $warnRatio = 90;
+ var $tablesSQL = false;
+ var $cliFormat = "%32s => %s \r\n";
+ var $sql1 = 'sql1'; // used for casting sql1 to text for mssql
+ var $explain = true;
+ var $helpurl = '<a href="http://adodb.org/dokuwiki/doku.php?id=v5:performance:logsql">LogSQL help</a>';
+ var $createTableSQL = false;
+ var $maxLength = 2000;
+
+ // Sets the tablename to be used
+ static function table($newtable = false)
+ {
+ static $_table;
+
+ if (!empty($newtable)) $_table = $newtable;
+ if (empty($_table)) $_table = 'adodb_logsql';
+ return $_table;
+ }
+
+ // returns array with info to calculate CPU Load
+ function _CPULoad()
+ {
+/*
+
+cpu 524152 2662 2515228 336057010
+cpu0 264339 1408 1257951 168025827
+cpu1 259813 1254 1257277 168031181
+page 622307 25475680
+swap 24 1891
+intr 890153570 868093576 6 0 4 4 0 6 1 2 0 0 0 124 0 8098760 2 13961053 0 0 0 0 0 0 0 0 0 0 0 0 0 16 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+disk_io: (3,0):(3144904,54369,610378,3090535,50936192) (3,1):(3630212,54097,633016,3576115,50951320)
+ctxt 66155838
+btime 1062315585
+processes 69293
+
+*/
+ // Algorithm is taken from
+ // http://social.technet.microsoft.com/Forums/en-US/winservergen/thread/414b0e1b-499c-411e-8a02-6a12e339c0f1/
+ if (strncmp(PHP_OS,'WIN',3)==0) {
+ if (PHP_VERSION == '5.0.0') return false;
+ if (PHP_VERSION == '5.0.1') return false;
+ if (PHP_VERSION == '5.0.2') return false;
+ if (PHP_VERSION == '5.0.3') return false;
+ if (PHP_VERSION == '4.3.10') return false; # see http://bugs.php.net/bug.php?id=31737
+
+ static $FAIL = false;
+ if ($FAIL) return false;
+
+ $objName = "winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\CIMV2";
+ $myQuery = "SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name = '_Total'";
+
+ try {
+ @$objWMIService = new COM($objName);
+ if (!$objWMIService) {
+ $FAIL = true;
+ return false;
+ }
+
+ $info[0] = -1;
+ $info[1] = 0;
+ $info[2] = 0;
+ $info[3] = 0;
+ foreach($objWMIService->ExecQuery($myQuery) as $objItem) {
+ $info[0] = $objItem->PercentProcessorTime();
+ }
+
+ } catch(Exception $e) {
+ $FAIL = true;
+ echo $e->getMessage();
+ return false;
+ }
+
+ return $info;
+ }
+
+ // Algorithm - Steve Blinch (BlitzAffe Online, http://www.blitzaffe.com)
+ $statfile = '/proc/stat';
+ if (!file_exists($statfile)) return false;
+
+ $fd = fopen($statfile,"r");
+ if (!$fd) return false;
+
+ $statinfo = explode("\n",fgets($fd, 1024));
+ fclose($fd);
+ foreach($statinfo as $line) {
+ $info = explode(" ",$line);
+ if($info[0]=="cpu") {
+ array_shift($info); // pop off "cpu"
+ if(!$info[0]) array_shift($info); // pop off blank space (if any)
+ return $info;
+ }
+ }
+
+ return false;
+
+ }
+
+ /* NOT IMPLEMENTED */
+ function MemInfo()
+ {
+ /*
+
+ total: used: free: shared: buffers: cached:
+Mem: 1055289344 917299200 137990144 0 165437440 599773184
+Swap: 2146775040 11055104 2135719936
+MemTotal: 1030556 kB
+MemFree: 134756 kB
+MemShared: 0 kB
+Buffers: 161560 kB
+Cached: 581384 kB
+SwapCached: 4332 kB
+Active: 494468 kB
+Inact_dirty: 322856 kB
+Inact_clean: 24256 kB
+Inact_target: 168316 kB
+HighTotal: 131064 kB
+HighFree: 1024 kB
+LowTotal: 899492 kB
+LowFree: 133732 kB
+SwapTotal: 2096460 kB
+SwapFree: 2085664 kB
+Committed_AS: 348732 kB
+ */
+ }
+
+
+ /*
+ Remember that this is client load, not db server load!
+ */
+ var $_lastLoad;
+ function CPULoad()
+ {
+ $info = $this->_CPULoad();
+ if (!$info) return false;
+
+ if (strncmp(PHP_OS,'WIN',3)==0) {
+ return (integer) $info[0];
+ }else {
+ if (empty($this->_lastLoad)) {
+ sleep(1);
+ $this->_lastLoad = $info;
+ $info = $this->_CPULoad();
+ }
+
+ $last = $this->_lastLoad;
+ $this->_lastLoad = $info;
+
+ $d_user = $info[0] - $last[0];
+ $d_nice = $info[1] - $last[1];
+ $d_system = $info[2] - $last[2];
+ $d_idle = $info[3] - $last[3];
+
+ //printf("Delta - User: %f Nice: %f System: %f Idle: %f<br>",$d_user,$d_nice,$d_system,$d_idle);
+
+ $total=$d_user+$d_nice+$d_system+$d_idle;
+ if ($total<1) $total=1;
+ return 100*($d_user+$d_nice+$d_system)/$total;
+ }
+ }
+
+ function Tracer($sql)
+ {
+ $perf_table = adodb_perf::table();
+ $saveE = $this->conn->fnExecute;
+ $this->conn->fnExecute = false;
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $sqlq = $this->conn->qstr($sql);
+ $arr = $this->conn->GetArray(
+"select count(*),tracer
+ from $perf_table where sql1=$sqlq
+ group by tracer
+ order by 1 desc");
+ $s = '';
+ if ($arr) {
+ $s .= '<h3>Scripts Affected</h3>';
+ foreach($arr as $k) {
+ $s .= sprintf("%4d",$k[0]).' &nbsp; '.strip_tags($k[1]).'<br>';
+ }
+ }
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_CACHE_MODE = $save;
+ $this->conn->fnExecute = $saveE;
+ return $s;
+ }
+
+ /*
+ Explain Plan for $sql.
+ If only a snippet of the $sql is passed in, then $partial will hold the crc32 of the
+ actual sql.
+ */
+ function Explain($sql,$partial=false)
+ {
+ return false;
+ }
+
+ function InvalidSQL($numsql = 10)
+ {
+
+ if (isset($_GET['sql'])) return;
+ $s = '<h3>Invalid SQL</h3>';
+ $saveE = $this->conn->fnExecute;
+ $this->conn->fnExecute = false;
+ $perf_table = adodb_perf::table();
+ $rs = $this->conn->SelectLimit("select distinct count(*),sql1,tracer as error_msg from $perf_table where tracer like 'ERROR:%' group by sql1,tracer order by 1 desc",$numsql);//,$numsql);
+ $this->conn->fnExecute = $saveE;
+ if ($rs) {
+ $s .= rs2html($rs,false,false,false,false);
+ } else
+ return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
+
+ return $s;
+ }
+
+
+ /*
+ This script identifies the longest running SQL
+ */
+ function _SuspiciousSQL($numsql = 10)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $perf_table = adodb_perf::table();
+ $saveE = $this->conn->fnExecute;
+ $this->conn->fnExecute = false;
+
+ if (isset($_GET['exps']) && isset($_GET['sql'])) {
+ $partial = !empty($_GET['part']);
+ echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
+ }
+
+ if (isset($_GET['sql'])) return;
+ $sql1 = $this->sql1;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+ //$this->conn->debug=1;
+ $rs = $this->conn->SelectLimit(
+ "select avg(timer) as avg_timer,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer
+ from $perf_table
+ where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT')
+ and (tracer is null or tracer not like 'ERROR:%')
+ group by sql1
+ order by 1 desc",$numsql);
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ $this->conn->fnExecute = $saveE;
+
+ if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
+ $s = "<h3>Suspicious SQL</h3>
+<font size=1>The following SQL have high average execution times</font><br>
+<table border=1 bgcolor=white><tr><td><b>Avg Time</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n";
+ $max = $this->maxLength;
+ while (!$rs->EOF) {
+ $sql = $rs->fields[1];
+ $raw = urlencode($sql);
+ if (strlen($raw)>$max-100) {
+ $sql2 = substr($sql,0,$max-500);
+ $raw = urlencode($sql2).'&part='.crc32($sql);
+ }
+ $prefix = "<a target=sql".rand()." href=\"?hidem=1&exps=1&sql=".$raw."&x#explain\">";
+ $suffix = "</a>";
+ if ($this->explain == false || strlen($prefix)>$max) {
+ $suffix = ' ... <i>String too long for GET parameter: '.strlen($prefix).'</i>';
+ $prefix = '';
+ }
+ $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>".
+ "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>";
+ $rs->MoveNext();
+ }
+ return $s."</table>";
+
+ }
+
+ function CheckMemory()
+ {
+ return '';
+ }
+
+
+ function SuspiciousSQL($numsql=10)
+ {
+ return adodb_perf::_SuspiciousSQL($numsql);
+ }
+
+ function ExpensiveSQL($numsql=10)
+ {
+ return adodb_perf::_ExpensiveSQL($numsql);
+ }
+
+
+ /*
+ This reports the percentage of load on the instance due to the most
+ expensive few SQL statements. Tuning these statements can often
+ make huge improvements in overall system performance.
+ */
+ function _ExpensiveSQL($numsql = 10)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $perf_table = adodb_perf::table();
+ $saveE = $this->conn->fnExecute;
+ $this->conn->fnExecute = false;
+
+ if (isset($_GET['expe']) && isset($_GET['sql'])) {
+ $partial = !empty($_GET['part']);
+ echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
+ }
+
+ if (isset($_GET['sql'])) return;
+
+ $sql1 = $this->sql1;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->SelectLimit(
+ "select sum(timer) as total,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer
+ from $perf_table
+ where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT')
+ and (tracer is null or tracer not like 'ERROR:%')
+ group by sql1
+ having count(*)>1
+ order by 1 desc",$numsql);
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $this->conn->fnExecute = $saveE;
+ $ADODB_FETCH_MODE = $save;
+ if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
+ $s = "<h3>Expensive SQL</h3>
+<font size=1>Tuning the following SQL could reduce the server load substantially</font><br>
+<table border=1 bgcolor=white><tr><td><b>Load</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n";
+ $max = $this->maxLength;
+ while (!$rs->EOF) {
+ $sql = $rs->fields[1];
+ $raw = urlencode($sql);
+ if (strlen($raw)>$max-100) {
+ $sql2 = substr($sql,0,$max-500);
+ $raw = urlencode($sql2).'&part='.crc32($sql);
+ }
+ $prefix = "<a target=sqle".rand()." href=\"?hidem=1&expe=1&sql=".$raw."&x#explain\">";
+ $suffix = "</a>";
+ if($this->explain == false || strlen($prefix>$max)) {
+ $prefix = '';
+ $suffix = '';
+ }
+ $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>".
+ "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>";
+ $rs->MoveNext();
+ }
+ return $s."</table>";
+ }
+
+ /*
+ Raw function to return parameter value from $settings.
+ */
+ function DBParameter($param)
+ {
+ if (empty($this->settings[$param])) return false;
+ $sql = $this->settings[$param][1];
+ return $this->_DBParameter($sql);
+ }
+
+ /*
+ Raw function returning array of poll paramters
+ */
+ function PollParameters()
+ {
+ $arr[0] = (float)$this->DBParameter('data cache hit ratio');
+ $arr[1] = (float)$this->DBParameter('data reads');
+ $arr[2] = (float)$this->DBParameter('data writes');
+ $arr[3] = (integer) $this->DBParameter('current connections');
+ return $arr;
+ }
+
+ /*
+ Low-level Get Database Parameter
+ */
+ function _DBParameter($sql)
+ {
+ $savelog = $this->conn->LogSQL(false);
+ if (is_array($sql)) {
+ global $ADODB_FETCH_MODE;
+
+ $sql1 = $sql[0];
+ $key = $sql[1];
+ if (sizeof($sql)>2) $pos = $sql[2];
+ else $pos = 1;
+ if (sizeof($sql)>3) $coef = $sql[3];
+ else $coef = false;
+ $ret = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->Execute($sql1);
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if ($rs) {
+ while (!$rs->EOF) {
+ $keyf = reset($rs->fields);
+ if (trim($keyf) == $key) {
+ $ret = $rs->fields[$pos];
+ if ($coef) $ret *= $coef;
+ break;
+ }
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ }
+ $this->conn->LogSQL($savelog);
+ return $ret;
+ } else {
+ if (strncmp($sql,'=',1) == 0) {
+ $fn = substr($sql,1);
+ return $this->$fn();
+ }
+ $sql = str_replace('$DATABASE',$this->conn->database,$sql);
+ $ret = $this->conn->GetOne($sql);
+ $this->conn->LogSQL($savelog);
+
+ return $ret;
+ }
+ }
+
+ /*
+ Warn if cache ratio falls below threshold. Displayed in "Description" column.
+ */
+ function WarnCacheRatio($val)
+ {
+ if ($val < $this->warnRatio)
+ return '<font color=red><b>Cache ratio should be at least '.$this->warnRatio.'%</b></font>';
+ else return '';
+ }
+
+ function clearsql()
+ {
+ $perf_table = adodb_perf::table();
+ $this->conn->Execute("delete from $perf_table where created<".$this->conn->sysTimeStamp);
+ }
+ /***********************************************************************************************/
+ // HIGH LEVEL UI FUNCTIONS
+ /***********************************************************************************************/
+
+
+ function UI($pollsecs=5)
+ {
+ global $ADODB_LOG_CONN;
+
+ $perf_table = adodb_perf::table();
+ $conn = $this->conn;
+
+ $app = $conn->host;
+ if ($conn->host && $conn->database) $app .= ', db=';
+ $app .= $conn->database;
+
+ if ($app) $app .= ', ';
+ $savelog = $this->conn->LogSQL(false);
+ $info = $conn->ServerInfo();
+ if (isset($_GET['clearsql'])) {
+ $this->clearsql();
+ }
+ $this->conn->LogSQL($savelog);
+
+ // magic quotes
+
+ if (isset($_GET['sql']) && get_magic_quotes_gpc()) {
+ $_GET['sql'] = $_GET['sql'] = str_replace(array("\\'",'\"'),array("'",'"'),$_GET['sql']);
+ }
+
+ if (!isset($_SESSION['ADODB_PERF_SQL'])) $nsql = $_SESSION['ADODB_PERF_SQL'] = 10;
+ else $nsql = $_SESSION['ADODB_PERF_SQL'];
+
+ $app .= $info['description'];
+
+
+ if (isset($_GET['do'])) $do = $_GET['do'];
+ else if (isset($_POST['do'])) $do = $_POST['do'];
+ else if (isset($_GET['sql'])) $do = 'viewsql';
+ else $do = 'stats';
+
+ if (isset($_GET['nsql'])) {
+ if ($_GET['nsql'] > 0) $nsql = $_SESSION['ADODB_PERF_SQL'] = (integer) $_GET['nsql'];
+ }
+ echo "<title>ADOdb Performance Monitor on $app</title><body bgcolor=white>";
+ if ($do == 'viewsql') $form = "<td><form># SQL:<input type=hidden value=viewsql name=do> <input type=text size=4 name=nsql value=$nsql><input type=submit value=Go></td></form>";
+ else $form = "<td>&nbsp;</td>";
+
+ $allowsql = !defined('ADODB_PERF_NO_RUN_SQL');
+ global $ADODB_PERF_MIN;
+ $app .= " (Min sql timing \$ADODB_PERF_MIN=$ADODB_PERF_MIN secs)";
+
+ if (empty($_GET['hidem']))
+ echo "<table border=1 width=100% bgcolor=lightyellow><tr><td colspan=2>
+ <b><a href=http://adodb.org/dokuwiki/doku.php?id=v5:performance:performance_index>ADOdb</a> Performance Monitor</b> <font size=1>for $app</font></tr><tr><td>
+ <a href=?do=stats><b>Performance Stats</b></a> &nbsp; <a href=?do=viewsql><b>View SQL</b></a>
+ &nbsp; <a href=?do=tables><b>View Tables</b></a> &nbsp; <a href=?do=poll><b>Poll Stats</b></a>",
+ $allowsql ? ' &nbsp; <a href=?do=dosql><b>Run SQL</b></a>' : '',
+ "$form",
+ "</tr></table>";
+
+
+ switch ($do) {
+ default:
+ case 'stats':
+ if (empty($ADODB_LOG_CONN))
+ echo "<p>&nbsp; <a href=\"?do=viewsql&clearsql=1\">Clear SQL Log</a><br>";
+ echo $this->HealthCheck();
+ //$this->conn->debug=1;
+ echo $this->CheckMemory();
+ break;
+ case 'poll':
+ $self = htmlspecialchars($_SERVER['PHP_SELF']);
+ echo "<iframe width=720 height=80%
+ src=\"{$self}?do=poll2&hidem=1\"></iframe>";
+ break;
+ case 'poll2':
+ echo "<pre>";
+ $this->Poll($pollsecs);
+ break;
+
+ case 'dosql':
+ if (!$allowsql) break;
+
+ $this->DoSQLForm();
+ break;
+ case 'viewsql':
+ if (empty($_GET['hidem']))
+ echo "&nbsp; <a href=\"?do=viewsql&clearsql=1\">Clear SQL Log</a><br>";
+ echo($this->SuspiciousSQL($nsql));
+ echo($this->ExpensiveSQL($nsql));
+ echo($this->InvalidSQL($nsql));
+ break;
+ case 'tables':
+ echo $this->Tables(); break;
+ }
+ global $ADODB_vers;
+ echo "<p><div align=center><font size=1>$ADODB_vers Sponsored by <a href=http://phplens.com/>phpLens</a></font></div>";
+ }
+
+ /*
+ Runs in infinite loop, returning real-time statistics
+ */
+ function Poll($secs=5)
+ {
+ $this->conn->fnExecute = false;
+ //$this->conn->debug=1;
+ if ($secs <= 1) $secs = 1;
+ echo "Accumulating statistics, every $secs seconds...\n";flush();
+ $arro = $this->PollParameters();
+ $cnt = 0;
+ set_time_limit(0);
+ sleep($secs);
+ while (1) {
+
+ $arr = $this->PollParameters();
+
+ $hits = sprintf('%2.2f',$arr[0]);
+ $reads = sprintf('%12.4f',($arr[1]-$arro[1])/$secs);
+ $writes = sprintf('%12.4f',($arr[2]-$arro[2])/$secs);
+ $sess = sprintf('%5d',$arr[3]);
+
+ $load = $this->CPULoad();
+ if ($load !== false) {
+ $oslabel = 'WS-CPU%';
+ $osval = sprintf(" %2.1f ",(float) $load);
+ }else {
+ $oslabel = '';
+ $osval = '';
+ }
+ if ($cnt % 10 == 0) echo " Time ".$oslabel." Hit% Sess Reads/s Writes/s\n";
+ $cnt += 1;
+ echo date('H:i:s').' '.$osval."$hits $sess $reads $writes\n";
+ flush();
+
+ if (connection_aborted()) return;
+
+ sleep($secs);
+ $arro = $arr;
+ }
+ }
+
+ /*
+ Returns basic health check in a command line interface
+ */
+ function HealthCheckCLI()
+ {
+ return $this->HealthCheck(true);
+ }
+
+
+ /*
+ Returns basic health check as HTML
+ */
+ function HealthCheck($cli=false)
+ {
+ $saveE = $this->conn->fnExecute;
+ $this->conn->fnExecute = false;
+ if ($cli) $html = '';
+ else $html = $this->table.'<tr><td colspan=3><h3>'.$this->conn->databaseType.'</h3></td></tr>'.$this->titles;
+
+ $oldc = false;
+ $bgc = '';
+ foreach($this->settings as $name => $arr) {
+ if ($arr === false) break;
+
+ if (!is_string($name)) {
+ if ($cli) $html .= " -- $arr -- \n";
+ else $html .= "<tr bgcolor=$this->color><td colspan=3><i>$arr</i> &nbsp;</td></tr>";
+ continue;
+ }
+
+ if (!is_array($arr)) break;
+ $category = $arr[0];
+ $how = $arr[1];
+ if (sizeof($arr)>2) $desc = $arr[2];
+ else $desc = ' &nbsp; ';
+
+
+ if ($category == 'HIDE') continue;
+
+ $val = $this->_DBParameter($how);
+
+ if ($desc && strncmp($desc,"=",1) === 0) {
+ $fn = substr($desc,1);
+ $desc = $this->$fn($val);
+ }
+
+ if ($val === false) {
+ $m = $this->conn->ErrorMsg();
+ $val = "Error: $m";
+ } else {
+ if (is_numeric($val) && $val >= 256*1024) {
+ if ($val % (1024*1024) == 0) {
+ $val /= (1024*1024);
+ $val .= 'M';
+ } else if ($val % 1024 == 0) {
+ $val /= 1024;
+ $val .= 'K';
+ }
+ //$val = htmlspecialchars($val);
+ }
+ }
+ if ($category != $oldc) {
+ $oldc = $category;
+ //$bgc = ($bgc == ' bgcolor='.$this->color) ? ' bgcolor=white' : ' bgcolor='.$this->color;
+ }
+ if (strlen($desc)==0) $desc = '&nbsp;';
+ if (strlen($val)==0) $val = '&nbsp;';
+ if ($cli) {
+ $html .= str_replace('&nbsp;','',sprintf($this->cliFormat,strip_tags($name),strip_tags($val),strip_tags($desc)));
+
+ }else {
+ $html .= "<tr$bgc><td>".$name.'</td><td>'.$val.'</td><td>'.$desc."</td></tr>\n";
+ }
+ }
+
+ if (!$cli) $html .= "</table>\n";
+ $this->conn->fnExecute = $saveE;
+
+ return $html;
+ }
+
+ function Tables($orderby='1')
+ {
+ if (!$this->tablesSQL) return false;
+
+ $savelog = $this->conn->LogSQL(false);
+ $rs = $this->conn->Execute($this->tablesSQL.' order by '.$orderby);
+ $this->conn->LogSQL($savelog);
+ $html = rs2html($rs,false,false,false,false);
+ return $html;
+ }
+
+
+ function CreateLogTable()
+ {
+ if (!$this->createTableSQL) return false;
+
+ $table = $this->table();
+ $sql = str_replace('adodb_logsql',$table,$this->createTableSQL);
+ $savelog = $this->conn->LogSQL(false);
+ $ok = $this->conn->Execute($sql);
+ $this->conn->LogSQL($savelog);
+ return ($ok) ? true : false;
+ }
+
+ function DoSQLForm()
+ {
+
+
+ $PHP_SELF = htmlspecialchars($_SERVER['PHP_SELF']);
+ $sql = isset($_REQUEST['sql']) ? $_REQUEST['sql'] : '';
+
+ if (isset($_SESSION['phplens_sqlrows'])) $rows = $_SESSION['phplens_sqlrows'];
+ else $rows = 3;
+
+ if (isset($_REQUEST['SMALLER'])) {
+ $rows /= 2;
+ if ($rows < 3) $rows = 3;
+ $_SESSION['phplens_sqlrows'] = $rows;
+ }
+ if (isset($_REQUEST['BIGGER'])) {
+ $rows *= 2;
+ $_SESSION['phplens_sqlrows'] = $rows;
+ }
+
+?>
+
+<form method="POST" action="<?php echo $PHP_SELF ?>">
+<table><tr>
+<td> Form size: <input type="submit" value=" &lt; " name="SMALLER"><input type="submit" value=" &gt; &gt; " name="BIGGER">
+</td>
+<td align=right>
+<input type="submit" value=" Run SQL Below " name="RUN"><input type=hidden name=do value=dosql>
+</td></tr>
+ <tr>
+ <td colspan=2><textarea rows=<?php print $rows; ?> name="sql" cols="80"><?php print htmlspecialchars($sql) ?></textarea>
+ </td>
+ </tr>
+ </table>
+</form>
+
+<?php
+ if (!isset($_REQUEST['sql'])) return;
+
+ $sql = $this->undomq(trim($sql));
+ if (substr($sql,strlen($sql)-1) === ';') {
+ $print = true;
+ $sqla = $this->SplitSQL($sql);
+ } else {
+ $print = false;
+ $sqla = array($sql);
+ }
+ foreach($sqla as $sqls) {
+
+ if (!$sqls) continue;
+
+ if ($print) {
+ print "<p>".htmlspecialchars($sqls)."</p>";
+ flush();
+ }
+ $savelog = $this->conn->LogSQL(false);
+ $rs = $this->conn->Execute($sqls);
+ $this->conn->LogSQL($savelog);
+ if ($rs && is_object($rs) && !$rs->EOF) {
+ rs2html($rs);
+ while ($rs->NextRecordSet()) {
+ print "<table width=98% bgcolor=#C0C0FF><tr><td>&nbsp;</td></tr></table>";
+ rs2html($rs);
+ }
+ } else {
+ $e1 = (integer) $this->conn->ErrorNo();
+ $e2 = $this->conn->ErrorMsg();
+ if (($e1) || ($e2)) {
+ if (empty($e1)) $e1 = '-1'; // postgresql fix
+ print ' &nbsp; '.$e1.': '.$e2;
+ } else {
+ print "<p>No Recordset returned<br></p>";
+ }
+ }
+ } // foreach
+ }
+
+ function SplitSQL($sql)
+ {
+ $arr = explode(';',$sql);
+ return $arr;
+ }
+
+ function undomq($m)
+ {
+ if (get_magic_quotes_gpc()) {
+ // undo the damage
+ $m = str_replace('\\\\','\\',$m);
+ $m = str_replace('\"','"',$m);
+ $m = str_replace('\\\'','\'',$m);
+ }
+ return $m;
+}
+
+
+ /************************************************************************/
+
+ /**
+ * Reorganise multiple table-indices/statistics/..
+ * OptimizeMode could be given by last Parameter
+ *
+ * @example
+ * <pre>
+ * optimizeTables( 'tableA');
+ * </pre>
+ * <pre>
+ * optimizeTables( 'tableA', 'tableB', 'tableC');
+ * </pre>
+ * <pre>
+ * optimizeTables( 'tableA', 'tableB', ADODB_OPT_LOW);
+ * </pre>
+ *
+ * @param string table name of the table to optimize
+ * @param int mode optimization-mode
+ * <code>ADODB_OPT_HIGH</code> for full optimization
+ * <code>ADODB_OPT_LOW</code> for CPU-less optimization
+ * Default is LOW <code>ADODB_OPT_LOW</code>
+ * @author Markus Staab
+ * @return Returns <code>true</code> on success and <code>false</code> on error
+ */
+ function OptimizeTables()
+ {
+ $args = func_get_args();
+ $numArgs = func_num_args();
+
+ if ( $numArgs == 0) return false;
+
+ $mode = ADODB_OPT_LOW;
+ $lastArg = $args[ $numArgs - 1];
+ if ( !is_string($lastArg)) {
+ $mode = $lastArg;
+ unset( $args[ $numArgs - 1]);
+ }
+
+ foreach( $args as $table) {
+ $this->optimizeTable( $table, $mode);
+ }
+ }
+
+ /**
+ * Reorganise the table-indices/statistics/.. depending on the given mode.
+ * Default Implementation throws an error.
+ *
+ * @param string table name of the table to optimize
+ * @param int mode optimization-mode
+ * <code>ADODB_OPT_HIGH</code> for full optimization
+ * <code>ADODB_OPT_LOW</code> for CPU-less optimization
+ * Default is LOW <code>ADODB_OPT_LOW</code>
+ * @author Markus Staab
+ * @return Returns <code>true</code> on success and <code>false</code> on error
+ */
+ function OptimizeTable( $table, $mode = ADODB_OPT_LOW)
+ {
+ ADOConnection::outp( sprintf( "<p>%s: '%s' not implemented for driver '%s'</p>", __CLASS__, __FUNCTION__, $this->conn->databaseType));
+ return false;
+ }
+
+ /**
+ * Reorganise current database.
+ * Default implementation loops over all <code>MetaTables()</code> and
+ * optimize each using <code>optmizeTable()</code>
+ *
+ * @author Markus Staab
+ * @return Returns <code>true</code> on success and <code>false</code> on error
+ */
+ function optimizeDatabase()
+ {
+ $conn = $this->conn;
+ if ( !$conn) return false;
+
+ $tables = $conn->MetaTables( 'TABLES');
+ if ( !$tables ) return false;
+
+ foreach( $tables as $table) {
+ if ( !$this->optimizeTable( $table)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ // end hack
+}
diff --git a/vendor/adodb/adodb-php/adodb-php4.inc.php b/vendor/adodb/adodb-php/adodb-php4.inc.php
new file mode 100644
index 0000000..138cee0
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-php4.inc.php
@@ -0,0 +1,16 @@
+<?php
+
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4.
+*/
+
+
+class ADODB_BASE_RS {
+}
diff --git a/vendor/adodb/adodb-php/adodb-time.inc.php b/vendor/adodb/adodb-php/adodb-time.inc.php
new file mode 100644
index 0000000..fa028c6
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-time.inc.php
@@ -0,0 +1,1489 @@
+<?php
+/*
+ADOdb Date Library, part of the ADOdb abstraction library
+
+Latest version is available at http://adodb.org/
+
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+PHP native date functions use integer timestamps for computations.
+Because of this, dates are restricted to the years 1901-2038 on Unix
+and 1970-2038 on Windows due to integer overflow for dates beyond
+those years. This library overcomes these limitations by replacing the
+native function's signed integers (normally 32-bits) with PHP floating
+point numbers (normally 64-bits).
+
+Dates from 100 A.D. to 3000 A.D. and later
+have been tested. The minimum is 100 A.D. as <100 will invoke the
+2 => 4 digit year conversion. The maximum is billions of years in the
+future, but this is a theoretical limit as the computation of that year
+would take too long with the current implementation of adodb_mktime().
+
+This library replaces native functions as follows:
+
+<pre>
+ getdate() with adodb_getdate()
+ date() with adodb_date()
+ gmdate() with adodb_gmdate()
+ mktime() with adodb_mktime()
+ gmmktime() with adodb_gmmktime()
+ strftime() with adodb_strftime()
+ strftime() with adodb_gmstrftime()
+</pre>
+
+The parameters are identical, except that adodb_date() accepts a subset
+of date()'s field formats. Mktime() will convert from local time to GMT,
+and date() will convert from GMT to local time, but daylight savings is
+not handled currently.
+
+This library is independant of the rest of ADOdb, and can be used
+as standalone code.
+
+PERFORMANCE
+
+For high speed, this library uses the native date functions where
+possible, and only switches to PHP code when the dates fall outside
+the 32-bit signed integer range.
+
+GREGORIAN CORRECTION
+
+Pope Gregory shortened October of A.D. 1582 by ten days. Thursday,
+October 4, 1582 (Julian) was followed immediately by Friday, October 15,
+1582 (Gregorian).
+
+Since 0.06, we handle this correctly, so:
+
+adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582)
+ == 24 * 3600 (1 day)
+
+=============================================================================
+
+COPYRIGHT
+
+(c) 2003-2014 John Lim and released under BSD-style license except for code by
+jackbbs, which includes adodb_mktime, adodb_get_gmt_diff, adodb_is_leap_year
+and originally found at http://www.php.net/manual/en/function.mktime.php
+
+=============================================================================
+
+BUG REPORTS
+
+These should be posted to the ADOdb forums at
+
+ http://phplens.com/lens/lensforum/topics.php?id=4
+
+=============================================================================
+
+FUNCTION DESCRIPTIONS
+
+** FUNCTION adodb_time()
+
+Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) as an unsigned integer.
+
+** FUNCTION adodb_getdate($date=false)
+
+Returns an array containing date information, as getdate(), but supports
+dates greater than 1901 to 2038. The local date/time format is derived from a
+heuristic the first time adodb_getdate is called.
+
+
+** FUNCTION adodb_date($fmt, $timestamp = false)
+
+Convert a timestamp to a formatted local date. If $timestamp is not defined, the
+current timestamp is used. Unlike the function date(), it supports dates
+outside the 1901 to 2038 range.
+
+The format fields that adodb_date supports:
+
+<pre>
+ a - "am" or "pm"
+ A - "AM" or "PM"
+ d - day of the month, 2 digits with leading zeros; i.e. "01" to "31"
+ D - day of the week, textual, 3 letters; e.g. "Fri"
+ F - month, textual, long; e.g. "January"
+ g - hour, 12-hour format without leading zeros; i.e. "1" to "12"
+ G - hour, 24-hour format without leading zeros; i.e. "0" to "23"
+ h - hour, 12-hour format; i.e. "01" to "12"
+ H - hour, 24-hour format; i.e. "00" to "23"
+ i - minutes; i.e. "00" to "59"
+ j - day of the month without leading zeros; i.e. "1" to "31"
+ l (lowercase 'L') - day of the week, textual, long; e.g. "Friday"
+ L - boolean for whether it is a leap year; i.e. "0" or "1"
+ m - month; i.e. "01" to "12"
+ M - month, textual, 3 letters; e.g. "Jan"
+ n - month without leading zeros; i.e. "1" to "12"
+ O - Difference to Greenwich time in hours; e.g. "+0200"
+ Q - Quarter, as in 1, 2, 3, 4
+ r - RFC 2822 formatted date; e.g. "Thu, 21 Dec 2000 16:01:07 +0200"
+ s - seconds; i.e. "00" to "59"
+ S - English ordinal suffix for the day of the month, 2 characters;
+ i.e. "st", "nd", "rd" or "th"
+ t - number of days in the given month; i.e. "28" to "31"
+ T - Timezone setting of this machine; e.g. "EST" or "MDT"
+ U - seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
+ w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday)
+ Y - year, 4 digits; e.g. "1999"
+ y - year, 2 digits; e.g. "99"
+ z - day of the year; i.e. "0" to "365"
+ Z - timezone offset in seconds (i.e. "-43200" to "43200").
+ The offset for timezones west of UTC is always negative,
+ and for those east of UTC is always positive.
+</pre>
+
+Unsupported:
+<pre>
+ B - Swatch Internet time
+ I (capital i) - "1" if Daylight Savings Time, "0" otherwise.
+ W - ISO-8601 week number of year, weeks starting on Monday
+
+</pre>
+
+
+** FUNCTION adodb_date2($fmt, $isoDateString = false)
+Same as adodb_date, but 2nd parameter accepts iso date, eg.
+
+ adodb_date2('d-M-Y H:i','2003-12-25 13:01:34');
+
+
+** FUNCTION adodb_gmdate($fmt, $timestamp = false)
+
+Convert a timestamp to a formatted GMT date. If $timestamp is not defined, the
+current timestamp is used. Unlike the function date(), it supports dates
+outside the 1901 to 2038 range.
+
+
+** FUNCTION adodb_mktime($hr, $min, $sec[, $month, $day, $year])
+
+Converts a local date to a unix timestamp. Unlike the function mktime(), it supports
+dates outside the 1901 to 2038 range. All parameters are optional.
+
+
+** FUNCTION adodb_gmmktime($hr, $min, $sec [, $month, $day, $year])
+
+Converts a gmt date to a unix timestamp. Unlike the function gmmktime(), it supports
+dates outside the 1901 to 2038 range. Differs from gmmktime() in that all parameters
+are currently compulsory.
+
+** FUNCTION adodb_gmstrftime($fmt, $timestamp = false)
+Convert a timestamp to a formatted GMT date.
+
+** FUNCTION adodb_strftime($fmt, $timestamp = false)
+
+Convert a timestamp to a formatted local date. Internally converts $fmt into
+adodb_date format, then echo result.
+
+For best results, you can define the local date format yourself. Define a global
+variable $ADODB_DATE_LOCALE which is an array, 1st element is date format using
+adodb_date syntax, and 2nd element is the time format, also in adodb_date syntax.
+
+ eg. $ADODB_DATE_LOCALE = array('d/m/Y','H:i:s');
+
+ Supported format codes:
+
+<pre>
+ %a - abbreviated weekday name according to the current locale
+ %A - full weekday name according to the current locale
+ %b - abbreviated month name according to the current locale
+ %B - full month name according to the current locale
+ %c - preferred date and time representation for the current locale
+ %d - day of the month as a decimal number (range 01 to 31)
+ %D - same as %m/%d/%y
+ %e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')
+ %h - same as %b
+ %H - hour as a decimal number using a 24-hour clock (range 00 to 23)
+ %I - hour as a decimal number using a 12-hour clock (range 01 to 12)
+ %m - month as a decimal number (range 01 to 12)
+ %M - minute as a decimal number
+ %n - newline character
+ %p - either `am' or `pm' according to the given time value, or the corresponding strings for the current locale
+ %r - time in a.m. and p.m. notation
+ %R - time in 24 hour notation
+ %S - second as a decimal number
+ %t - tab character
+ %T - current time, equal to %H:%M:%S
+ %x - preferred date representation for the current locale without the time
+ %X - preferred time representation for the current locale without the date
+ %y - year as a decimal number without a century (range 00 to 99)
+ %Y - year as a decimal number including the century
+ %Z - time zone or name or abbreviation
+ %% - a literal `%' character
+</pre>
+
+ Unsupported codes:
+<pre>
+ %C - century number (the year divided by 100 and truncated to an integer, range 00 to 99)
+ %g - like %G, but without the century.
+ %G - The 4-digit year corresponding to the ISO week number (see %V).
+ This has the same format and value as %Y, except that if the ISO week number belongs
+ to the previous or next year, that year is used instead.
+ %j - day of the year as a decimal number (range 001 to 366)
+ %u - weekday as a decimal number [1,7], with 1 representing Monday
+ %U - week number of the current year as a decimal number, starting
+ with the first Sunday as the first day of the first week
+ %V - The ISO 8601:1988 week number of the current year as a decimal number,
+ range 01 to 53, where week 1 is the first week that has at least 4 days in the
+ current year, and with Monday as the first day of the week. (Use %G or %g for
+ the year component that corresponds to the week number for the specified timestamp.)
+ %w - day of the week as a decimal, Sunday being 0
+ %W - week number of the current year as a decimal number, starting with the
+ first Monday as the first day of the first week
+</pre>
+
+=============================================================================
+
+NOTES
+
+Useful url for generating test timestamps:
+ http://www.4webhelp.net/us/timestamp.php
+
+Possible future optimizations include
+
+a. Using an algorithm similar to Plauger's in "The Standard C Library"
+(page 428, xttotm.c _Ttotm() function). Plauger's algorithm will not
+work outside 32-bit signed range, so i decided not to implement it.
+
+b. Implement daylight savings, which looks awfully complicated, see
+ http://webexhibits.org/daylightsaving/
+
+
+CHANGELOG
+- 16 Jan 2011 0.36
+Added adodb_time() which returns current time. If > 2038, will return as float
+
+- 7 Feb 2011 0.35
+Changed adodb_date to be symmetric with adodb_mktime. See $jan1_71. fix for bc.
+
+- 13 July 2010 0.34
+Changed adodb_get_gm_diff to use DateTimeZone().
+
+- 11 Feb 2008 0.33
+* Bug in 0.32 fix for hour handling. Fixed.
+
+- 1 Feb 2008 0.32
+* Now adodb_mktime(0,0,0,12+$m,20,2040) works properly.
+
+- 10 Jan 2008 0.31
+* Now adodb_mktime(0,0,0,24,1,2037) works correctly.
+
+- 15 July 2007 0.30
+Added PHP 5.2.0 compatability fixes.
+ * gmtime behaviour for 1970 has changed. We use the actual date if it is between 1970 to 2038 to get the
+ * timezone, otherwise we use the current year as the baseline to retrieve the timezone.
+ * Also the timezone's in php 5.2.* support historical data better, eg. if timezone today was +8, but
+ in 1970 it was +7:30, then php 5.2 return +7:30, while this library will use +8.
+ *
+
+- 19 March 2006 0.24
+Changed strftime() locale detection, because some locales prepend the day of week to the date when %c is used.
+
+- 10 Feb 2006 0.23
+PHP5 compat: when we detect PHP5, the RFC2822 format for gmt 0000hrs is changed from -0000 to +0000.
+ In PHP4, we will still use -0000 for 100% compat with PHP4.
+
+- 08 Sept 2005 0.22
+In adodb_date2(), $is_gmt not supported properly. Fixed.
+
+- 18 July 2005 0.21
+In PHP 4.3.11, the 'r' format has changed. Leading 0 in day is added. Changed for compat.
+Added support for negative months in adodb_mktime().
+
+- 24 Feb 2005 0.20
+Added limited strftime/gmstrftime support. x10 improvement in performance of adodb_date().
+
+- 21 Dec 2004 0.17
+In adodb_getdate(), the timestamp was accidentally converted to gmt when $is_gmt is false.
+Also adodb_mktime(0,0,0) did not work properly. Both fixed thx Mauro.
+
+- 17 Nov 2004 0.16
+Removed intval typecast in adodb_mktime() for secs, allowing:
+ adodb_mktime(0,0,0 + 2236672153,1,1,1934);
+Suggested by Ryan.
+
+- 18 July 2004 0.15
+All params in adodb_mktime were formerly compulsory. Now only the hour, min, secs is compulsory.
+This brings it more in line with mktime (still not identical).
+
+- 23 June 2004 0.14
+
+Allow you to define your own daylights savings function, adodb_daylight_sv.
+If the function is defined (somewhere in an include), then you can correct for daylights savings.
+
+In this example, we apply daylights savings in June or July, adding one hour. This is extremely
+unrealistic as it does not take into account time-zone, geographic location, current year.
+
+function adodb_daylight_sv(&$arr, $is_gmt)
+{
+ if ($is_gmt) return;
+ $m = $arr['mon'];
+ if ($m == 6 || $m == 7) $arr['hours'] += 1;
+}
+
+This is only called by adodb_date() and not by adodb_mktime().
+
+The format of $arr is
+Array (
+ [seconds] => 0
+ [minutes] => 0
+ [hours] => 0
+ [mday] => 1 # day of month, eg 1st day of the month
+ [mon] => 2 # month (eg. Feb)
+ [year] => 2102
+ [yday] => 31 # days in current year
+ [leap] => # true if leap year
+ [ndays] => 28 # no of days in current month
+ )
+
+
+- 28 Apr 2004 0.13
+Fixed adodb_date to properly support $is_gmt. Thx to Dimitar Angelov.
+
+- 20 Mar 2004 0.12
+Fixed month calculation error in adodb_date. 2102-June-01 appeared as 2102-May-32.
+
+- 26 Oct 2003 0.11
+Because of daylight savings problems (some systems apply daylight savings to
+January!!!), changed adodb_get_gmt_diff() to ignore daylight savings.
+
+- 9 Aug 2003 0.10
+Fixed bug with dates after 2038.
+See http://phplens.com/lens/lensforum/msgs.php?id=6980
+
+- 1 July 2003 0.09
+Added support for Q (Quarter).
+Added adodb_date2(), which accepts ISO date in 2nd param
+
+- 3 March 2003 0.08
+Added support for 'S' adodb_date() format char. Added constant ADODB_ALLOW_NEGATIVE_TS
+if you want PHP to handle negative timestamps between 1901 to 1969.
+
+- 27 Feb 2003 0.07
+All negative numbers handled by adodb now because of RH 7.3+ problems.
+See http://bugs.php.net/bug.php?id=20048&edit=2
+
+- 4 Feb 2003 0.06
+Fixed a typo, 1852 changed to 1582! This means that pre-1852 dates
+are now correctly handled.
+
+- 29 Jan 2003 0.05
+
+Leap year checking differs under Julian calendar (pre 1582). Also
+leap year code optimized by checking for most common case first.
+
+We also handle month overflow correctly in mktime (eg month set to 13).
+
+Day overflow for less than one month's days is supported.
+
+- 28 Jan 2003 0.04
+
+Gregorian correction handled. In PHP5, we might throw an error if
+mktime uses invalid dates around 5-14 Oct 1582. Released with ADOdb 3.10.
+Added limbo 5-14 Oct 1582 check, when we set to 15 Oct 1582.
+
+- 27 Jan 2003 0.03
+
+Fixed some more month problems due to gmt issues. Added constant ADODB_DATE_VERSION.
+Fixed calculation of days since start of year for <1970.
+
+- 27 Jan 2003 0.02
+
+Changed _adodb_getdate() to inline leap year checking for better performance.
+Fixed problem with time-zones west of GMT +0000.
+
+- 24 Jan 2003 0.01
+
+First implementation.
+*/
+
+
+/* Initialization */
+
+/*
+ Version Number
+*/
+define('ADODB_DATE_VERSION',0.35);
+
+$ADODB_DATETIME_CLASS = (PHP_VERSION >= 5.2);
+
+/*
+ This code was originally for windows. But apparently this problem happens
+ also with Linux, RH 7.3 and later!
+
+ glibc-2.2.5-34 and greater has been changed to return -1 for dates <
+ 1970. This used to work. The problem exists with RedHat 7.3 and 8.0
+ echo (mktime(0, 0, 0, 1, 1, 1960)); // prints -1
+
+ References:
+ http://bugs.php.net/bug.php?id=20048&edit=2
+ http://lists.debian.org/debian-glibc/2002/debian-glibc-200205/msg00010.html
+*/
+
+if (!defined('ADODB_ALLOW_NEGATIVE_TS')) define('ADODB_NO_NEGATIVE_TS',1);
+
+if (!DEFINED('ADODB_FUTURE_DATE_CUTOFF_YEARS'))
+ DEFINE('ADODB_FUTURE_DATE_CUTOFF_YEARS',200);
+
+function adodb_date_test_date($y1,$m,$d=13)
+{
+ $h = round(rand()% 24);
+ $t = adodb_mktime($h,0,0,$m,$d,$y1);
+ $rez = adodb_date('Y-n-j H:i:s',$t);
+ if ($h == 0) $h = '00';
+ else if ($h < 10) $h = '0'.$h;
+ if ("$y1-$m-$d $h:00:00" != $rez) {
+ print "<b>$y1 error, expected=$y1-$m-$d $h:00:00, adodb=$rez</b><br>";
+ return false;
+ }
+ return true;
+}
+
+function adodb_date_test_strftime($fmt)
+{
+ $s1 = strftime($fmt);
+ $s2 = adodb_strftime($fmt);
+
+ if ($s1 == $s2) return true;
+
+ echo "error for $fmt, strftime=$s1, adodb=$s2<br>";
+ return false;
+}
+
+/**
+ Test Suite
+*/
+function adodb_date_test()
+{
+
+ for ($m=-24; $m<=24; $m++)
+ echo "$m :",adodb_date('d-m-Y',adodb_mktime(0,0,0,1+$m,20,2040)),"<br>";
+
+ error_reporting(E_ALL);
+ print "<h4>Testing adodb_date and adodb_mktime. version=".ADODB_DATE_VERSION.' PHP='.PHP_VERSION."</h4>";
+ @set_time_limit(0);
+ $fail = false;
+
+ // This flag disables calling of PHP native functions, so we can properly test the code
+ if (!defined('ADODB_TEST_DATES')) define('ADODB_TEST_DATES',1);
+
+ $t = time();
+
+
+ $fmt = 'Y-m-d H:i:s';
+ echo '<pre>';
+ echo 'adodb: ',adodb_date($fmt,$t),'<br>';
+ echo 'php : ',date($fmt,$t),'<br>';
+ echo '</pre>';
+
+ adodb_date_test_strftime('%Y %m %x %X');
+ adodb_date_test_strftime("%A %d %B %Y");
+ adodb_date_test_strftime("%H %M S");
+
+ $t = adodb_mktime(0,0,0);
+ if (!(adodb_date('Y-m-d') == date('Y-m-d'))) print 'Error in '.adodb_mktime(0,0,0).'<br>';
+
+ $t = adodb_mktime(0,0,0,6,1,2102);
+ if (!(adodb_date('Y-m-d',$t) == '2102-06-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
+
+ $t = adodb_mktime(0,0,0,2,1,2102);
+ if (!(adodb_date('Y-m-d',$t) == '2102-02-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
+
+
+ print "<p>Testing gregorian <=> julian conversion<p>";
+ $t = adodb_mktime(0,0,0,10,11,1492);
+ //http://www.holidayorigins.com/html/columbus_day.html - Friday check
+ if (!(adodb_date('D Y-m-d',$t) == 'Fri 1492-10-11')) print 'Error in Columbus landing<br>';
+
+ $t = adodb_mktime(0,0,0,2,29,1500);
+ if (!(adodb_date('Y-m-d',$t) == '1500-02-29')) print 'Error in julian leap years<br>';
+
+ $t = adodb_mktime(0,0,0,2,29,1700);
+ if (!(adodb_date('Y-m-d',$t) == '1700-03-01')) print 'Error in gregorian leap years<br>';
+
+ print adodb_mktime(0,0,0,10,4,1582).' ';
+ print adodb_mktime(0,0,0,10,15,1582);
+ $diff = (adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582));
+ if ($diff != 3600*24) print " <b>Error in gregorian correction = ".($diff/3600/24)." days </b><br>";
+
+ print " 15 Oct 1582, Fri=".(adodb_dow(1582,10,15) == 5 ? 'Fri' : '<b>Error</b>')."<br>";
+ print " 4 Oct 1582, Thu=".(adodb_dow(1582,10,4) == 4 ? 'Thu' : '<b>Error</b>')."<br>";
+
+ print "<p>Testing overflow<p>";
+
+ $t = adodb_mktime(0,0,0,3,33,1965);
+ if (!(adodb_date('Y-m-d',$t) == '1965-04-02')) print 'Error in day overflow 1 <br>';
+ $t = adodb_mktime(0,0,0,4,33,1971);
+ if (!(adodb_date('Y-m-d',$t) == '1971-05-03')) print 'Error in day overflow 2 <br>';
+ $t = adodb_mktime(0,0,0,1,60,1965);
+ if (!(adodb_date('Y-m-d',$t) == '1965-03-01')) print 'Error in day overflow 3 '.adodb_date('Y-m-d',$t).' <br>';
+ $t = adodb_mktime(0,0,0,12,32,1965);
+ if (!(adodb_date('Y-m-d',$t) == '1966-01-01')) print 'Error in day overflow 4 '.adodb_date('Y-m-d',$t).' <br>';
+ $t = adodb_mktime(0,0,0,12,63,1965);
+ if (!(adodb_date('Y-m-d',$t) == '1966-02-01')) print 'Error in day overflow 5 '.adodb_date('Y-m-d',$t).' <br>';
+ $t = adodb_mktime(0,0,0,13,3,1965);
+ if (!(adodb_date('Y-m-d',$t) == '1966-01-03')) print 'Error in mth overflow 1 <br>';
+
+ print "Testing 2-digit => 4-digit year conversion<p>";
+ if (adodb_year_digit_check(00) != 2000) print "Err 2-digit 2000<br>";
+ if (adodb_year_digit_check(10) != 2010) print "Err 2-digit 2010<br>";
+ if (adodb_year_digit_check(20) != 2020) print "Err 2-digit 2020<br>";
+ if (adodb_year_digit_check(30) != 2030) print "Err 2-digit 2030<br>";
+ if (adodb_year_digit_check(40) != 1940) print "Err 2-digit 1940<br>";
+ if (adodb_year_digit_check(50) != 1950) print "Err 2-digit 1950<br>";
+ if (adodb_year_digit_check(90) != 1990) print "Err 2-digit 1990<br>";
+
+ // Test string formating
+ print "<p>Testing date formating</p>";
+
+ $fmt = '\d\a\t\e T Y-m-d H:i:s a A d D F g G h H i j l L m M n O \R\F\C2822 r s t U w y Y z Z 2003';
+ $s1 = date($fmt,0);
+ $s2 = adodb_date($fmt,0);
+ if ($s1 != $s2) {
+ print " date() 0 failed<br>$s1<br>$s2<br>";
+ }
+ flush();
+ for ($i=100; --$i > 0; ) {
+
+ $ts = 3600.0*((rand()%60000)+(rand()%60000))+(rand()%60000);
+ $s1 = date($fmt,$ts);
+ $s2 = adodb_date($fmt,$ts);
+ //print "$s1 <br>$s2 <p>";
+ $pos = strcmp($s1,$s2);
+
+ if (($s1) != ($s2)) {
+ for ($j=0,$k=strlen($s1); $j < $k; $j++) {
+ if ($s1[$j] != $s2[$j]) {
+ print substr($s1,$j).' ';
+ break;
+ }
+ }
+ print "<b>Error date(): $ts<br><pre>
+&nbsp; \"$s1\" (date len=".strlen($s1).")
+&nbsp; \"$s2\" (adodb_date len=".strlen($s2).")</b></pre><br>";
+ $fail = true;
+ }
+
+ $a1 = getdate($ts);
+ $a2 = adodb_getdate($ts);
+ $rez = array_diff($a1,$a2);
+ if (sizeof($rez)>0) {
+ print "<b>Error getdate() $ts</b><br>";
+ print_r($a1);
+ print "<br>";
+ print_r($a2);
+ print "<p>";
+ $fail = true;
+ }
+ }
+
+ // Test generation of dates outside 1901-2038
+ print "<p>Testing random dates between 100 and 4000</p>";
+ adodb_date_test_date(100,1);
+ for ($i=100; --$i >= 0;) {
+ $y1 = 100+rand(0,1970-100);
+ $m = rand(1,12);
+ adodb_date_test_date($y1,$m);
+
+ $y1 = 3000-rand(0,3000-1970);
+ adodb_date_test_date($y1,$m);
+ }
+ print '<p>';
+ $start = 1960+rand(0,10);
+ $yrs = 12;
+ $i = 365.25*86400*($start-1970);
+ $offset = 36000+rand(10000,60000);
+ $max = 365*$yrs*86400;
+ $lastyear = 0;
+
+ // we generate a timestamp, convert it to a date, and convert it back to a timestamp
+ // and check if the roundtrip broke the original timestamp value.
+ print "Testing $start to ".($start+$yrs).", or $max seconds, offset=$offset: ";
+ $cnt = 0;
+ for ($max += $i; $i < $max; $i += $offset) {
+ $ret = adodb_date('m,d,Y,H,i,s',$i);
+ $arr = explode(',',$ret);
+ if ($lastyear != $arr[2]) {
+ $lastyear = $arr[2];
+ print " $lastyear ";
+ flush();
+ }
+ $newi = adodb_mktime($arr[3],$arr[4],$arr[5],$arr[0],$arr[1],$arr[2]);
+ if ($i != $newi) {
+ print "Error at $i, adodb_mktime returned $newi ($ret)";
+ $fail = true;
+ break;
+ }
+ $cnt += 1;
+ }
+ echo "Tested $cnt dates<br>";
+ if (!$fail) print "<p>Passed !</p>";
+ else print "<p><b>Failed</b> :-(</p>";
+}
+
+function adodb_time()
+{
+ $d = new DateTime();
+ return $d->format('U');
+}
+
+/**
+ Returns day of week, 0 = Sunday,... 6=Saturday.
+ Algorithm from PEAR::Date_Calc
+*/
+function adodb_dow($year, $month, $day)
+{
+/*
+Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
+proclaimed that from that time onwards 3 days would be dropped from the calendar
+every 400 years.
+
+Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
+*/
+ if ($year <= 1582) {
+ if ($year < 1582 ||
+ ($year == 1582 && ($month < 10 || ($month == 10 && $day < 15)))) $greg_correction = 3;
+ else
+ $greg_correction = 0;
+ } else
+ $greg_correction = 0;
+
+ if($month > 2)
+ $month -= 2;
+ else {
+ $month += 10;
+ $year--;
+ }
+
+ $day = floor((13 * $month - 1) / 5) +
+ $day + ($year % 100) +
+ floor(($year % 100) / 4) +
+ floor(($year / 100) / 4) - 2 *
+ floor($year / 100) + 77 + $greg_correction;
+
+ return $day - 7 * floor($day / 7);
+}
+
+
+/**
+ Checks for leap year, returns true if it is. No 2-digit year check. Also
+ handles julian calendar correctly.
+*/
+function _adodb_is_leap_year($year)
+{
+ if ($year % 4 != 0) return false;
+
+ if ($year % 400 == 0) {
+ return true;
+ // if gregorian calendar (>1582), century not-divisible by 400 is not leap
+ } else if ($year > 1582 && $year % 100 == 0 ) {
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ checks for leap year, returns true if it is. Has 2-digit year check
+*/
+function adodb_is_leap_year($year)
+{
+ return _adodb_is_leap_year(adodb_year_digit_check($year));
+}
+
+/**
+ Fix 2-digit years. Works for any century.
+ Assumes that if 2-digit is more than 30 years in future, then previous century.
+*/
+function adodb_year_digit_check($y)
+{
+ if ($y < 100) {
+
+ $yr = (integer) date("Y");
+ $century = (integer) ($yr /100);
+
+ if ($yr%100 > 50) {
+ $c1 = $century + 1;
+ $c0 = $century;
+ } else {
+ $c1 = $century;
+ $c0 = $century - 1;
+ }
+ $c1 *= 100;
+ // if 2-digit year is less than 30 years in future, set it to this century
+ // otherwise if more than 30 years in future, then we set 2-digit year to the prev century.
+ if (($y + $c1) < $yr+30) $y = $y + $c1;
+ else $y = $y + $c0*100;
+ }
+ return $y;
+}
+
+function adodb_get_gmt_diff_ts($ts)
+{
+ if (0 <= $ts && $ts <= 0x7FFFFFFF) { // check if number in 32-bit signed range) {
+ $arr = getdate($ts);
+ $y = $arr['year'];
+ $m = $arr['mon'];
+ $d = $arr['mday'];
+ return adodb_get_gmt_diff($y,$m,$d);
+ } else {
+ return adodb_get_gmt_diff(false,false,false);
+ }
+
+}
+
+/**
+ get local time zone offset from GMT. Does not handle historical timezones before 1970.
+*/
+function adodb_get_gmt_diff($y,$m,$d)
+{
+static $TZ,$tzo;
+global $ADODB_DATETIME_CLASS;
+
+ if (!defined('ADODB_TEST_DATES')) $y = false;
+ else if ($y < 1970 || $y >= 2038) $y = false;
+
+ if ($ADODB_DATETIME_CLASS && $y !== false) {
+ $dt = new DateTime();
+ $dt->setISODate($y,$m,$d);
+ if (empty($tzo)) {
+ $tzo = new DateTimeZone(date_default_timezone_get());
+ # $tzt = timezone_transitions_get( $tzo );
+ }
+ return -$tzo->getOffset($dt);
+ } else {
+ if (isset($TZ)) return $TZ;
+ $y = date('Y');
+ /*
+ if (function_exists('date_default_timezone_get') && function_exists('timezone_offset_get')) {
+ $tzonename = date_default_timezone_get();
+ if ($tzonename) {
+ $tobj = new DateTimeZone($tzonename);
+ $TZ = -timezone_offset_get($tobj,new DateTime("now",$tzo));
+ }
+ }
+ */
+ if (empty($TZ)) $TZ = mktime(0,0,0,12,2,$y) - gmmktime(0,0,0,12,2,$y);
+ }
+ return $TZ;
+}
+
+/**
+ Returns an array with date info.
+*/
+function adodb_getdate($d=false,$fast=false)
+{
+ if ($d === false) return getdate();
+ if (!defined('ADODB_TEST_DATES')) {
+ if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
+ if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer
+ return @getdate($d);
+ }
+ }
+ return _adodb_getdate($d);
+}
+
+/*
+// generate $YRS table for _adodb_getdate()
+function adodb_date_gentable($out=true)
+{
+
+ for ($i=1970; $i >= 1600; $i-=10) {
+ $s = adodb_gmmktime(0,0,0,1,1,$i);
+ echo "$i => $s,<br>";
+ }
+}
+adodb_date_gentable();
+
+for ($i=1970; $i > 1500; $i--) {
+
+echo "<hr />$i ";
+ adodb_date_test_date($i,1,1);
+}
+
+*/
+
+
+$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
+$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
+
+function adodb_validdate($y,$m,$d)
+{
+global $_month_table_normal,$_month_table_leaf;
+
+ if (_adodb_is_leap_year($y)) $marr = $_month_table_leaf;
+ else $marr = $_month_table_normal;
+
+ if ($m > 12 || $m < 1) return false;
+
+ if ($d > 31 || $d < 1) return false;
+
+ if ($marr[$m] < $d) return false;
+
+ if ($y < 1000 || $y > 3000) return false;
+
+ return true;
+}
+
+/**
+ Low-level function that returns the getdate() array. We have a special
+ $fast flag, which if set to true, will return fewer array values,
+ and is much faster as it does not calculate dow, etc.
+*/
+function _adodb_getdate($origd=false,$fast=false,$is_gmt=false)
+{
+static $YRS;
+global $_month_table_normal,$_month_table_leaf, $_adodb_last_date_call_failed;
+
+ $_adodb_last_date_call_failed = false;
+
+ $d = $origd - ($is_gmt ? 0 : adodb_get_gmt_diff_ts($origd));
+ $_day_power = 86400;
+ $_hour_power = 3600;
+ $_min_power = 60;
+
+ $cutoffDate = time() + (60 * 60 * 24 * 365 * ADODB_FUTURE_DATE_CUTOFF_YEARS);
+
+ if ($d > $cutoffDate)
+ {
+ $d = $cutoffDate;
+ $_adodb_last_date_call_failed = true;
+ }
+
+ if ($d < -12219321600) $d -= 86400*10; // if 15 Oct 1582 or earlier, gregorian correction
+
+ $_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
+ $_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
+
+ $d366 = $_day_power * 366;
+ $d365 = $_day_power * 365;
+
+ if ($d < 0) {
+
+ if (empty($YRS)) $YRS = array(
+ 1970 => 0,
+ 1960 => -315619200,
+ 1950 => -631152000,
+ 1940 => -946771200,
+ 1930 => -1262304000,
+ 1920 => -1577923200,
+ 1910 => -1893456000,
+ 1900 => -2208988800,
+ 1890 => -2524521600,
+ 1880 => -2840140800,
+ 1870 => -3155673600,
+ 1860 => -3471292800,
+ 1850 => -3786825600,
+ 1840 => -4102444800,
+ 1830 => -4417977600,
+ 1820 => -4733596800,
+ 1810 => -5049129600,
+ 1800 => -5364662400,
+ 1790 => -5680195200,
+ 1780 => -5995814400,
+ 1770 => -6311347200,
+ 1760 => -6626966400,
+ 1750 => -6942499200,
+ 1740 => -7258118400,
+ 1730 => -7573651200,
+ 1720 => -7889270400,
+ 1710 => -8204803200,
+ 1700 => -8520336000,
+ 1690 => -8835868800,
+ 1680 => -9151488000,
+ 1670 => -9467020800,
+ 1660 => -9782640000,
+ 1650 => -10098172800,
+ 1640 => -10413792000,
+ 1630 => -10729324800,
+ 1620 => -11044944000,
+ 1610 => -11360476800,
+ 1600 => -11676096000);
+
+ if ($is_gmt) $origd = $d;
+ // The valid range of a 32bit signed timestamp is typically from
+ // Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT
+ //
+
+ # old algorithm iterates through all years. new algorithm does it in
+ # 10 year blocks
+
+ /*
+ # old algo
+ for ($a = 1970 ; --$a >= 0;) {
+ $lastd = $d;
+
+ if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
+ else $d += $d365;
+
+ if ($d >= 0) {
+ $year = $a;
+ break;
+ }
+ }
+ */
+
+ $lastsecs = 0;
+ $lastyear = 1970;
+ foreach($YRS as $year => $secs) {
+ if ($d >= $secs) {
+ $a = $lastyear;
+ break;
+ }
+ $lastsecs = $secs;
+ $lastyear = $year;
+ }
+
+ $d -= $lastsecs;
+ if (!isset($a)) $a = $lastyear;
+
+ //echo ' yr=',$a,' ', $d,'.';
+
+ for (; --$a >= 0;) {
+ $lastd = $d;
+
+ if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
+ else $d += $d365;
+
+ if ($d >= 0) {
+ $year = $a;
+ break;
+ }
+ }
+ /**/
+
+ $secsInYear = 86400 * ($leaf ? 366 : 365) + $lastd;
+
+ $d = $lastd;
+ $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
+ for ($a = 13 ; --$a > 0;) {
+ $lastd = $d;
+ $d += $mtab[$a] * $_day_power;
+ if ($d >= 0) {
+ $month = $a;
+ $ndays = $mtab[$a];
+ break;
+ }
+ }
+
+ $d = $lastd;
+ $day = $ndays + ceil(($d+1) / ($_day_power));
+
+ $d += ($ndays - $day+1)* $_day_power;
+ $hour = floor($d/$_hour_power);
+
+ } else {
+ for ($a = 1970 ;; $a++) {
+ $lastd = $d;
+
+ if ($leaf = _adodb_is_leap_year($a)) $d -= $d366;
+ else $d -= $d365;
+ if ($d < 0) {
+ $year = $a;
+ break;
+ }
+ }
+ $secsInYear = $lastd;
+ $d = $lastd;
+ $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
+ for ($a = 1 ; $a <= 12; $a++) {
+ $lastd = $d;
+ $d -= $mtab[$a] * $_day_power;
+ if ($d < 0) {
+ $month = $a;
+ $ndays = $mtab[$a];
+ break;
+ }
+ }
+ $d = $lastd;
+ $day = ceil(($d+1) / $_day_power);
+ $d = $d - ($day-1) * $_day_power;
+ $hour = floor($d /$_hour_power);
+ }
+
+ $d -= $hour * $_hour_power;
+ $min = floor($d/$_min_power);
+ $secs = $d - $min * $_min_power;
+ if ($fast) {
+ return array(
+ 'seconds' => $secs,
+ 'minutes' => $min,
+ 'hours' => $hour,
+ 'mday' => $day,
+ 'mon' => $month,
+ 'year' => $year,
+ 'yday' => floor($secsInYear/$_day_power),
+ 'leap' => $leaf,
+ 'ndays' => $ndays
+ );
+ }
+
+
+ $dow = adodb_dow($year,$month,$day);
+
+ return array(
+ 'seconds' => $secs,
+ 'minutes' => $min,
+ 'hours' => $hour,
+ 'mday' => $day,
+ 'wday' => $dow,
+ 'mon' => $month,
+ 'year' => $year,
+ 'yday' => floor($secsInYear/$_day_power),
+ 'weekday' => gmdate('l',$_day_power*(3+$dow)),
+ 'month' => gmdate('F',mktime(0,0,0,$month,2,1971)),
+ 0 => $origd
+ );
+}
+/*
+ if ($isphp5)
+ $dates .= sprintf('%s%04d',($gmt<=0)?'+':'-',abs($gmt)/36);
+ else
+ $dates .= sprintf('%s%04d',($gmt<0)?'+':'-',abs($gmt)/36);
+ break;*/
+function adodb_tz_offset($gmt,$isphp5)
+{
+ $zhrs = abs($gmt)/3600;
+ $hrs = floor($zhrs);
+ if ($isphp5)
+ return sprintf('%s%02d%02d',($gmt<=0)?'+':'-',floor($zhrs),($zhrs-$hrs)*60);
+ else
+ return sprintf('%s%02d%02d',($gmt<0)?'+':'-',floor($zhrs),($zhrs-$hrs)*60);
+}
+
+
+function adodb_gmdate($fmt,$d=false)
+{
+ return adodb_date($fmt,$d,true);
+}
+
+// accepts unix timestamp and iso date format in $d
+function adodb_date2($fmt, $d=false, $is_gmt=false)
+{
+ if ($d !== false) {
+ if (!preg_match(
+ "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
+ ($d), $rr)) return adodb_date($fmt,false,$is_gmt);
+
+ if ($rr[1] <= 100 && $rr[2]<= 1) return adodb_date($fmt,false,$is_gmt);
+
+ // h-m-s-MM-DD-YY
+ if (!isset($rr[5])) $d = adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1],false,$is_gmt);
+ else $d = @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1],false,$is_gmt);
+ }
+
+ return adodb_date($fmt,$d,$is_gmt);
+}
+
+
+/**
+ Return formatted date based on timestamp $d
+*/
+function adodb_date($fmt,$d=false,$is_gmt=false)
+{
+static $daylight;
+global $ADODB_DATETIME_CLASS;
+static $jan1_1971;
+
+
+ if (!isset($daylight)) {
+ $daylight = function_exists('adodb_daylight_sv');
+ if (empty($jan1_1971)) $jan1_1971 = mktime(0,0,0,1,1,1971); // we only use date() when > 1970 as adodb_mktime() only uses mktime() when > 1970
+ }
+
+ if ($d === false) return ($is_gmt)? @gmdate($fmt): @date($fmt);
+ if (!defined('ADODB_TEST_DATES')) {
+ if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
+
+ if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= $jan1_1971) // if windows, must be +ve integer
+ return ($is_gmt)? @gmdate($fmt,$d): @date($fmt,$d);
+
+ }
+ }
+ $_day_power = 86400;
+
+ $arr = _adodb_getdate($d,true,$is_gmt);
+
+ if ($daylight) adodb_daylight_sv($arr, $is_gmt);
+
+ $year = $arr['year'];
+ $month = $arr['mon'];
+ $day = $arr['mday'];
+ $hour = $arr['hours'];
+ $min = $arr['minutes'];
+ $secs = $arr['seconds'];
+
+ $max = strlen($fmt);
+ $dates = '';
+
+ $isphp5 = PHP_VERSION >= 5;
+
+ /*
+ at this point, we have the following integer vars to manipulate:
+ $year, $month, $day, $hour, $min, $secs
+ */
+ for ($i=0; $i < $max; $i++) {
+ switch($fmt[$i]) {
+ case 'e':
+ $dates .= date('e');
+ break;
+ case 'T':
+ if ($ADODB_DATETIME_CLASS) {
+ $dt = new DateTime();
+ $dt->SetDate($year,$month,$day);
+ $dates .= $dt->Format('T');
+ } else
+ $dates .= date('T');
+ break;
+ // YEAR
+ case 'L': $dates .= $arr['leap'] ? '1' : '0'; break;
+ case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
+
+ // 4.3.11 uses '04 Jun 2004'
+ // 4.3.8 uses ' 4 Jun 2004'
+ $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))).', '
+ . ($day<10?'0'.$day:$day) . ' '.date('M',mktime(0,0,0,$month,2,1971)).' '.$year.' ';
+
+ if ($hour < 10) $dates .= '0'.$hour; else $dates .= $hour;
+
+ if ($min < 10) $dates .= ':0'.$min; else $dates .= ':'.$min;
+
+ if ($secs < 10) $dates .= ':0'.$secs; else $dates .= ':'.$secs;
+
+ $gmt = adodb_get_gmt_diff($year,$month,$day);
+
+ $dates .= ' '.adodb_tz_offset($gmt,$isphp5);
+ break;
+
+ case 'Y': $dates .= $year; break;
+ case 'y': $dates .= substr($year,strlen($year)-2,2); break;
+ // MONTH
+ case 'm': if ($month<10) $dates .= '0'.$month; else $dates .= $month; break;
+ case 'Q': $dates .= ($month+3)>>2; break;
+ case 'n': $dates .= $month; break;
+ case 'M': $dates .= date('M',mktime(0,0,0,$month,2,1971)); break;
+ case 'F': $dates .= date('F',mktime(0,0,0,$month,2,1971)); break;
+ // DAY
+ case 't': $dates .= $arr['ndays']; break;
+ case 'z': $dates .= $arr['yday']; break;
+ case 'w': $dates .= adodb_dow($year,$month,$day); break;
+ case 'l': $dates .= gmdate('l',$_day_power*(3+adodb_dow($year,$month,$day))); break;
+ case 'D': $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))); break;
+ case 'j': $dates .= $day; break;
+ case 'd': if ($day<10) $dates .= '0'.$day; else $dates .= $day; break;
+ case 'S':
+ $d10 = $day % 10;
+ if ($d10 == 1) $dates .= 'st';
+ else if ($d10 == 2 && $day != 12) $dates .= 'nd';
+ else if ($d10 == 3) $dates .= 'rd';
+ else $dates .= 'th';
+ break;
+
+ // HOUR
+ case 'Z':
+ $dates .= ($is_gmt) ? 0 : -adodb_get_gmt_diff($year,$month,$day); break;
+ case 'O':
+ $gmt = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$month,$day);
+
+ $dates .= adodb_tz_offset($gmt,$isphp5);
+ break;
+
+ case 'H':
+ if ($hour < 10) $dates .= '0'.$hour;
+ else $dates .= $hour;
+ break;
+ case 'h':
+ if ($hour > 12) $hh = $hour - 12;
+ else {
+ if ($hour == 0) $hh = '12';
+ else $hh = $hour;
+ }
+
+ if ($hh < 10) $dates .= '0'.$hh;
+ else $dates .= $hh;
+ break;
+
+ case 'G':
+ $dates .= $hour;
+ break;
+
+ case 'g':
+ if ($hour > 12) $hh = $hour - 12;
+ else {
+ if ($hour == 0) $hh = '12';
+ else $hh = $hour;
+ }
+ $dates .= $hh;
+ break;
+ // MINUTES
+ case 'i': if ($min < 10) $dates .= '0'.$min; else $dates .= $min; break;
+ // SECONDS
+ case 'U': $dates .= $d; break;
+ case 's': if ($secs < 10) $dates .= '0'.$secs; else $dates .= $secs; break;
+ // AM/PM
+ // Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
+ case 'a':
+ if ($hour>=12) $dates .= 'pm';
+ else $dates .= 'am';
+ break;
+ case 'A':
+ if ($hour>=12) $dates .= 'PM';
+ else $dates .= 'AM';
+ break;
+ default:
+ $dates .= $fmt[$i]; break;
+ // ESCAPE
+ case "\\":
+ $i++;
+ if ($i < $max) $dates .= $fmt[$i];
+ break;
+ }
+ }
+ return $dates;
+}
+
+/**
+ Returns a timestamp given a GMT/UTC time.
+ Note that $is_dst is not implemented and is ignored.
+*/
+function adodb_gmmktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false)
+{
+ return adodb_mktime($hr,$min,$sec,$mon,$day,$year,$is_dst,true);
+}
+
+/**
+ Return a timestamp given a local time. Originally by jackbbs.
+ Note that $is_dst is not implemented and is ignored.
+
+ Not a very fast algorithm - O(n) operation. Could be optimized to O(1).
+*/
+function adodb_mktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false,$is_gmt=false)
+{
+ if (!defined('ADODB_TEST_DATES')) {
+
+ if ($mon === false) {
+ return $is_gmt? @gmmktime($hr,$min,$sec): @mktime($hr,$min,$sec);
+ }
+
+ // for windows, we don't check 1970 because with timezone differences,
+ // 1 Jan 1970 could generate negative timestamp, which is illegal
+ $usephpfns = (1970 < $year && $year < 2038
+ || !defined('ADODB_NO_NEGATIVE_TS') && (1901 < $year && $year < 2038)
+ );
+
+
+ if ($usephpfns && ($year + $mon/12+$day/365.25+$hr/(24*365.25) >= 2038)) $usephpfns = false;
+
+ if ($usephpfns) {
+ return $is_gmt ?
+ @gmmktime($hr,$min,$sec,$mon,$day,$year):
+ @mktime($hr,$min,$sec,$mon,$day,$year);
+ }
+ }
+
+ $gmt_different = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$mon,$day);
+
+ /*
+ # disabled because some people place large values in $sec.
+ # however we need it for $mon because we use an array...
+ $hr = intval($hr);
+ $min = intval($min);
+ $sec = intval($sec);
+ */
+ $mon = intval($mon);
+ $day = intval($day);
+ $year = intval($year);
+
+
+ $year = adodb_year_digit_check($year);
+
+ if ($mon > 12) {
+ $y = floor(($mon-1)/ 12);
+ $year += $y;
+ $mon -= $y*12;
+ } else if ($mon < 1) {
+ $y = ceil((1-$mon) / 12);
+ $year -= $y;
+ $mon += $y*12;
+ }
+
+ $_day_power = 86400;
+ $_hour_power = 3600;
+ $_min_power = 60;
+
+ $_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
+ $_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
+
+ $_total_date = 0;
+ if ($year >= 1970) {
+ for ($a = 1970 ; $a <= $year; $a++) {
+ $leaf = _adodb_is_leap_year($a);
+ if ($leaf == true) {
+ $loop_table = $_month_table_leaf;
+ $_add_date = 366;
+ } else {
+ $loop_table = $_month_table_normal;
+ $_add_date = 365;
+ }
+ if ($a < $year) {
+ $_total_date += $_add_date;
+ } else {
+ for($b=1;$b<$mon;$b++) {
+ $_total_date += $loop_table[$b];
+ }
+ }
+ }
+ $_total_date +=$day-1;
+ $ret = $_total_date * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
+
+ } else {
+ for ($a = 1969 ; $a >= $year; $a--) {
+ $leaf = _adodb_is_leap_year($a);
+ if ($leaf == true) {
+ $loop_table = $_month_table_leaf;
+ $_add_date = 366;
+ } else {
+ $loop_table = $_month_table_normal;
+ $_add_date = 365;
+ }
+ if ($a > $year) { $_total_date += $_add_date;
+ } else {
+ for($b=12;$b>$mon;$b--) {
+ $_total_date += $loop_table[$b];
+ }
+ }
+ }
+ $_total_date += $loop_table[$mon] - $day;
+
+ $_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
+ $_day_time = $_day_power - $_day_time;
+ $ret = -( $_total_date * $_day_power + $_day_time - $gmt_different);
+ if ($ret < -12220185600) $ret += 10*86400; // if earlier than 5 Oct 1582 - gregorian correction
+ else if ($ret < -12219321600) $ret = -12219321600; // if in limbo, reset to 15 Oct 1582.
+ }
+ //print " dmy=$day/$mon/$year $hr:$min:$sec => " .$ret;
+ return $ret;
+}
+
+function adodb_gmstrftime($fmt, $ts=false)
+{
+ return adodb_strftime($fmt,$ts,true);
+}
+
+// hack - convert to adodb_date
+function adodb_strftime($fmt, $ts=false,$is_gmt=false)
+{
+global $ADODB_DATE_LOCALE;
+
+ if (!defined('ADODB_TEST_DATES')) {
+ if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
+ if (!defined('ADODB_NO_NEGATIVE_TS') || $ts >= 0) // if windows, must be +ve integer
+ return ($is_gmt)? @gmstrftime($fmt,$ts): @strftime($fmt,$ts);
+
+ }
+ }
+
+ if (empty($ADODB_DATE_LOCALE)) {
+ /*
+ $tstr = strtoupper(gmstrftime('%c',31366800)); // 30 Dec 1970, 1 am
+ $sep = substr($tstr,2,1);
+ $hasAM = strrpos($tstr,'M') !== false;
+ */
+ # see http://phplens.com/lens/lensforum/msgs.php?id=14865 for reasoning, and changelog for version 0.24
+ $dstr = gmstrftime('%x',31366800); // 30 Dec 1970, 1 am
+ $sep = substr($dstr,2,1);
+ $tstr = strtoupper(gmstrftime('%X',31366800)); // 30 Dec 1970, 1 am
+ $hasAM = strrpos($tstr,'M') !== false;
+
+ $ADODB_DATE_LOCALE = array();
+ $ADODB_DATE_LOCALE[] = strncmp($tstr,'30',2) == 0 ? 'd'.$sep.'m'.$sep.'y' : 'm'.$sep.'d'.$sep.'y';
+ $ADODB_DATE_LOCALE[] = ($hasAM) ? 'h:i:s a' : 'H:i:s';
+
+ }
+ $inpct = false;
+ $fmtdate = '';
+ for ($i=0,$max = strlen($fmt); $i < $max; $i++) {
+ $ch = $fmt[$i];
+ if ($ch == '%') {
+ if ($inpct) {
+ $fmtdate .= '%';
+ $inpct = false;
+ } else
+ $inpct = true;
+ } else if ($inpct) {
+
+ $inpct = false;
+ switch($ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'E':
+ case 'O':
+ /* ignore format modifiers */
+ $inpct = true;
+ break;
+
+ case 'a': $fmtdate .= 'D'; break;
+ case 'A': $fmtdate .= 'l'; break;
+ case 'h':
+ case 'b': $fmtdate .= 'M'; break;
+ case 'B': $fmtdate .= 'F'; break;
+ case 'c': $fmtdate .= $ADODB_DATE_LOCALE[0].$ADODB_DATE_LOCALE[1]; break;
+ case 'C': $fmtdate .= '\C?'; break; // century
+ case 'd': $fmtdate .= 'd'; break;
+ case 'D': $fmtdate .= 'm/d/y'; break;
+ case 'e': $fmtdate .= 'j'; break;
+ case 'g': $fmtdate .= '\g?'; break; //?
+ case 'G': $fmtdate .= '\G?'; break; //?
+ case 'H': $fmtdate .= 'H'; break;
+ case 'I': $fmtdate .= 'h'; break;
+ case 'j': $fmtdate .= '?z'; $parsej = true; break; // wrong as j=1-based, z=0-basd
+ case 'm': $fmtdate .= 'm'; break;
+ case 'M': $fmtdate .= 'i'; break;
+ case 'n': $fmtdate .= "\n"; break;
+ case 'p': $fmtdate .= 'a'; break;
+ case 'r': $fmtdate .= 'h:i:s a'; break;
+ case 'R': $fmtdate .= 'H:i:s'; break;
+ case 'S': $fmtdate .= 's'; break;
+ case 't': $fmtdate .= "\t"; break;
+ case 'T': $fmtdate .= 'H:i:s'; break;
+ case 'u': $fmtdate .= '?u'; $parseu = true; break; // wrong strftime=1-based, date=0-based
+ case 'U': $fmtdate .= '?U'; $parseU = true; break;// wrong strftime=1-based, date=0-based
+ case 'x': $fmtdate .= $ADODB_DATE_LOCALE[0]; break;
+ case 'X': $fmtdate .= $ADODB_DATE_LOCALE[1]; break;
+ case 'w': $fmtdate .= '?w'; $parseu = true; break; // wrong strftime=1-based, date=0-based
+ case 'W': $fmtdate .= '?W'; $parseU = true; break;// wrong strftime=1-based, date=0-based
+ case 'y': $fmtdate .= 'y'; break;
+ case 'Y': $fmtdate .= 'Y'; break;
+ case 'Z': $fmtdate .= 'T'; break;
+ }
+ } else if (('A' <= ($ch) && ($ch) <= 'Z' ) || ('a' <= ($ch) && ($ch) <= 'z' ))
+ $fmtdate .= "\\".$ch;
+ else
+ $fmtdate .= $ch;
+ }
+ //echo "fmt=",$fmtdate,"<br>";
+ if ($ts === false) $ts = time();
+ $ret = adodb_date($fmtdate, $ts, $is_gmt);
+ return $ret;
+}
+
+/**
+* Returns the status of the last date calculation and whether it exceeds
+* the limit of ADODB_FUTURE_DATE_CUTOFF_YEARS
+*
+* @return boolean
+*/
+function adodb_last_date_status()
+{
+ global $_adodb_last_date_call_failed;
+
+ return $_adodb_last_date_call_failed;
+}
diff --git a/vendor/adodb/adodb-php/adodb-xmlschema.inc.php b/vendor/adodb/adodb-php/adodb-xmlschema.inc.php
new file mode 100644
index 0000000..b53d4e2
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-xmlschema.inc.php
@@ -0,0 +1,2226 @@
+<?php
+// Copyright (c) 2004 ars Cognita Inc., all rights reserved
+/* ******************************************************************************
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+*******************************************************************************/
+/**
+ * xmlschema is a class that allows the user to quickly and easily
+ * build a database on any ADOdb-supported platform using a simple
+ * XML schema.
+ *
+ * Last Editor: $Author: jlim $
+ * @author Richard Tango-Lowy & Dan Cech
+ * @version $Revision: 1.12 $
+ *
+ * @package axmls
+ * @tutorial getting_started.pkg
+ */
+
+function _file_get_contents($file)
+{
+ if (function_exists('file_get_contents')) return file_get_contents($file);
+
+ $f = fopen($file,'r');
+ if (!$f) return '';
+ $t = '';
+
+ while ($s = fread($f,100000)) $t .= $s;
+ fclose($f);
+ return $t;
+}
+
+
+/**
+* Debug on or off
+*/
+if( !defined( 'XMLS_DEBUG' ) ) {
+ define( 'XMLS_DEBUG', FALSE );
+}
+
+/**
+* Default prefix key
+*/
+if( !defined( 'XMLS_PREFIX' ) ) {
+ define( 'XMLS_PREFIX', '%%P' );
+}
+
+/**
+* Maximum length allowed for object prefix
+*/
+if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
+ define( 'XMLS_PREFIX_MAXLEN', 10 );
+}
+
+/**
+* Execute SQL inline as it is generated
+*/
+if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
+ define( 'XMLS_EXECUTE_INLINE', FALSE );
+}
+
+/**
+* Continue SQL Execution if an error occurs?
+*/
+if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
+ define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
+}
+
+/**
+* Current Schema Version
+*/
+if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
+ define( 'XMLS_SCHEMA_VERSION', '0.2' );
+}
+
+/**
+* Default Schema Version. Used for Schemas without an explicit version set.
+*/
+if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
+ define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
+}
+
+/**
+* Default Schema Version. Used for Schemas without an explicit version set.
+*/
+if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
+ define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
+}
+
+/**
+* Include the main ADODB library
+*/
+if( !defined( '_ADODB_LAYER' ) ) {
+ require( 'adodb.inc.php' );
+ require( 'adodb-datadict.inc.php' );
+}
+
+/**
+* Abstract DB Object. This class provides basic methods for database objects, such
+* as tables and indexes.
+*
+* @package axmls
+* @access private
+*/
+class dbObject {
+
+ /**
+ * var object Parent
+ */
+ var $parent;
+
+ /**
+ * var string current element
+ */
+ var $currentElement;
+
+ /**
+ * NOP
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+
+ }
+
+ function create(&$xmls) {
+ return array();
+ }
+
+ /**
+ * Destroys the object
+ */
+ function destroy() {
+ }
+
+ /**
+ * Checks whether the specified RDBMS is supported by the current
+ * database object or its ranking ancestor.
+ *
+ * @param string $platform RDBMS platform name (from ADODB platform list).
+ * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
+ */
+ function supportedPlatform( $platform = NULL ) {
+ return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
+ }
+
+ /**
+ * Returns the prefix set by the ranking ancestor of the database object.
+ *
+ * @param string $name Prefix string.
+ * @return string Prefix.
+ */
+ function prefix( $name = '' ) {
+ return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
+ }
+
+ /**
+ * Extracts a field ID from the specified field.
+ *
+ * @param string $field Field.
+ * @return string Field ID.
+ */
+ function FieldID( $field ) {
+ return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
+ }
+}
+
+/**
+* Creates a table object in ADOdb's datadict format
+*
+* This class stores information about a database table. As charactaristics
+* of the table are loaded from the external source, methods and properties
+* of this class are used to build up the table description in ADOdb's
+* datadict format.
+*
+* @package axmls
+* @access private
+*/
+class dbTable extends dbObject {
+
+ /**
+ * @var string Table name
+ */
+ var $name;
+
+ /**
+ * @var array Field specifier: Meta-information about each field
+ */
+ var $fields = array();
+
+ /**
+ * @var array List of table indexes.
+ */
+ var $indexes = array();
+
+ /**
+ * @var array Table options: Table-level options
+ */
+ var $opts = array();
+
+ /**
+ * @var string Field index: Keeps track of which field is currently being processed
+ */
+ var $current_field;
+
+ /**
+ * @var boolean Mark table for destruction
+ * @access private
+ */
+ var $drop_table;
+
+ /**
+ * @var boolean Mark field for destruction (not yet implemented)
+ * @access private
+ */
+ var $drop_field = array();
+
+ /**
+ * Iniitializes a new table object.
+ *
+ * @param string $prefix DB Object prefix
+ * @param array $attributes Array of table attributes.
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ $this->name = $this->prefix($attributes['NAME']);
+ }
+
+ /**
+ * XML Callback to process start elements. Elements currently
+ * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'INDEX':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $index = $this->addIndex( $attributes );
+ xml_set_object( $parser, $index );
+ }
+ break;
+ case 'DATA':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $data = $this->addData( $attributes );
+ xml_set_object( $parser, $data );
+ }
+ break;
+ case 'DROP':
+ $this->drop();
+ break;
+ case 'FIELD':
+ // Add a field
+ $fieldName = $attributes['NAME'];
+ $fieldType = $attributes['TYPE'];
+ $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
+ $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
+
+ $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
+ break;
+ case 'KEY':
+ case 'NOTNULL':
+ case 'AUTOINCREMENT':
+ // Add a field option
+ $this->addFieldOpt( $this->current_field, $this->currentElement );
+ break;
+ case 'DEFAULT':
+ // Add a field option to the table object
+
+ // Work around ADOdb datadict issue that misinterprets empty strings.
+ if( $attributes['VALUE'] == '' ) {
+ $attributes['VALUE'] = " '' ";
+ }
+
+ $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
+ break;
+ case 'DEFDATE':
+ case 'DEFTIMESTAMP':
+ // Add a field option to the table object
+ $this->addFieldOpt( $this->current_field, $this->currentElement );
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Table constraint
+ case 'CONSTRAINT':
+ if( isset( $this->current_field ) ) {
+ $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
+ } else {
+ $this->addTableOpt( $cdata );
+ }
+ break;
+ // Table option
+ case 'OPT':
+ $this->addTableOpt( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'TABLE':
+ $this->parent->addSQL( $this->create( $this->parent ) );
+ xml_set_object( $parser, $this->parent );
+ $this->destroy();
+ break;
+ case 'FIELD':
+ unset($this->current_field);
+ break;
+
+ }
+ }
+
+ /**
+ * Adds an index to a table object
+ *
+ * @param array $attributes Index attributes
+ * @return object dbIndex object
+ */
+ function addIndex( $attributes ) {
+ $name = strtoupper( $attributes['NAME'] );
+ $this->indexes[$name] = new dbIndex( $this, $attributes );
+ return $this->indexes[$name];
+ }
+
+ /**
+ * Adds data to a table object
+ *
+ * @param array $attributes Data attributes
+ * @return object dbData object
+ */
+ function addData( $attributes ) {
+ if( !isset( $this->data ) ) {
+ $this->data = new dbData( $this, $attributes );
+ }
+ return $this->data;
+ }
+
+ /**
+ * Adds a field to a table object
+ *
+ * $name is the name of the table to which the field should be added.
+ * $type is an ADODB datadict field type. The following field types
+ * are supported as of ADODB 3.40:
+ * - C: varchar
+ * - X: CLOB (character large object) or largest varchar size
+ * if CLOB is not supported
+ * - C2: Multibyte varchar
+ * - X2: Multibyte CLOB
+ * - B: BLOB (binary large object)
+ * - D: Date (some databases do not support this, and we return a datetime type)
+ * - T: Datetime or Timestamp
+ * - L: Integer field suitable for storing booleans (0 or 1)
+ * - I: Integer (mapped to I4)
+ * - I1: 1-byte integer
+ * - I2: 2-byte integer
+ * - I4: 4-byte integer
+ * - I8: 8-byte integer
+ * - F: Floating point number
+ * - N: Numeric or decimal number
+ *
+ * @param string $name Name of the table to which the field will be added.
+ * @param string $type ADODB datadict field type.
+ * @param string $size Field size
+ * @param array $opts Field options array
+ * @return array Field specifier array
+ */
+ function addField( $name, $type, $size = NULL, $opts = NULL ) {
+ $field_id = $this->FieldID( $name );
+
+ // Set the field index so we know where we are
+ $this->current_field = $field_id;
+
+ // Set the field name (required)
+ $this->fields[$field_id]['NAME'] = $name;
+
+ // Set the field type (required)
+ $this->fields[$field_id]['TYPE'] = $type;
+
+ // Set the field size (optional)
+ if( isset( $size ) ) {
+ $this->fields[$field_id]['SIZE'] = $size;
+ }
+
+ // Set the field options
+ if( isset( $opts ) ) {
+ $this->fields[$field_id]['OPTS'][] = $opts;
+ }
+ }
+
+ /**
+ * Adds a field option to the current field specifier
+ *
+ * This method adds a field option allowed by the ADOdb datadict
+ * and appends it to the given field.
+ *
+ * @param string $field Field name
+ * @param string $opt ADOdb field option
+ * @param mixed $value Field option value
+ * @return array Field specifier array
+ */
+ function addFieldOpt( $field, $opt, $value = NULL ) {
+ if( !isset( $value ) ) {
+ $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
+ // Add the option and value
+ } else {
+ $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
+ }
+ }
+
+ /**
+ * Adds an option to the table
+ *
+ * This method takes a comma-separated list of table-level options
+ * and appends them to the table object.
+ *
+ * @param string $opt Table option
+ * @return array Options
+ */
+ function addTableOpt( $opt ) {
+ if(isset($this->currentPlatform)) {
+ $this->opts[$this->parent->db->databaseType] = $opt;
+ }
+ return $this->opts;
+ }
+
+
+ /**
+ * Generates the SQL that will create the table in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing table creation SQL
+ */
+ function create( &$xmls ) {
+ $sql = array();
+
+ // drop any existing indexes
+ if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
+ foreach( $legacy_indexes as $index => $index_details ) {
+ $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
+ }
+ }
+
+ // remove fields to be dropped from table object
+ foreach( $this->drop_field as $field ) {
+ unset( $this->fields[$field] );
+ }
+
+ // if table exists
+ if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
+ // drop table
+ if( $this->drop_table ) {
+ $sql[] = $xmls->dict->DropTableSQL( $this->name );
+
+ return $sql;
+ }
+
+ // drop any existing fields not in schema
+ foreach( $legacy_fields as $field_id => $field ) {
+ if( !isset( $this->fields[$field_id] ) ) {
+ $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
+ }
+ }
+ // if table doesn't exist
+ } else {
+ if( $this->drop_table ) {
+ return $sql;
+ }
+
+ $legacy_fields = array();
+ }
+
+ // Loop through the field specifier array, building the associative array for the field options
+ $fldarray = array();
+
+ foreach( $this->fields as $field_id => $finfo ) {
+ // Set an empty size if it isn't supplied
+ if( !isset( $finfo['SIZE'] ) ) {
+ $finfo['SIZE'] = '';
+ }
+
+ // Initialize the field array with the type and size
+ $fldarray[$field_id] = array(
+ 'NAME' => $finfo['NAME'],
+ 'TYPE' => $finfo['TYPE'],
+ 'SIZE' => $finfo['SIZE']
+ );
+
+ // Loop through the options array and add the field options.
+ if( isset( $finfo['OPTS'] ) ) {
+ foreach( $finfo['OPTS'] as $opt ) {
+ // Option has an argument.
+ if( is_array( $opt ) ) {
+ $key = key( $opt );
+ $value = $opt[key( $opt )];
+ @$fldarray[$field_id][$key] .= $value;
+ // Option doesn't have arguments
+ } else {
+ $fldarray[$field_id][$opt] = $opt;
+ }
+ }
+ }
+ }
+
+ if( empty( $legacy_fields ) ) {
+ // Create the new table
+ $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
+ logMsg( end( $sql ), 'Generated CreateTableSQL' );
+ } else {
+ // Upgrade an existing table
+ logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
+ switch( $xmls->upgrade ) {
+ // Use ChangeTableSQL
+ case 'ALTER':
+ logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
+ $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
+ break;
+ case 'REPLACE':
+ logMsg( 'Doing upgrade REPLACE (testing)' );
+ $sql[] = $xmls->dict->DropTableSQL( $this->name );
+ $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
+ break;
+ // ignore table
+ default:
+ return array();
+ }
+ }
+
+ foreach( $this->indexes as $index ) {
+ $sql[] = $index->create( $xmls );
+ }
+
+ if( isset( $this->data ) ) {
+ $sql[] = $this->data->create( $xmls );
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Marks a field or table for destruction
+ */
+ function drop() {
+ if( isset( $this->current_field ) ) {
+ // Drop the current field
+ logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
+ // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
+ $this->drop_field[$this->current_field] = $this->current_field;
+ } else {
+ // Drop the current table
+ logMsg( "Dropping table '{$this->name}'" );
+ // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
+ $this->drop_table = TRUE;
+ }
+ }
+}
+
+/**
+* Creates an index object in ADOdb's datadict format
+*
+* This class stores information about a database index. As charactaristics
+* of the index are loaded from the external source, methods and properties
+* of this class are used to build up the index description in ADOdb's
+* datadict format.
+*
+* @package axmls
+* @access private
+*/
+class dbIndex extends dbObject {
+
+ /**
+ * @var string Index name
+ */
+ var $name;
+
+ /**
+ * @var array Index options: Index-level options
+ */
+ var $opts = array();
+
+ /**
+ * @var array Indexed fields: Table columns included in this index
+ */
+ var $columns = array();
+
+ /**
+ * @var boolean Mark index for destruction
+ * @access private
+ */
+ var $drop = FALSE;
+
+ /**
+ * Initializes the new dbIndex object.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ *
+ * @internal
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+
+ $this->name = $this->prefix ($attributes['NAME']);
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * Processes XML opening tags.
+ * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'DROP':
+ $this->drop();
+ break;
+ case 'CLUSTERED':
+ case 'BITMAP':
+ case 'UNIQUE':
+ case 'FULLTEXT':
+ case 'HASH':
+ // Add index Option
+ $this->addIndexOpt( $this->currentElement );
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * Processes XML cdata.
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Index field name
+ case 'COL':
+ $this->addField( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'INDEX':
+ xml_set_object( $parser, $this->parent );
+ break;
+ }
+ }
+
+ /**
+ * Adds a field to the index
+ *
+ * @param string $name Field name
+ * @return string Field list
+ */
+ function addField( $name ) {
+ $this->columns[$this->FieldID( $name )] = $name;
+
+ // Return the field list
+ return $this->columns;
+ }
+
+ /**
+ * Adds options to the index
+ *
+ * @param string $opt Comma-separated list of index options.
+ * @return string Option list
+ */
+ function addIndexOpt( $opt ) {
+ $this->opts[] = $opt;
+
+ // Return the options list
+ return $this->opts;
+ }
+
+ /**
+ * Generates the SQL that will create the index in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing index creation SQL
+ */
+ function create( &$xmls ) {
+ if( $this->drop ) {
+ return NULL;
+ }
+
+ // eliminate any columns that aren't in the table
+ foreach( $this->columns as $id => $col ) {
+ if( !isset( $this->parent->fields[$id] ) ) {
+ unset( $this->columns[$id] );
+ }
+ }
+
+ return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
+ }
+
+ /**
+ * Marks an index for destruction
+ */
+ function drop() {
+ $this->drop = TRUE;
+ }
+}
+
+/**
+* Creates a data object in ADOdb's datadict format
+*
+* This class stores information about table data.
+*
+* @package axmls
+* @access private
+*/
+class dbData extends dbObject {
+
+ var $data = array();
+
+ var $row;
+
+ /**
+ * Initializes the new dbIndex object.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ *
+ * @internal
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * Processes XML opening tags.
+ * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'ROW':
+ $this->row = count( $this->data );
+ $this->data[$this->row] = array();
+ break;
+ case 'F':
+ $this->addField($attributes);
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * Processes XML cdata.
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Index field name
+ case 'F':
+ $this->addData( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'DATA':
+ xml_set_object( $parser, $this->parent );
+ break;
+ }
+ }
+
+ /**
+ * Adds a field to the index
+ *
+ * @param string $name Field name
+ * @return string Field list
+ */
+ function addField( $attributes ) {
+ if( isset( $attributes['NAME'] ) ) {
+ $name = $attributes['NAME'];
+ } else {
+ $name = count($this->data[$this->row]);
+ }
+
+ // Set the field index so we know where we are
+ $this->current_field = $this->FieldID( $name );
+ }
+
+ /**
+ * Adds options to the index
+ *
+ * @param string $opt Comma-separated list of index options.
+ * @return string Option list
+ */
+ function addData( $cdata ) {
+ if( !isset( $this->data[$this->row] ) ) {
+ $this->data[$this->row] = array();
+ }
+
+ if( !isset( $this->data[$this->row][$this->current_field] ) ) {
+ $this->data[$this->row][$this->current_field] = '';
+ }
+
+ $this->data[$this->row][$this->current_field] .= $cdata;
+ }
+
+ /**
+ * Generates the SQL that will create the index in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing index creation SQL
+ */
+ function create( &$xmls ) {
+ $table = $xmls->dict->TableName($this->parent->name);
+ $table_field_count = count($this->parent->fields);
+ $sql = array();
+
+ // eliminate any columns that aren't in the table
+ foreach( $this->data as $row ) {
+ $table_fields = $this->parent->fields;
+ $fields = array();
+
+ foreach( $row as $field_id => $field_data ) {
+ if( !array_key_exists( $field_id, $table_fields ) ) {
+ if( is_numeric( $field_id ) ) {
+ $field_id = reset( array_keys( $table_fields ) );
+ } else {
+ continue;
+ }
+ }
+
+ $name = $table_fields[$field_id]['NAME'];
+
+ switch( $table_fields[$field_id]['TYPE'] ) {
+ case 'C':
+ case 'C2':
+ case 'X':
+ case 'X2':
+ $fields[$name] = $xmls->db->qstr( $field_data );
+ break;
+ case 'I':
+ case 'I1':
+ case 'I2':
+ case 'I4':
+ case 'I8':
+ $fields[$name] = intval($field_data);
+ break;
+ default:
+ $fields[$name] = $field_data;
+ }
+
+ unset($table_fields[$field_id]);
+ }
+
+ // check that at least 1 column is specified
+ if( empty( $fields ) ) {
+ continue;
+ }
+
+ // check that no required columns are missing
+ if( count( $fields ) < $table_field_count ) {
+ foreach( $table_fields as $field ) {
+ if (isset( $field['OPTS'] ))
+ if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
+ continue(2);
+ }
+ }
+ }
+
+ $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
+ }
+
+ return $sql;
+ }
+}
+
+/**
+* Creates the SQL to execute a list of provided SQL queries
+*
+* @package axmls
+* @access private
+*/
+class dbQuerySet extends dbObject {
+
+ /**
+ * @var array List of SQL queries
+ */
+ var $queries = array();
+
+ /**
+ * @var string String used to build of a query line by line
+ */
+ var $query;
+
+ /**
+ * @var string Query prefix key
+ */
+ var $prefixKey = '';
+
+ /**
+ * @var boolean Auto prefix enable (TRUE)
+ */
+ var $prefixMethod = 'AUTO';
+
+ /**
+ * Initializes the query set.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+
+ // Overrides the manual prefix key
+ if( isset( $attributes['KEY'] ) ) {
+ $this->prefixKey = $attributes['KEY'];
+ }
+
+ $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
+
+ // Enables or disables automatic prefix prepending
+ switch( $prefixMethod ) {
+ case 'AUTO':
+ $this->prefixMethod = 'AUTO';
+ break;
+ case 'MANUAL':
+ $this->prefixMethod = 'MANUAL';
+ break;
+ case 'NONE':
+ $this->prefixMethod = 'NONE';
+ break;
+ }
+ }
+
+ /**
+ * XML Callback to process start elements. Elements currently
+ * processed are: QUERY.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'QUERY':
+ // Create a new query in a SQL queryset.
+ // Ignore this query set if a platform is specified and it's different than the
+ // current connection platform.
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $this->newQuery();
+ } else {
+ $this->discardQuery();
+ }
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Line of queryset SQL data
+ case 'QUERY':
+ $this->buildQuery( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'QUERY':
+ // Add the finished query to the open query set.
+ $this->addQuery();
+ break;
+ case 'SQL':
+ $this->parent->addSQL( $this->create( $this->parent ) );
+ xml_set_object( $parser, $this->parent );
+ $this->destroy();
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * Re-initializes the query.
+ *
+ * @return boolean TRUE
+ */
+ function newQuery() {
+ $this->query = '';
+
+ return TRUE;
+ }
+
+ /**
+ * Discards the existing query.
+ *
+ * @return boolean TRUE
+ */
+ function discardQuery() {
+ unset( $this->query );
+
+ return TRUE;
+ }
+
+ /**
+ * Appends a line to a query that is being built line by line
+ *
+ * @param string $data Line of SQL data or NULL to initialize a new query
+ * @return string SQL query string.
+ */
+ function buildQuery( $sql = NULL ) {
+ if( !isset( $this->query ) OR empty( $sql ) ) {
+ return FALSE;
+ }
+
+ $this->query .= $sql;
+
+ return $this->query;
+ }
+
+ /**
+ * Adds a completed query to the query list
+ *
+ * @return string SQL of added query
+ */
+ function addQuery() {
+ if( !isset( $this->query ) ) {
+ return FALSE;
+ }
+
+ $this->queries[] = $return = trim($this->query);
+
+ unset( $this->query );
+
+ return $return;
+ }
+
+ /**
+ * Creates and returns the current query set
+ *
+ * @param object $xmls adoSchema object
+ * @return array Query set
+ */
+ function create( &$xmls ) {
+ foreach( $this->queries as $id => $query ) {
+ switch( $this->prefixMethod ) {
+ case 'AUTO':
+ // Enable auto prefix replacement
+
+ // Process object prefix.
+ // Evaluate SQL statements to prepend prefix to objects
+ $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+ $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+ $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+
+ // SELECT statements aren't working yet
+ #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
+
+ case 'MANUAL':
+ // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
+ // If prefixKey is not set, we use the default constant XMLS_PREFIX
+ if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
+ // Enable prefix override
+ $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
+ } else {
+ // Use default replacement
+ $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
+ }
+ }
+
+ $this->queries[$id] = trim( $query );
+ }
+
+ // Return the query set array
+ return $this->queries;
+ }
+
+ /**
+ * Rebuilds the query with the prefix attached to any objects
+ *
+ * @param string $regex Regex used to add prefix
+ * @param string $query SQL query string
+ * @param string $prefix Prefix to be appended to tables, indices, etc.
+ * @return string Prefixed SQL query string.
+ */
+ function prefixQuery( $regex, $query, $prefix = NULL ) {
+ if( !isset( $prefix ) ) {
+ return $query;
+ }
+
+ if( preg_match( $regex, $query, $match ) ) {
+ $preamble = $match[1];
+ $postamble = $match[5];
+ $objectList = explode( ',', $match[3] );
+ // $prefix = $prefix . '_';
+
+ $prefixedList = '';
+
+ foreach( $objectList as $object ) {
+ if( $prefixedList !== '' ) {
+ $prefixedList .= ', ';
+ }
+
+ $prefixedList .= $prefix . trim( $object );
+ }
+
+ $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
+ }
+
+ return $query;
+ }
+}
+
+/**
+* Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
+*
+* This class is used to load and parse the XML file, to create an array of SQL statements
+* that can be used to build a database, and to build the database using the SQL array.
+*
+* @tutorial getting_started.pkg
+*
+* @author Richard Tango-Lowy & Dan Cech
+* @version $Revision: 1.12 $
+*
+* @package axmls
+*/
+class adoSchema {
+
+ /**
+ * @var array Array containing SQL queries to generate all objects
+ * @access private
+ */
+ var $sqlArray;
+
+ /**
+ * @var object ADOdb connection object
+ * @access private
+ */
+ var $db;
+
+ /**
+ * @var object ADOdb Data Dictionary
+ * @access private
+ */
+ var $dict;
+
+ /**
+ * @var string Current XML element
+ * @access private
+ */
+ var $currentElement = '';
+
+ /**
+ * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
+ * @access private
+ */
+ var $upgrade = '';
+
+ /**
+ * @var string Optional object prefix
+ * @access private
+ */
+ var $objectPrefix = '';
+
+ /**
+ * @var long Original Magic Quotes Runtime value
+ * @access private
+ */
+ var $mgq;
+
+ /**
+ * @var long System debug
+ * @access private
+ */
+ var $debug;
+
+ /**
+ * @var string Regular expression to find schema version
+ * @access private
+ */
+ var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
+
+ /**
+ * @var string Current schema version
+ * @access private
+ */
+ var $schemaVersion;
+
+ /**
+ * @var int Success of last Schema execution
+ */
+ var $success;
+
+ /**
+ * @var bool Execute SQL inline as it is generated
+ */
+ var $executeInline;
+
+ /**
+ * @var bool Continue SQL execution if errors occur
+ */
+ var $continueOnError;
+
+ /**
+ * Creates an adoSchema object
+ *
+ * Creating an adoSchema object is the first step in processing an XML schema.
+ * The only parameter is an ADOdb database connection object, which must already
+ * have been created.
+ *
+ * @param object $db ADOdb database connection object.
+ */
+ function __construct( $db ) {
+ // Initialize the environment
+ $this->mgq = get_magic_quotes_runtime();
+ if ($this->mgq !== false) {
+ ini_set('magic_quotes_runtime', 0);
+ }
+
+ $this->db = $db;
+ $this->debug = $this->db->debug;
+ $this->dict = NewDataDictionary( $this->db );
+ $this->sqlArray = array();
+ $this->schemaVersion = XMLS_SCHEMA_VERSION;
+ $this->executeInline( XMLS_EXECUTE_INLINE );
+ $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
+ $this->setUpgradeMethod();
+ }
+
+ /**
+ * Sets the method to be used for upgrading an existing database
+ *
+ * Use this method to specify how existing database objects should be upgraded.
+ * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
+ * alter each database object directly, REPLACE attempts to rebuild each object
+ * from scratch, BEST attempts to determine the best upgrade method for each
+ * object, and NONE disables upgrading.
+ *
+ * This method is not yet used by AXMLS, but exists for backward compatibility.
+ * The ALTER method is automatically assumed when the adoSchema object is
+ * instantiated; other upgrade methods are not currently supported.
+ *
+ * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
+ * @returns string Upgrade method used
+ */
+ function SetUpgradeMethod( $method = '' ) {
+ if( !is_string( $method ) ) {
+ return FALSE;
+ }
+
+ $method = strtoupper( $method );
+
+ // Handle the upgrade methods
+ switch( $method ) {
+ case 'ALTER':
+ $this->upgrade = $method;
+ break;
+ case 'REPLACE':
+ $this->upgrade = $method;
+ break;
+ case 'BEST':
+ $this->upgrade = 'ALTER';
+ break;
+ case 'NONE':
+ $this->upgrade = 'NONE';
+ break;
+ default:
+ // Use default if no legitimate method is passed.
+ $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
+ }
+
+ return $this->upgrade;
+ }
+
+ /**
+ * Enables/disables inline SQL execution.
+ *
+ * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
+ * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
+ * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
+ * to apply the schema to the database.
+ *
+ * @param bool $mode execute
+ * @return bool current execution mode
+ *
+ * @see ParseSchema(), ExecuteSchema()
+ */
+ function ExecuteInline( $mode = NULL ) {
+ if( is_bool( $mode ) ) {
+ $this->executeInline = $mode;
+ }
+
+ return $this->executeInline;
+ }
+
+ /**
+ * Enables/disables SQL continue on error.
+ *
+ * Call this method to enable or disable continuation of SQL execution if an error occurs.
+ * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
+ * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
+ * of the schema will continue.
+ *
+ * @param bool $mode execute
+ * @return bool current continueOnError mode
+ *
+ * @see addSQL(), ExecuteSchema()
+ */
+ function ContinueOnError( $mode = NULL ) {
+ if( is_bool( $mode ) ) {
+ $this->continueOnError = $mode;
+ }
+
+ return $this->continueOnError;
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to SQL.
+ *
+ * Call this method to load the specified schema (see the DTD for the proper format) from
+ * the filesystem and generate the SQL necessary to create the database described.
+ * @see ParseSchemaString()
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute
+ */
+ function ParseSchema( $filename, $returnSchema = FALSE ) {
+ return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to SQL.
+ *
+ * Call this method to load the specified schema from a file (see the DTD for the proper format)
+ * and generate the SQL necessary to create the database described by the schema.
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ *
+ * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
+ * @see ParseSchema(), ParseSchemaString()
+ */
+ function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
+ // Open the file
+ if( !($fp = fopen( $filename, 'r' )) ) {
+ // die( 'Unable to open file' );
+ return FALSE;
+ }
+
+ // do version detection here
+ if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
+ return FALSE;
+ }
+
+ if ( $returnSchema )
+ {
+ $xmlstring = '';
+ while( $data = fread( $fp, 100000 ) ) {
+ $xmlstring .= $data;
+ }
+ return $xmlstring;
+ }
+
+ $this->success = 2;
+
+ $xmlParser = $this->create_parser();
+
+ // Process the file
+ while( $data = fread( $fp, 4096 ) ) {
+ if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
+ die( sprintf(
+ "XML error: %s at line %d",
+ xml_error_string( xml_get_error_code( $xmlParser) ),
+ xml_get_current_line_number( $xmlParser)
+ ) );
+ }
+ }
+
+ xml_parser_free( $xmlParser );
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Converts an XML schema string to SQL.
+ *
+ * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+ * and generate the SQL necessary to create the database described by the schema.
+ * @see ParseSchema()
+ *
+ * @param string $xmlstring XML schema string.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ */
+ function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
+ if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
+ return FALSE;
+ }
+
+ // do version detection here
+ if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
+ return FALSE;
+ }
+
+ if ( $returnSchema )
+ {
+ return $xmlstring;
+ }
+
+ $this->success = 2;
+
+ $xmlParser = $this->create_parser();
+
+ if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
+ die( sprintf(
+ "XML error: %s at line %d",
+ xml_error_string( xml_get_error_code( $xmlParser) ),
+ xml_get_current_line_number( $xmlParser)
+ ) );
+ }
+
+ xml_parser_free( $xmlParser );
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to uninstallation SQL.
+ *
+ * Call this method to load the specified schema (see the DTD for the proper format) from
+ * the filesystem and generate the SQL necessary to remove the database described.
+ * @see RemoveSchemaString()
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute
+ */
+ function RemoveSchema( $filename, $returnSchema = FALSE ) {
+ return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
+ }
+
+ /**
+ * Converts an XML schema string to uninstallation SQL.
+ *
+ * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+ * and generate the SQL necessary to uninstall the database described by the schema.
+ * @see RemoveSchema()
+ *
+ * @param string $schema XML schema string.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ */
+ function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
+ return FALSE;
+ }
+
+ return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
+ }
+
+ /**
+ * Applies the current XML schema to the database (post execution).
+ *
+ * Call this method to apply the current schema (generally created by calling
+ * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
+ * and executing other SQL specified in the schema) after parsing.
+ * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
+ *
+ * @param array $sqlArray Array of SQL statements that will be applied rather than
+ * the current schema.
+ * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
+ * @returns integer 0 if failure, 1 if errors, 2 if successful.
+ */
+ function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) {
+ if( !is_bool( $continueOnErr ) ) {
+ $continueOnErr = $this->ContinueOnError();
+ }
+
+ if( !isset( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+
+ if( !is_array( $sqlArray ) ) {
+ $this->success = 0;
+ } else {
+ $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
+ }
+
+ return $this->success;
+ }
+
+ /**
+ * Returns the current SQL array.
+ *
+ * Call this method to fetch the array of SQL queries resulting from
+ * ParseSchema() or ParseSchemaString().
+ *
+ * @param string $format Format: HTML, TEXT, or NONE (PHP array)
+ * @return array Array of SQL statements or FALSE if an error occurs
+ */
+ function PrintSQL( $format = 'NONE' ) {
+ $sqlArray = null;
+ return $this->getSQL( $format, $sqlArray );
+ }
+
+ /**
+ * Saves the current SQL array to the local filesystem as a list of SQL queries.
+ *
+ * Call this method to save the array of SQL queries (generally resulting from a
+ * parsed XML schema) to the filesystem.
+ *
+ * @param string $filename Path and name where the file should be saved.
+ * @return boolean TRUE if save is successful, else FALSE.
+ */
+ function SaveSQL( $filename = './schema.sql' ) {
+
+ if( !isset( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+ if( !isset( $sqlArray ) ) {
+ return FALSE;
+ }
+
+ $fp = fopen( $filename, "w" );
+
+ foreach( $sqlArray as $key => $query ) {
+ fwrite( $fp, $query . ";\n" );
+ }
+ fclose( $fp );
+ }
+
+ /**
+ * Create an xml parser
+ *
+ * @return object PHP XML parser object
+ *
+ * @access private
+ */
+ function create_parser() {
+ // Create the parser
+ $xmlParser = xml_parser_create();
+ xml_set_object( $xmlParser, $this );
+
+ // Initialize the XML callback functions
+ xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
+ xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
+
+ return $xmlParser;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ switch( strtoupper( $tag ) ) {
+ case 'TABLE':
+ $this->obj = new dbTable( $this, $attributes );
+ xml_set_object( $parser, $this->obj );
+ break;
+ case 'SQL':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $this->obj = new dbQuerySet( $this, $attributes );
+ xml_set_object( $parser, $this->obj );
+ }
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ * @internal
+ */
+ function _tag_close( &$parser, $tag ) {
+
+ }
+
+ /**
+ * Converts an XML schema string to the specified DTD version.
+ *
+ * Call this method to convert a string containing an XML schema to a different AXMLS
+ * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+ * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+ * parameter is specified, the schema will be converted to the current DTD version.
+ * If the newFile parameter is provided, the converted schema will be written to the specified
+ * file.
+ * @see ConvertSchemaFile()
+ *
+ * @param string $schema String containing XML schema that will be converted.
+ * @param string $newVersion DTD version to convert to.
+ * @param string $newFile File name of (converted) output file.
+ * @return string Converted XML schema or FALSE if an error occurs.
+ */
+ function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
+ return FALSE;
+ }
+
+ if( !isset ($newVersion) ) {
+ $newVersion = $this->schemaVersion;
+ }
+
+ if( $version == $newVersion ) {
+ $result = $schema;
+ } else {
+ $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
+ }
+
+ if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
+ fwrite( $fp, $result );
+ fclose( $fp );
+ }
+
+ return $result;
+ }
+
+ // compat for pre-4.3 - jlim
+ function _file_get_contents($path)
+ {
+ if (function_exists('file_get_contents')) return file_get_contents($path);
+ return join('',file($path));
+ }
+
+ /**
+ * Converts an XML schema file to the specified DTD version.
+ *
+ * Call this method to convert the specified XML schema file to a different AXMLS
+ * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+ * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+ * parameter is specified, the schema will be converted to the current DTD version.
+ * If the newFile parameter is provided, the converted schema will be written to the specified
+ * file.
+ * @see ConvertSchemaString()
+ *
+ * @param string $filename Name of XML schema file that will be converted.
+ * @param string $newVersion DTD version to convert to.
+ * @param string $newFile File name of (converted) output file.
+ * @return string Converted XML schema or FALSE if an error occurs.
+ */
+ function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
+ return FALSE;
+ }
+
+ if( !isset ($newVersion) ) {
+ $newVersion = $this->schemaVersion;
+ }
+
+ if( $version == $newVersion ) {
+ $result = _file_get_contents( $filename );
+
+ // remove unicode BOM if present
+ if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
+ $result = substr( $result, 3 );
+ }
+ } else {
+ $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
+ }
+
+ if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
+ fwrite( $fp, $result );
+ fclose( $fp );
+ }
+
+ return $result;
+ }
+
+ function TransformSchema( $schema, $xsl, $schematype='string' )
+ {
+ // Fail if XSLT extension is not available
+ if( ! function_exists( 'xslt_create' ) ) {
+ return FALSE;
+ }
+
+ $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
+
+ // look for xsl
+ if( !is_readable( $xsl_file ) ) {
+ return FALSE;
+ }
+
+ switch( $schematype )
+ {
+ case 'file':
+ if( !is_readable( $schema ) ) {
+ return FALSE;
+ }
+
+ $schema = _file_get_contents( $schema );
+ break;
+ case 'string':
+ default:
+ if( !is_string( $schema ) ) {
+ return FALSE;
+ }
+ }
+
+ $arguments = array (
+ '/_xml' => $schema,
+ '/_xsl' => _file_get_contents( $xsl_file )
+ );
+
+ // create an XSLT processor
+ $xh = xslt_create ();
+
+ // set error handler
+ xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
+
+ // process the schema
+ $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
+
+ xslt_free ($xh);
+
+ return $result;
+ }
+
+ /**
+ * Processes XSLT transformation errors
+ *
+ * @param object $parser XML parser object
+ * @param integer $errno Error number
+ * @param integer $level Error level
+ * @param array $fields Error information fields
+ *
+ * @access private
+ */
+ function xslt_error_handler( $parser, $errno, $level, $fields ) {
+ if( is_array( $fields ) ) {
+ $msg = array(
+ 'Message Type' => ucfirst( $fields['msgtype'] ),
+ 'Message Code' => $fields['code'],
+ 'Message' => $fields['msg'],
+ 'Error Number' => $errno,
+ 'Level' => $level
+ );
+
+ switch( $fields['URI'] ) {
+ case 'arg:/_xml':
+ $msg['Input'] = 'XML';
+ break;
+ case 'arg:/_xsl':
+ $msg['Input'] = 'XSL';
+ break;
+ default:
+ $msg['Input'] = $fields['URI'];
+ }
+
+ $msg['Line'] = $fields['line'];
+ } else {
+ $msg = array(
+ 'Message Type' => 'Error',
+ 'Error Number' => $errno,
+ 'Level' => $level,
+ 'Fields' => var_export( $fields, TRUE )
+ );
+ }
+
+ $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
+ . '<table>' . "\n";
+
+ foreach( $msg as $label => $details ) {
+ $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
+ }
+
+ $error_details .= '</table>';
+
+ trigger_error( $error_details, E_USER_ERROR );
+ }
+
+ /**
+ * Returns the AXMLS Schema Version of the requested XML schema file.
+ *
+ * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
+ * @see SchemaStringVersion()
+ *
+ * @param string $filename AXMLS schema file
+ * @return string Schema version number or FALSE on error
+ */
+ function SchemaFileVersion( $filename ) {
+ // Open the file
+ if( !($fp = fopen( $filename, 'r' )) ) {
+ // die( 'Unable to open file' );
+ return FALSE;
+ }
+
+ // Process the file
+ while( $data = fread( $fp, 4096 ) ) {
+ if( preg_match( $this->versionRegex, $data, $matches ) ) {
+ return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
+ }
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Returns the AXMLS Schema Version of the provided XML schema string.
+ *
+ * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
+ * @see SchemaFileVersion()
+ *
+ * @param string $xmlstring XML schema string
+ * @return string Schema version number or FALSE on error
+ */
+ function SchemaStringVersion( $xmlstring ) {
+ if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
+ return FALSE;
+ }
+
+ if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
+ return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Extracts an XML schema from an existing database.
+ *
+ * Call this method to create an XML schema string from an existing database.
+ * If the data parameter is set to TRUE, AXMLS will include the data from the database
+ * in the schema.
+ *
+ * @param boolean $data Include data in schema dump
+ * @return string Generated XML schema
+ */
+ function ExtractSchema( $data = FALSE ) {
+ $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
+
+ $schema = '<?xml version="1.0"?>' . "\n"
+ . '<schema version="' . $this->schemaVersion . '">' . "\n";
+
+ if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
+ foreach( $tables as $table ) {
+ $schema .= ' <table name="' . $table . '">' . "\n";
+
+ // grab details from database
+ $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' );
+ $fields = $this->db->MetaColumns( $table );
+ $indexes = $this->db->MetaIndexes( $table );
+
+ if( is_array( $fields ) ) {
+ foreach( $fields as $details ) {
+ $extra = '';
+ $content = array();
+
+ if( $details->max_length > 0 ) {
+ $extra .= ' size="' . $details->max_length . '"';
+ }
+
+ if( $details->primary_key ) {
+ $content[] = '<KEY/>';
+ } elseif( $details->not_null ) {
+ $content[] = '<NOTNULL/>';
+ }
+
+ if( $details->has_default ) {
+ $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
+ }
+
+ if( $details->auto_increment ) {
+ $content[] = '<AUTOINCREMENT/>';
+ }
+
+ // this stops the creation of 'R' columns,
+ // AUTOINCREMENT is used to create auto columns
+ $details->primary_key = 0;
+ $type = $rs->MetaType( $details );
+
+ $schema .= ' <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
+
+ if( !empty( $content ) ) {
+ $schema .= "\n " . implode( "\n ", $content ) . "\n ";
+ }
+
+ $schema .= '</field>' . "\n";
+ }
+ }
+
+ if( is_array( $indexes ) ) {
+ foreach( $indexes as $index => $details ) {
+ $schema .= ' <index name="' . $index . '">' . "\n";
+
+ if( $details['unique'] ) {
+ $schema .= ' <UNIQUE/>' . "\n";
+ }
+
+ foreach( $details['columns'] as $column ) {
+ $schema .= ' <col>' . $column . '</col>' . "\n";
+ }
+
+ $schema .= ' </index>' . "\n";
+ }
+ }
+
+ if( $data ) {
+ $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
+
+ if( is_object( $rs ) ) {
+ $schema .= ' <data>' . "\n";
+
+ while( $row = $rs->FetchRow() ) {
+ foreach( $row as $key => $val ) {
+ $row[$key] = htmlentities($val);
+ }
+
+ $schema .= ' <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
+ }
+
+ $schema .= ' </data>' . "\n";
+ }
+ }
+
+ $schema .= ' </table>' . "\n";
+ }
+ }
+
+ $this->db->SetFetchMode( $old_mode );
+
+ $schema .= '</schema>';
+ return $schema;
+ }
+
+ /**
+ * Sets a prefix for database objects
+ *
+ * Call this method to set a standard prefix that will be prepended to all database tables
+ * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
+ *
+ * @param string $prefix Prefix that will be prepended.
+ * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
+ * @return boolean TRUE if successful, else FALSE
+ */
+ function SetPrefix( $prefix = '', $underscore = TRUE ) {
+ switch( TRUE ) {
+ // clear prefix
+ case empty( $prefix ):
+ logMsg( 'Cleared prefix' );
+ $this->objectPrefix = '';
+ return TRUE;
+ // prefix too long
+ case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
+ // prefix contains invalid characters
+ case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
+ logMsg( 'Invalid prefix: ' . $prefix );
+ return FALSE;
+ }
+
+ if( $underscore AND substr( $prefix, -1 ) != '_' ) {
+ $prefix .= '_';
+ }
+
+ // prefix valid
+ logMsg( 'Set prefix: ' . $prefix );
+ $this->objectPrefix = $prefix;
+ return TRUE;
+ }
+
+ /**
+ * Returns an object name with the current prefix prepended.
+ *
+ * @param string $name Name
+ * @return string Prefixed name
+ *
+ * @access private
+ */
+ function prefix( $name = '' ) {
+ // if prefix is set
+ if( !empty( $this->objectPrefix ) ) {
+ // Prepend the object prefix to the table name
+ // prepend after quote if used
+ return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
+ }
+
+ // No prefix set. Use name provided.
+ return $name;
+ }
+
+ /**
+ * Checks if element references a specific platform
+ *
+ * @param string $platform Requested platform
+ * @returns boolean TRUE if platform check succeeds
+ *
+ * @access private
+ */
+ function supportedPlatform( $platform = NULL ) {
+ $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
+
+ if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
+ logMsg( "Platform $platform is supported" );
+ return TRUE;
+ } else {
+ logMsg( "Platform $platform is NOT supported" );
+ return FALSE;
+ }
+ }
+
+ /**
+ * Clears the array of generated SQL.
+ *
+ * @access private
+ */
+ function clearSQL() {
+ $this->sqlArray = array();
+ }
+
+ /**
+ * Adds SQL into the SQL array.
+ *
+ * @param mixed $sql SQL to Add
+ * @return boolean TRUE if successful, else FALSE.
+ *
+ * @access private
+ */
+ function addSQL( $sql = NULL ) {
+ if( is_array( $sql ) ) {
+ foreach( $sql as $line ) {
+ $this->addSQL( $line );
+ }
+
+ return TRUE;
+ }
+
+ if( is_string( $sql ) ) {
+ $this->sqlArray[] = $sql;
+
+ // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
+ if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
+ $saved = $this->db->debug;
+ $this->db->debug = $this->debug;
+ $ok = $this->db->Execute( $sql );
+ $this->db->debug = $saved;
+
+ if( !$ok ) {
+ if( $this->debug ) {
+ ADOConnection::outp( $this->db->ErrorMsg() );
+ }
+
+ $this->success = 1;
+ }
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Gets the SQL array in the specified format.
+ *
+ * @param string $format Format
+ * @return mixed SQL
+ *
+ * @access private
+ */
+ function getSQL( $format = NULL, $sqlArray = NULL ) {
+ if( !is_array( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+
+ if( !is_array( $sqlArray ) ) {
+ return FALSE;
+ }
+
+ switch( strtolower( $format ) ) {
+ case 'string':
+ case 'text':
+ return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
+ case'html':
+ return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
+ }
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Destroys an adoSchema object.
+ *
+ * Call this method to clean up after an adoSchema object that is no longer in use.
+ * @deprecated adoSchema now cleans up automatically.
+ */
+ function Destroy() {
+ if ($this->mgq !== false) {
+ ini_set('magic_quotes_runtime', $this->mgq );
+ }
+ }
+}
+
+/**
+* Message logging function
+*
+* @access private
+*/
+function logMsg( $msg, $title = NULL, $force = FALSE ) {
+ if( XMLS_DEBUG or $force ) {
+ echo '<pre>';
+
+ if( isset( $title ) ) {
+ echo '<h3>' . htmlentities( $title ) . '</h3>';
+ }
+
+ if( is_object( $this ) ) {
+ echo '[' . get_class( $this ) . '] ';
+ }
+
+ print_r( $msg );
+
+ echo '</pre>';
+ }
+}
diff --git a/vendor/adodb/adodb-php/adodb-xmlschema03.inc.php b/vendor/adodb/adodb-php/adodb-xmlschema03.inc.php
new file mode 100644
index 0000000..4d1faad
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb-xmlschema03.inc.php
@@ -0,0 +1,2408 @@
+<?php
+// Copyright (c) 2004-2005 ars Cognita Inc., all rights reserved
+/* ******************************************************************************
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+*******************************************************************************/
+/**
+ * xmlschema is a class that allows the user to quickly and easily
+ * build a database on any ADOdb-supported platform using a simple
+ * XML schema.
+ *
+ * Last Editor: $Author: jlim $
+ * @author Richard Tango-Lowy & Dan Cech
+ * @version $Revision: 1.62 $
+ *
+ * @package axmls
+ * @tutorial getting_started.pkg
+ */
+
+function _file_get_contents($file)
+{
+ if (function_exists('file_get_contents')) return file_get_contents($file);
+
+ $f = fopen($file,'r');
+ if (!$f) return '';
+ $t = '';
+
+ while ($s = fread($f,100000)) $t .= $s;
+ fclose($f);
+ return $t;
+}
+
+
+/**
+* Debug on or off
+*/
+if( !defined( 'XMLS_DEBUG' ) ) {
+ define( 'XMLS_DEBUG', FALSE );
+}
+
+/**
+* Default prefix key
+*/
+if( !defined( 'XMLS_PREFIX' ) ) {
+ define( 'XMLS_PREFIX', '%%P' );
+}
+
+/**
+* Maximum length allowed for object prefix
+*/
+if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
+ define( 'XMLS_PREFIX_MAXLEN', 10 );
+}
+
+/**
+* Execute SQL inline as it is generated
+*/
+if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
+ define( 'XMLS_EXECUTE_INLINE', FALSE );
+}
+
+/**
+* Continue SQL Execution if an error occurs?
+*/
+if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
+ define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
+}
+
+/**
+* Current Schema Version
+*/
+if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
+ define( 'XMLS_SCHEMA_VERSION', '0.3' );
+}
+
+/**
+* Default Schema Version. Used for Schemas without an explicit version set.
+*/
+if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
+ define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
+}
+
+/**
+* How to handle data rows that already exist in a database during and upgrade.
+* Options are INSERT (attempts to insert duplicate rows), UPDATE (updates existing
+* rows) and IGNORE (ignores existing rows).
+*/
+if( !defined( 'XMLS_MODE_INSERT' ) ) {
+ define( 'XMLS_MODE_INSERT', 0 );
+}
+if( !defined( 'XMLS_MODE_UPDATE' ) ) {
+ define( 'XMLS_MODE_UPDATE', 1 );
+}
+if( !defined( 'XMLS_MODE_IGNORE' ) ) {
+ define( 'XMLS_MODE_IGNORE', 2 );
+}
+if( !defined( 'XMLS_EXISTING_DATA' ) ) {
+ define( 'XMLS_EXISTING_DATA', XMLS_MODE_INSERT );
+}
+
+/**
+* Default Schema Version. Used for Schemas without an explicit version set.
+*/
+if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
+ define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
+}
+
+/**
+* Include the main ADODB library
+*/
+if( !defined( '_ADODB_LAYER' ) ) {
+ require( 'adodb.inc.php' );
+ require( 'adodb-datadict.inc.php' );
+}
+
+/**
+* Abstract DB Object. This class provides basic methods for database objects, such
+* as tables and indexes.
+*
+* @package axmls
+* @access private
+*/
+class dbObject {
+
+ /**
+ * var object Parent
+ */
+ var $parent;
+
+ /**
+ * var string current element
+ */
+ var $currentElement;
+
+ /**
+ * NOP
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+
+ }
+
+ function create(&$xmls) {
+ return array();
+ }
+
+ /**
+ * Destroys the object
+ */
+ function destroy() {
+ }
+
+ /**
+ * Checks whether the specified RDBMS is supported by the current
+ * database object or its ranking ancestor.
+ *
+ * @param string $platform RDBMS platform name (from ADODB platform list).
+ * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
+ */
+ function supportedPlatform( $platform = NULL ) {
+ return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
+ }
+
+ /**
+ * Returns the prefix set by the ranking ancestor of the database object.
+ *
+ * @param string $name Prefix string.
+ * @return string Prefix.
+ */
+ function prefix( $name = '' ) {
+ return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
+ }
+
+ /**
+ * Extracts a field ID from the specified field.
+ *
+ * @param string $field Field.
+ * @return string Field ID.
+ */
+ function FieldID( $field ) {
+ return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
+ }
+}
+
+/**
+* Creates a table object in ADOdb's datadict format
+*
+* This class stores information about a database table. As charactaristics
+* of the table are loaded from the external source, methods and properties
+* of this class are used to build up the table description in ADOdb's
+* datadict format.
+*
+* @package axmls
+* @access private
+*/
+class dbTable extends dbObject {
+
+ /**
+ * @var string Table name
+ */
+ var $name;
+
+ /**
+ * @var array Field specifier: Meta-information about each field
+ */
+ var $fields = array();
+
+ /**
+ * @var array List of table indexes.
+ */
+ var $indexes = array();
+
+ /**
+ * @var array Table options: Table-level options
+ */
+ var $opts = array();
+
+ /**
+ * @var string Field index: Keeps track of which field is currently being processed
+ */
+ var $current_field;
+
+ /**
+ * @var boolean Mark table for destruction
+ * @access private
+ */
+ var $drop_table;
+
+ /**
+ * @var boolean Mark field for destruction (not yet implemented)
+ * @access private
+ */
+ var $drop_field = array();
+
+ /**
+ * @var array Platform-specific options
+ * @access private
+ */
+ var $currentPlatform = true;
+
+
+ /**
+ * Iniitializes a new table object.
+ *
+ * @param string $prefix DB Object prefix
+ * @param array $attributes Array of table attributes.
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ $this->name = $this->prefix($attributes['NAME']);
+ }
+
+ /**
+ * XML Callback to process start elements. Elements currently
+ * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'INDEX':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $index = $this->addIndex( $attributes );
+ xml_set_object( $parser, $index );
+ }
+ break;
+ case 'DATA':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $data = $this->addData( $attributes );
+ xml_set_object( $parser, $data );
+ }
+ break;
+ case 'DROP':
+ $this->drop();
+ break;
+ case 'FIELD':
+ // Add a field
+ $fieldName = $attributes['NAME'];
+ $fieldType = $attributes['TYPE'];
+ $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
+ $fieldOpts = !empty( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
+
+ $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
+ break;
+ case 'KEY':
+ case 'NOTNULL':
+ case 'AUTOINCREMENT':
+ case 'DEFDATE':
+ case 'DEFTIMESTAMP':
+ case 'UNSIGNED':
+ // Add a field option
+ $this->addFieldOpt( $this->current_field, $this->currentElement );
+ break;
+ case 'DEFAULT':
+ // Add a field option to the table object
+
+ // Work around ADOdb datadict issue that misinterprets empty strings.
+ if( $attributes['VALUE'] == '' ) {
+ $attributes['VALUE'] = " '' ";
+ }
+
+ $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
+ break;
+ case 'OPT':
+ case 'CONSTRAINT':
+ // Accept platform-specific options
+ $this->currentPlatform = ( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) );
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Table/field constraint
+ case 'CONSTRAINT':
+ if( isset( $this->current_field ) ) {
+ $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
+ } else {
+ $this->addTableOpt( $cdata );
+ }
+ break;
+ // Table/field option
+ case 'OPT':
+ if( isset( $this->current_field ) ) {
+ $this->addFieldOpt( $this->current_field, $cdata );
+ } else {
+ $this->addTableOpt( $cdata );
+ }
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'TABLE':
+ $this->parent->addSQL( $this->create( $this->parent ) );
+ xml_set_object( $parser, $this->parent );
+ $this->destroy();
+ break;
+ case 'FIELD':
+ unset($this->current_field);
+ break;
+ case 'OPT':
+ case 'CONSTRAINT':
+ $this->currentPlatform = true;
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * Adds an index to a table object
+ *
+ * @param array $attributes Index attributes
+ * @return object dbIndex object
+ */
+ function addIndex( $attributes ) {
+ $name = strtoupper( $attributes['NAME'] );
+ $this->indexes[$name] = new dbIndex( $this, $attributes );
+ return $this->indexes[$name];
+ }
+
+ /**
+ * Adds data to a table object
+ *
+ * @param array $attributes Data attributes
+ * @return object dbData object
+ */
+ function addData( $attributes ) {
+ if( !isset( $this->data ) ) {
+ $this->data = new dbData( $this, $attributes );
+ }
+ return $this->data;
+ }
+
+ /**
+ * Adds a field to a table object
+ *
+ * $name is the name of the table to which the field should be added.
+ * $type is an ADODB datadict field type. The following field types
+ * are supported as of ADODB 3.40:
+ * - C: varchar
+ * - X: CLOB (character large object) or largest varchar size
+ * if CLOB is not supported
+ * - C2: Multibyte varchar
+ * - X2: Multibyte CLOB
+ * - B: BLOB (binary large object)
+ * - D: Date (some databases do not support this, and we return a datetime type)
+ * - T: Datetime or Timestamp
+ * - L: Integer field suitable for storing booleans (0 or 1)
+ * - I: Integer (mapped to I4)
+ * - I1: 1-byte integer
+ * - I2: 2-byte integer
+ * - I4: 4-byte integer
+ * - I8: 8-byte integer
+ * - F: Floating point number
+ * - N: Numeric or decimal number
+ *
+ * @param string $name Name of the table to which the field will be added.
+ * @param string $type ADODB datadict field type.
+ * @param string $size Field size
+ * @param array $opts Field options array
+ * @return array Field specifier array
+ */
+ function addField( $name, $type, $size = NULL, $opts = NULL ) {
+ $field_id = $this->FieldID( $name );
+
+ // Set the field index so we know where we are
+ $this->current_field = $field_id;
+
+ // Set the field name (required)
+ $this->fields[$field_id]['NAME'] = $name;
+
+ // Set the field type (required)
+ $this->fields[$field_id]['TYPE'] = $type;
+
+ // Set the field size (optional)
+ if( isset( $size ) ) {
+ $this->fields[$field_id]['SIZE'] = $size;
+ }
+
+ // Set the field options
+ if( isset( $opts ) ) {
+ $this->fields[$field_id]['OPTS'] = array($opts);
+ } else {
+ $this->fields[$field_id]['OPTS'] = array();
+ }
+ }
+
+ /**
+ * Adds a field option to the current field specifier
+ *
+ * This method adds a field option allowed by the ADOdb datadict
+ * and appends it to the given field.
+ *
+ * @param string $field Field name
+ * @param string $opt ADOdb field option
+ * @param mixed $value Field option value
+ * @return array Field specifier array
+ */
+ function addFieldOpt( $field, $opt, $value = NULL ) {
+ if( $this->currentPlatform ) {
+ if( !isset( $value ) ) {
+ $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
+ // Add the option and value
+ } else {
+ $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
+ }
+ }
+ }
+
+ /**
+ * Adds an option to the table
+ *
+ * This method takes a comma-separated list of table-level options
+ * and appends them to the table object.
+ *
+ * @param string $opt Table option
+ * @return array Options
+ */
+ function addTableOpt( $opt ) {
+ if(isset($this->currentPlatform)) {
+ $this->opts[$this->parent->db->databaseType] = $opt;
+ }
+ return $this->opts;
+ }
+
+
+ /**
+ * Generates the SQL that will create the table in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing table creation SQL
+ */
+ function create( &$xmls ) {
+ $sql = array();
+
+ // drop any existing indexes
+ if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
+ foreach( $legacy_indexes as $index => $index_details ) {
+ $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
+ }
+ }
+
+ // remove fields to be dropped from table object
+ foreach( $this->drop_field as $field ) {
+ unset( $this->fields[$field] );
+ }
+
+ // if table exists
+ if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
+ // drop table
+ if( $this->drop_table ) {
+ $sql[] = $xmls->dict->DropTableSQL( $this->name );
+
+ return $sql;
+ }
+
+ // drop any existing fields not in schema
+ foreach( $legacy_fields as $field_id => $field ) {
+ if( !isset( $this->fields[$field_id] ) ) {
+ $sql[] = $xmls->dict->DropColumnSQL( $this->name, $field->name );
+ }
+ }
+ // if table doesn't exist
+ } else {
+ if( $this->drop_table ) {
+ return $sql;
+ }
+
+ $legacy_fields = array();
+ }
+
+ // Loop through the field specifier array, building the associative array for the field options
+ $fldarray = array();
+
+ foreach( $this->fields as $field_id => $finfo ) {
+ // Set an empty size if it isn't supplied
+ if( !isset( $finfo['SIZE'] ) ) {
+ $finfo['SIZE'] = '';
+ }
+
+ // Initialize the field array with the type and size
+ $fldarray[$field_id] = array(
+ 'NAME' => $finfo['NAME'],
+ 'TYPE' => $finfo['TYPE'],
+ 'SIZE' => $finfo['SIZE']
+ );
+
+ // Loop through the options array and add the field options.
+ if( isset( $finfo['OPTS'] ) ) {
+ foreach( $finfo['OPTS'] as $opt ) {
+ // Option has an argument.
+ if( is_array( $opt ) ) {
+ $key = key( $opt );
+ $value = $opt[key( $opt )];
+ @$fldarray[$field_id][$key] .= $value;
+ // Option doesn't have arguments
+ } else {
+ $fldarray[$field_id][$opt] = $opt;
+ }
+ }
+ }
+ }
+
+ if( empty( $legacy_fields ) ) {
+ // Create the new table
+ $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
+ logMsg( end( $sql ), 'Generated CreateTableSQL' );
+ } else {
+ // Upgrade an existing table
+ logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
+ switch( $xmls->upgrade ) {
+ // Use ChangeTableSQL
+ case 'ALTER':
+ logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
+ $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
+ break;
+ case 'REPLACE':
+ logMsg( 'Doing upgrade REPLACE (testing)' );
+ $sql[] = $xmls->dict->DropTableSQL( $this->name );
+ $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
+ break;
+ // ignore table
+ default:
+ return array();
+ }
+ }
+
+ foreach( $this->indexes as $index ) {
+ $sql[] = $index->create( $xmls );
+ }
+
+ if( isset( $this->data ) ) {
+ $sql[] = $this->data->create( $xmls );
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Marks a field or table for destruction
+ */
+ function drop() {
+ if( isset( $this->current_field ) ) {
+ // Drop the current field
+ logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
+ // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
+ $this->drop_field[$this->current_field] = $this->current_field;
+ } else {
+ // Drop the current table
+ logMsg( "Dropping table '{$this->name}'" );
+ // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
+ $this->drop_table = TRUE;
+ }
+ }
+}
+
+/**
+* Creates an index object in ADOdb's datadict format
+*
+* This class stores information about a database index. As charactaristics
+* of the index are loaded from the external source, methods and properties
+* of this class are used to build up the index description in ADOdb's
+* datadict format.
+*
+* @package axmls
+* @access private
+*/
+class dbIndex extends dbObject {
+
+ /**
+ * @var string Index name
+ */
+ var $name;
+
+ /**
+ * @var array Index options: Index-level options
+ */
+ var $opts = array();
+
+ /**
+ * @var array Indexed fields: Table columns included in this index
+ */
+ var $columns = array();
+
+ /**
+ * @var boolean Mark index for destruction
+ * @access private
+ */
+ var $drop = FALSE;
+
+ /**
+ * Initializes the new dbIndex object.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ *
+ * @internal
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+
+ $this->name = $this->prefix ($attributes['NAME']);
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * Processes XML opening tags.
+ * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'DROP':
+ $this->drop();
+ break;
+ case 'CLUSTERED':
+ case 'BITMAP':
+ case 'UNIQUE':
+ case 'FULLTEXT':
+ case 'HASH':
+ // Add index Option
+ $this->addIndexOpt( $this->currentElement );
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * Processes XML cdata.
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Index field name
+ case 'COL':
+ $this->addField( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'INDEX':
+ xml_set_object( $parser, $this->parent );
+ break;
+ }
+ }
+
+ /**
+ * Adds a field to the index
+ *
+ * @param string $name Field name
+ * @return string Field list
+ */
+ function addField( $name ) {
+ $this->columns[$this->FieldID( $name )] = $name;
+
+ // Return the field list
+ return $this->columns;
+ }
+
+ /**
+ * Adds options to the index
+ *
+ * @param string $opt Comma-separated list of index options.
+ * @return string Option list
+ */
+ function addIndexOpt( $opt ) {
+ $this->opts[] = $opt;
+
+ // Return the options list
+ return $this->opts;
+ }
+
+ /**
+ * Generates the SQL that will create the index in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing index creation SQL
+ */
+ function create( &$xmls ) {
+ if( $this->drop ) {
+ return NULL;
+ }
+
+ // eliminate any columns that aren't in the table
+ foreach( $this->columns as $id => $col ) {
+ if( !isset( $this->parent->fields[$id] ) ) {
+ unset( $this->columns[$id] );
+ }
+ }
+
+ return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
+ }
+
+ /**
+ * Marks an index for destruction
+ */
+ function drop() {
+ $this->drop = TRUE;
+ }
+}
+
+/**
+* Creates a data object in ADOdb's datadict format
+*
+* This class stores information about table data, and is called
+* when we need to load field data into a table.
+*
+* @package axmls
+* @access private
+*/
+class dbData extends dbObject {
+
+ var $data = array();
+
+ var $row;
+
+ /**
+ * Initializes the new dbData object.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ *
+ * @internal
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * Processes XML opening tags.
+ * Elements currently processed are: ROW and F (field).
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'ROW':
+ $this->row = count( $this->data );
+ $this->data[$this->row] = array();
+ break;
+ case 'F':
+ $this->addField($attributes);
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * Processes XML cdata.
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Index field name
+ case 'F':
+ $this->addData( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'DATA':
+ xml_set_object( $parser, $this->parent );
+ break;
+ }
+ }
+
+ /**
+ * Adds a field to the insert
+ *
+ * @param string $name Field name
+ * @return string Field list
+ */
+ function addField( $attributes ) {
+ // check we're in a valid row
+ if( !isset( $this->row ) || !isset( $this->data[$this->row] ) ) {
+ return;
+ }
+
+ // Set the field index so we know where we are
+ if( isset( $attributes['NAME'] ) ) {
+ $this->current_field = $this->FieldID( $attributes['NAME'] );
+ } else {
+ $this->current_field = count( $this->data[$this->row] );
+ }
+
+ // initialise data
+ if( !isset( $this->data[$this->row][$this->current_field] ) ) {
+ $this->data[$this->row][$this->current_field] = '';
+ }
+ }
+
+ /**
+ * Adds options to the index
+ *
+ * @param string $opt Comma-separated list of index options.
+ * @return string Option list
+ */
+ function addData( $cdata ) {
+ // check we're in a valid field
+ if ( isset( $this->data[$this->row][$this->current_field] ) ) {
+ // add data to field
+ $this->data[$this->row][$this->current_field] .= $cdata;
+ }
+ }
+
+ /**
+ * Generates the SQL that will add/update the data in the database
+ *
+ * @param object $xmls adoSchema object
+ * @return array Array containing index creation SQL
+ */
+ function create( &$xmls ) {
+ $table = $xmls->dict->TableName($this->parent->name);
+ $table_field_count = count($this->parent->fields);
+ $tables = $xmls->db->MetaTables();
+ $sql = array();
+
+ $ukeys = $xmls->db->MetaPrimaryKeys( $table );
+ if( !empty( $this->parent->indexes ) and !empty( $ukeys ) ) {
+ foreach( $this->parent->indexes as $indexObj ) {
+ if( !in_array( $indexObj->name, $ukeys ) ) $ukeys[] = $indexObj->name;
+ }
+ }
+
+ // eliminate any columns that aren't in the table
+ foreach( $this->data as $row ) {
+ $table_fields = $this->parent->fields;
+ $fields = array();
+ $rawfields = array(); // Need to keep some of the unprocessed data on hand.
+
+ foreach( $row as $field_id => $field_data ) {
+ if( !array_key_exists( $field_id, $table_fields ) ) {
+ if( is_numeric( $field_id ) ) {
+ $field_id = reset( array_keys( $table_fields ) );
+ } else {
+ continue;
+ }
+ }
+
+ $name = $table_fields[$field_id]['NAME'];
+
+ switch( $table_fields[$field_id]['TYPE'] ) {
+ case 'I':
+ case 'I1':
+ case 'I2':
+ case 'I4':
+ case 'I8':
+ $fields[$name] = intval($field_data);
+ break;
+ case 'C':
+ case 'C2':
+ case 'X':
+ case 'X2':
+ default:
+ $fields[$name] = $xmls->db->qstr( $field_data );
+ $rawfields[$name] = $field_data;
+ }
+
+ unset($table_fields[$field_id]);
+
+ }
+
+ // check that at least 1 column is specified
+ if( empty( $fields ) ) {
+ continue;
+ }
+
+ // check that no required columns are missing
+ if( count( $fields ) < $table_field_count ) {
+ foreach( $table_fields as $field ) {
+ if( isset( $field['OPTS'] ) and ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
+ continue(2);
+ }
+ }
+ }
+
+ // The rest of this method deals with updating existing data records.
+
+ if( !in_array( $table, $tables ) or ( $mode = $xmls->existingData() ) == XMLS_MODE_INSERT ) {
+ // Table doesn't yet exist, so it's safe to insert.
+ logMsg( "$table doesn't exist, inserting or mode is INSERT" );
+ $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
+ continue;
+ }
+
+ // Prepare to test for potential violations. Get primary keys and unique indexes
+ $mfields = array_merge( $fields, $rawfields );
+ $keyFields = array_intersect( $ukeys, array_keys( $mfields ) );
+
+ if( empty( $ukeys ) or count( $keyFields ) == 0 ) {
+ // No unique keys in schema, so safe to insert
+ logMsg( "Either schema or data has no unique keys, so safe to insert" );
+ $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
+ continue;
+ }
+
+ // Select record containing matching unique keys.
+ $where = '';
+ foreach( $ukeys as $key ) {
+ if( isset( $mfields[$key] ) and $mfields[$key] ) {
+ if( $where ) $where .= ' AND ';
+ $where .= $key . ' = ' . $xmls->db->qstr( $mfields[$key] );
+ }
+ }
+ $records = $xmls->db->Execute( 'SELECT * FROM ' . $table . ' WHERE ' . $where );
+ switch( $records->RecordCount() ) {
+ case 0:
+ // No matching record, so safe to insert.
+ logMsg( "No matching records. Inserting new row with unique data" );
+ $sql[] = $xmls->db->GetInsertSQL( $records, $mfields );
+ break;
+ case 1:
+ // Exactly one matching record, so we can update if the mode permits.
+ logMsg( "One matching record..." );
+ if( $mode == XMLS_MODE_UPDATE ) {
+ logMsg( "...Updating existing row from unique data" );
+ $sql[] = $xmls->db->GetUpdateSQL( $records, $mfields );
+ }
+ break;
+ default:
+ // More than one matching record; the result is ambiguous, so we must ignore the row.
+ logMsg( "More than one matching record. Ignoring row." );
+ }
+ }
+ return $sql;
+ }
+}
+
+/**
+* Creates the SQL to execute a list of provided SQL queries
+*
+* @package axmls
+* @access private
+*/
+class dbQuerySet extends dbObject {
+
+ /**
+ * @var array List of SQL queries
+ */
+ var $queries = array();
+
+ /**
+ * @var string String used to build of a query line by line
+ */
+ var $query;
+
+ /**
+ * @var string Query prefix key
+ */
+ var $prefixKey = '';
+
+ /**
+ * @var boolean Auto prefix enable (TRUE)
+ */
+ var $prefixMethod = 'AUTO';
+
+ /**
+ * Initializes the query set.
+ *
+ * @param object $parent Parent object
+ * @param array $attributes Attributes
+ */
+ function __construct( &$parent, $attributes = NULL ) {
+ $this->parent = $parent;
+
+ // Overrides the manual prefix key
+ if( isset( $attributes['KEY'] ) ) {
+ $this->prefixKey = $attributes['KEY'];
+ }
+
+ $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
+
+ // Enables or disables automatic prefix prepending
+ switch( $prefixMethod ) {
+ case 'AUTO':
+ $this->prefixMethod = 'AUTO';
+ break;
+ case 'MANUAL':
+ $this->prefixMethod = 'MANUAL';
+ break;
+ case 'NONE':
+ $this->prefixMethod = 'NONE';
+ break;
+ }
+ }
+
+ /**
+ * XML Callback to process start elements. Elements currently
+ * processed are: QUERY.
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ $this->currentElement = strtoupper( $tag );
+
+ switch( $this->currentElement ) {
+ case 'QUERY':
+ // Create a new query in a SQL queryset.
+ // Ignore this query set if a platform is specified and it's different than the
+ // current connection platform.
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $this->newQuery();
+ } else {
+ $this->discardQuery();
+ }
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ switch( $this->currentElement ) {
+ // Line of queryset SQL data
+ case 'QUERY':
+ $this->buildQuery( $cdata );
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ */
+ function _tag_close( &$parser, $tag ) {
+ $this->currentElement = '';
+
+ switch( strtoupper( $tag ) ) {
+ case 'QUERY':
+ // Add the finished query to the open query set.
+ $this->addQuery();
+ break;
+ case 'SQL':
+ $this->parent->addSQL( $this->create( $this->parent ) );
+ xml_set_object( $parser, $this->parent );
+ $this->destroy();
+ break;
+ default:
+
+ }
+ }
+
+ /**
+ * Re-initializes the query.
+ *
+ * @return boolean TRUE
+ */
+ function newQuery() {
+ $this->query = '';
+
+ return TRUE;
+ }
+
+ /**
+ * Discards the existing query.
+ *
+ * @return boolean TRUE
+ */
+ function discardQuery() {
+ unset( $this->query );
+
+ return TRUE;
+ }
+
+ /**
+ * Appends a line to a query that is being built line by line
+ *
+ * @param string $data Line of SQL data or NULL to initialize a new query
+ * @return string SQL query string.
+ */
+ function buildQuery( $sql = NULL ) {
+ if( !isset( $this->query ) OR empty( $sql ) ) {
+ return FALSE;
+ }
+
+ $this->query .= $sql;
+
+ return $this->query;
+ }
+
+ /**
+ * Adds a completed query to the query list
+ *
+ * @return string SQL of added query
+ */
+ function addQuery() {
+ if( !isset( $this->query ) ) {
+ return FALSE;
+ }
+
+ $this->queries[] = $return = trim($this->query);
+
+ unset( $this->query );
+
+ return $return;
+ }
+
+ /**
+ * Creates and returns the current query set
+ *
+ * @param object $xmls adoSchema object
+ * @return array Query set
+ */
+ function create( &$xmls ) {
+ foreach( $this->queries as $id => $query ) {
+ switch( $this->prefixMethod ) {
+ case 'AUTO':
+ // Enable auto prefix replacement
+
+ // Process object prefix.
+ // Evaluate SQL statements to prepend prefix to objects
+ $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+ $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+ $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
+
+ // SELECT statements aren't working yet
+ #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
+
+ case 'MANUAL':
+ // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
+ // If prefixKey is not set, we use the default constant XMLS_PREFIX
+ if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
+ // Enable prefix override
+ $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
+ } else {
+ // Use default replacement
+ $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
+ }
+ }
+
+ $this->queries[$id] = trim( $query );
+ }
+
+ // Return the query set array
+ return $this->queries;
+ }
+
+ /**
+ * Rebuilds the query with the prefix attached to any objects
+ *
+ * @param string $regex Regex used to add prefix
+ * @param string $query SQL query string
+ * @param string $prefix Prefix to be appended to tables, indices, etc.
+ * @return string Prefixed SQL query string.
+ */
+ function prefixQuery( $regex, $query, $prefix = NULL ) {
+ if( !isset( $prefix ) ) {
+ return $query;
+ }
+
+ if( preg_match( $regex, $query, $match ) ) {
+ $preamble = $match[1];
+ $postamble = $match[5];
+ $objectList = explode( ',', $match[3] );
+ // $prefix = $prefix . '_';
+
+ $prefixedList = '';
+
+ foreach( $objectList as $object ) {
+ if( $prefixedList !== '' ) {
+ $prefixedList .= ', ';
+ }
+
+ $prefixedList .= $prefix . trim( $object );
+ }
+
+ $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
+ }
+
+ return $query;
+ }
+}
+
+/**
+* Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
+*
+* This class is used to load and parse the XML file, to create an array of SQL statements
+* that can be used to build a database, and to build the database using the SQL array.
+*
+* @tutorial getting_started.pkg
+*
+* @author Richard Tango-Lowy & Dan Cech
+* @version $Revision: 1.62 $
+*
+* @package axmls
+*/
+class adoSchema {
+
+ /**
+ * @var array Array containing SQL queries to generate all objects
+ * @access private
+ */
+ var $sqlArray;
+
+ /**
+ * @var object ADOdb connection object
+ * @access private
+ */
+ var $db;
+
+ /**
+ * @var object ADOdb Data Dictionary
+ * @access private
+ */
+ var $dict;
+
+ /**
+ * @var string Current XML element
+ * @access private
+ */
+ var $currentElement = '';
+
+ /**
+ * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
+ * @access private
+ */
+ var $upgrade = '';
+
+ /**
+ * @var string Optional object prefix
+ * @access private
+ */
+ var $objectPrefix = '';
+
+ /**
+ * @var long Original Magic Quotes Runtime value
+ * @access private
+ */
+ var $mgq;
+
+ /**
+ * @var long System debug
+ * @access private
+ */
+ var $debug;
+
+ /**
+ * @var string Regular expression to find schema version
+ * @access private
+ */
+ var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
+
+ /**
+ * @var string Current schema version
+ * @access private
+ */
+ var $schemaVersion;
+
+ /**
+ * @var int Success of last Schema execution
+ */
+ var $success;
+
+ /**
+ * @var bool Execute SQL inline as it is generated
+ */
+ var $executeInline;
+
+ /**
+ * @var bool Continue SQL execution if errors occur
+ */
+ var $continueOnError;
+
+ /**
+ * @var int How to handle existing data rows (insert, update, or ignore)
+ */
+ var $existingData;
+
+ /**
+ * Creates an adoSchema object
+ *
+ * Creating an adoSchema object is the first step in processing an XML schema.
+ * The only parameter is an ADOdb database connection object, which must already
+ * have been created.
+ *
+ * @param object $db ADOdb database connection object.
+ */
+ function __construct( $db ) {
+ // Initialize the environment
+ $this->mgq = get_magic_quotes_runtime();
+ if ($this->mgq !== false) {
+ ini_set('magic_quotes_runtime', 0 );
+ }
+
+ $this->db = $db;
+ $this->debug = $this->db->debug;
+ $this->dict = NewDataDictionary( $this->db );
+ $this->sqlArray = array();
+ $this->schemaVersion = XMLS_SCHEMA_VERSION;
+ $this->executeInline( XMLS_EXECUTE_INLINE );
+ $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
+ $this->existingData( XMLS_EXISTING_DATA );
+ $this->setUpgradeMethod();
+ }
+
+ /**
+ * Sets the method to be used for upgrading an existing database
+ *
+ * Use this method to specify how existing database objects should be upgraded.
+ * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
+ * alter each database object directly, REPLACE attempts to rebuild each object
+ * from scratch, BEST attempts to determine the best upgrade method for each
+ * object, and NONE disables upgrading.
+ *
+ * This method is not yet used by AXMLS, but exists for backward compatibility.
+ * The ALTER method is automatically assumed when the adoSchema object is
+ * instantiated; other upgrade methods are not currently supported.
+ *
+ * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
+ * @returns string Upgrade method used
+ */
+ function SetUpgradeMethod( $method = '' ) {
+ if( !is_string( $method ) ) {
+ return FALSE;
+ }
+
+ $method = strtoupper( $method );
+
+ // Handle the upgrade methods
+ switch( $method ) {
+ case 'ALTER':
+ $this->upgrade = $method;
+ break;
+ case 'REPLACE':
+ $this->upgrade = $method;
+ break;
+ case 'BEST':
+ $this->upgrade = 'ALTER';
+ break;
+ case 'NONE':
+ $this->upgrade = 'NONE';
+ break;
+ default:
+ // Use default if no legitimate method is passed.
+ $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
+ }
+
+ return $this->upgrade;
+ }
+
+ /**
+ * Specifies how to handle existing data row when there is a unique key conflict.
+ *
+ * The existingData setting specifies how the parser should handle existing rows
+ * when a unique key violation occurs during the insert. This can happen when inserting
+ * data into an existing table with one or more primary keys or unique indexes.
+ * The existingData method takes one of three options: XMLS_MODE_INSERT attempts
+ * to always insert the data as a new row. In the event of a unique key violation,
+ * the database will generate an error. XMLS_MODE_UPDATE attempts to update the
+ * any existing rows with the new data based upon primary or unique key fields in
+ * the schema. If the data row in the schema specifies no unique fields, the row
+ * data will be inserted as a new row. XMLS_MODE_IGNORE specifies that any data rows
+ * that would result in a unique key violation be ignored; no inserts or updates will
+ * take place. For backward compatibility, the default setting is XMLS_MODE_INSERT,
+ * but XMLS_MODE_UPDATE will generally be the most appropriate setting.
+ *
+ * @param int $mode XMLS_MODE_INSERT, XMLS_MODE_UPDATE, or XMLS_MODE_IGNORE
+ * @return int current mode
+ */
+ function ExistingData( $mode = NULL ) {
+ if( is_int( $mode ) ) {
+ switch( $mode ) {
+ case XMLS_MODE_UPDATE:
+ $mode = XMLS_MODE_UPDATE;
+ break;
+ case XMLS_MODE_IGNORE:
+ $mode = XMLS_MODE_IGNORE;
+ break;
+ case XMLS_MODE_INSERT:
+ $mode = XMLS_MODE_INSERT;
+ break;
+ default:
+ $mode = XMLS_EXISTING_DATA;
+ break;
+ }
+ $this->existingData = $mode;
+ }
+
+ return $this->existingData;
+ }
+
+ /**
+ * Enables/disables inline SQL execution.
+ *
+ * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
+ * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
+ * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
+ * to apply the schema to the database.
+ *
+ * @param bool $mode execute
+ * @return bool current execution mode
+ *
+ * @see ParseSchema(), ExecuteSchema()
+ */
+ function ExecuteInline( $mode = NULL ) {
+ if( is_bool( $mode ) ) {
+ $this->executeInline = $mode;
+ }
+
+ return $this->executeInline;
+ }
+
+ /**
+ * Enables/disables SQL continue on error.
+ *
+ * Call this method to enable or disable continuation of SQL execution if an error occurs.
+ * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
+ * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
+ * of the schema will continue.
+ *
+ * @param bool $mode execute
+ * @return bool current continueOnError mode
+ *
+ * @see addSQL(), ExecuteSchema()
+ */
+ function ContinueOnError( $mode = NULL ) {
+ if( is_bool( $mode ) ) {
+ $this->continueOnError = $mode;
+ }
+
+ return $this->continueOnError;
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to SQL.
+ *
+ * Call this method to load the specified schema (see the DTD for the proper format) from
+ * the filesystem and generate the SQL necessary to create the database
+ * described. This method automatically converts the schema to the latest
+ * axmls schema version.
+ * @see ParseSchemaString()
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute
+ */
+ function ParseSchema( $filename, $returnSchema = FALSE ) {
+ return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to SQL.
+ *
+ * Call this method to load the specified schema directly from a file (see
+ * the DTD for the proper format) and generate the SQL necessary to create
+ * the database described by the schema. Use this method when you are dealing
+ * with large schema files. Otherwise, ParseSchema() is faster.
+ * This method does not automatically convert the schema to the latest axmls
+ * schema version. You must convert the schema manually using either the
+ * ConvertSchemaFile() or ConvertSchemaString() method.
+ * @see ParseSchema()
+ * @see ConvertSchemaFile()
+ * @see ConvertSchemaString()
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ *
+ * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
+ * @see ParseSchema(), ParseSchemaString()
+ */
+ function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
+ // Open the file
+ if( !($fp = fopen( $filename, 'r' )) ) {
+ logMsg( 'Unable to open file' );
+ return FALSE;
+ }
+
+ // do version detection here
+ if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
+ logMsg( 'Invalid Schema Version' );
+ return FALSE;
+ }
+
+ if( $returnSchema ) {
+ $xmlstring = '';
+ while( $data = fread( $fp, 4096 ) ) {
+ $xmlstring .= $data . "\n";
+ }
+ return $xmlstring;
+ }
+
+ $this->success = 2;
+
+ $xmlParser = $this->create_parser();
+
+ // Process the file
+ while( $data = fread( $fp, 4096 ) ) {
+ if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
+ die( sprintf(
+ "XML error: %s at line %d",
+ xml_error_string( xml_get_error_code( $xmlParser) ),
+ xml_get_current_line_number( $xmlParser)
+ ) );
+ }
+ }
+
+ xml_parser_free( $xmlParser );
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Converts an XML schema string to SQL.
+ *
+ * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+ * and generate the SQL necessary to create the database described by the schema.
+ * @see ParseSchema()
+ *
+ * @param string $xmlstring XML schema string.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ */
+ function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
+ if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
+ logMsg( 'Empty or Invalid Schema' );
+ return FALSE;
+ }
+
+ // do version detection here
+ if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
+ logMsg( 'Invalid Schema Version' );
+ return FALSE;
+ }
+
+ if( $returnSchema ) {
+ return $xmlstring;
+ }
+
+ $this->success = 2;
+
+ $xmlParser = $this->create_parser();
+
+ if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
+ die( sprintf(
+ "XML error: %s at line %d",
+ xml_error_string( xml_get_error_code( $xmlParser) ),
+ xml_get_current_line_number( $xmlParser)
+ ) );
+ }
+
+ xml_parser_free( $xmlParser );
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Loads an XML schema from a file and converts it to uninstallation SQL.
+ *
+ * Call this method to load the specified schema (see the DTD for the proper format) from
+ * the filesystem and generate the SQL necessary to remove the database described.
+ * @see RemoveSchemaString()
+ *
+ * @param string $file Name of XML schema file.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute
+ */
+ function RemoveSchema( $filename, $returnSchema = FALSE ) {
+ return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
+ }
+
+ /**
+ * Converts an XML schema string to uninstallation SQL.
+ *
+ * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+ * and generate the SQL necessary to uninstall the database described by the schema.
+ * @see RemoveSchema()
+ *
+ * @param string $schema XML schema string.
+ * @param bool $returnSchema Return schema rather than parsing.
+ * @return array Array of SQL queries, ready to execute.
+ */
+ function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
+ return FALSE;
+ }
+
+ return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
+ }
+
+ /**
+ * Applies the current XML schema to the database (post execution).
+ *
+ * Call this method to apply the current schema (generally created by calling
+ * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
+ * and executing other SQL specified in the schema) after parsing.
+ * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
+ *
+ * @param array $sqlArray Array of SQL statements that will be applied rather than
+ * the current schema.
+ * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
+ * @returns integer 0 if failure, 1 if errors, 2 if successful.
+ */
+ function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) {
+ if( !is_bool( $continueOnErr ) ) {
+ $continueOnErr = $this->ContinueOnError();
+ }
+
+ if( !isset( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+
+ if( !is_array( $sqlArray ) ) {
+ $this->success = 0;
+ } else {
+ $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
+ }
+
+ return $this->success;
+ }
+
+ /**
+ * Returns the current SQL array.
+ *
+ * Call this method to fetch the array of SQL queries resulting from
+ * ParseSchema() or ParseSchemaString().
+ *
+ * @param string $format Format: HTML, TEXT, or NONE (PHP array)
+ * @return array Array of SQL statements or FALSE if an error occurs
+ */
+ function PrintSQL( $format = 'NONE' ) {
+ $sqlArray = null;
+ return $this->getSQL( $format, $sqlArray );
+ }
+
+ /**
+ * Saves the current SQL array to the local filesystem as a list of SQL queries.
+ *
+ * Call this method to save the array of SQL queries (generally resulting from a
+ * parsed XML schema) to the filesystem.
+ *
+ * @param string $filename Path and name where the file should be saved.
+ * @return boolean TRUE if save is successful, else FALSE.
+ */
+ function SaveSQL( $filename = './schema.sql' ) {
+
+ if( !isset( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+ if( !isset( $sqlArray ) ) {
+ return FALSE;
+ }
+
+ $fp = fopen( $filename, "w" );
+
+ foreach( $sqlArray as $key => $query ) {
+ fwrite( $fp, $query . ";\n" );
+ }
+ fclose( $fp );
+ }
+
+ /**
+ * Create an xml parser
+ *
+ * @return object PHP XML parser object
+ *
+ * @access private
+ */
+ function create_parser() {
+ // Create the parser
+ $xmlParser = xml_parser_create();
+ xml_set_object( $xmlParser, $this );
+
+ // Initialize the XML callback functions
+ xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
+ xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
+
+ return $xmlParser;
+ }
+
+ /**
+ * XML Callback to process start elements
+ *
+ * @access private
+ */
+ function _tag_open( &$parser, $tag, $attributes ) {
+ switch( strtoupper( $tag ) ) {
+ case 'TABLE':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $this->obj = new dbTable( $this, $attributes );
+ xml_set_object( $parser, $this->obj );
+ }
+ break;
+ case 'SQL':
+ if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
+ $this->obj = new dbQuerySet( $this, $attributes );
+ xml_set_object( $parser, $this->obj );
+ }
+ break;
+ default:
+ // print_r( array( $tag, $attributes ) );
+ }
+
+ }
+
+ /**
+ * XML Callback to process CDATA elements
+ *
+ * @access private
+ */
+ function _tag_cdata( &$parser, $cdata ) {
+ }
+
+ /**
+ * XML Callback to process end elements
+ *
+ * @access private
+ * @internal
+ */
+ function _tag_close( &$parser, $tag ) {
+
+ }
+
+ /**
+ * Converts an XML schema string to the specified DTD version.
+ *
+ * Call this method to convert a string containing an XML schema to a different AXMLS
+ * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+ * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+ * parameter is specified, the schema will be converted to the current DTD version.
+ * If the newFile parameter is provided, the converted schema will be written to the specified
+ * file.
+ * @see ConvertSchemaFile()
+ *
+ * @param string $schema String containing XML schema that will be converted.
+ * @param string $newVersion DTD version to convert to.
+ * @param string $newFile File name of (converted) output file.
+ * @return string Converted XML schema or FALSE if an error occurs.
+ */
+ function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
+ return FALSE;
+ }
+
+ if( !isset ($newVersion) ) {
+ $newVersion = $this->schemaVersion;
+ }
+
+ if( $version == $newVersion ) {
+ $result = $schema;
+ } else {
+ $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
+ }
+
+ if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
+ fwrite( $fp, $result );
+ fclose( $fp );
+ }
+
+ return $result;
+ }
+
+ /*
+ // compat for pre-4.3 - jlim
+ function _file_get_contents($path)
+ {
+ if (function_exists('file_get_contents')) return file_get_contents($path);
+ return join('',file($path));
+ }*/
+
+ /**
+ * Converts an XML schema file to the specified DTD version.
+ *
+ * Call this method to convert the specified XML schema file to a different AXMLS
+ * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+ * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+ * parameter is specified, the schema will be converted to the current DTD version.
+ * If the newFile parameter is provided, the converted schema will be written to the specified
+ * file.
+ * @see ConvertSchemaString()
+ *
+ * @param string $filename Name of XML schema file that will be converted.
+ * @param string $newVersion DTD version to convert to.
+ * @param string $newFile File name of (converted) output file.
+ * @return string Converted XML schema or FALSE if an error occurs.
+ */
+ function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
+
+ // grab current version
+ if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
+ return FALSE;
+ }
+
+ if( !isset ($newVersion) ) {
+ $newVersion = $this->schemaVersion;
+ }
+
+ if( $version == $newVersion ) {
+ $result = _file_get_contents( $filename );
+
+ // remove unicode BOM if present
+ if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
+ $result = substr( $result, 3 );
+ }
+ } else {
+ $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
+ }
+
+ if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
+ fwrite( $fp, $result );
+ fclose( $fp );
+ }
+
+ return $result;
+ }
+
+ function TransformSchema( $schema, $xsl, $schematype='string' )
+ {
+ // Fail if XSLT extension is not available
+ if( ! function_exists( 'xslt_create' ) ) {
+ return FALSE;
+ }
+
+ $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
+
+ // look for xsl
+ if( !is_readable( $xsl_file ) ) {
+ return FALSE;
+ }
+
+ switch( $schematype )
+ {
+ case 'file':
+ if( !is_readable( $schema ) ) {
+ return FALSE;
+ }
+
+ $schema = _file_get_contents( $schema );
+ break;
+ case 'string':
+ default:
+ if( !is_string( $schema ) ) {
+ return FALSE;
+ }
+ }
+
+ $arguments = array (
+ '/_xml' => $schema,
+ '/_xsl' => _file_get_contents( $xsl_file )
+ );
+
+ // create an XSLT processor
+ $xh = xslt_create ();
+
+ // set error handler
+ xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
+
+ // process the schema
+ $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
+
+ xslt_free ($xh);
+
+ return $result;
+ }
+
+ /**
+ * Processes XSLT transformation errors
+ *
+ * @param object $parser XML parser object
+ * @param integer $errno Error number
+ * @param integer $level Error level
+ * @param array $fields Error information fields
+ *
+ * @access private
+ */
+ function xslt_error_handler( $parser, $errno, $level, $fields ) {
+ if( is_array( $fields ) ) {
+ $msg = array(
+ 'Message Type' => ucfirst( $fields['msgtype'] ),
+ 'Message Code' => $fields['code'],
+ 'Message' => $fields['msg'],
+ 'Error Number' => $errno,
+ 'Level' => $level
+ );
+
+ switch( $fields['URI'] ) {
+ case 'arg:/_xml':
+ $msg['Input'] = 'XML';
+ break;
+ case 'arg:/_xsl':
+ $msg['Input'] = 'XSL';
+ break;
+ default:
+ $msg['Input'] = $fields['URI'];
+ }
+
+ $msg['Line'] = $fields['line'];
+ } else {
+ $msg = array(
+ 'Message Type' => 'Error',
+ 'Error Number' => $errno,
+ 'Level' => $level,
+ 'Fields' => var_export( $fields, TRUE )
+ );
+ }
+
+ $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
+ . '<table>' . "\n";
+
+ foreach( $msg as $label => $details ) {
+ $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
+ }
+
+ $error_details .= '</table>';
+
+ trigger_error( $error_details, E_USER_ERROR );
+ }
+
+ /**
+ * Returns the AXMLS Schema Version of the requested XML schema file.
+ *
+ * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
+ * @see SchemaStringVersion()
+ *
+ * @param string $filename AXMLS schema file
+ * @return string Schema version number or FALSE on error
+ */
+ function SchemaFileVersion( $filename ) {
+ // Open the file
+ if( !($fp = fopen( $filename, 'r' )) ) {
+ // die( 'Unable to open file' );
+ return FALSE;
+ }
+
+ // Process the file
+ while( $data = fread( $fp, 4096 ) ) {
+ if( preg_match( $this->versionRegex, $data, $matches ) ) {
+ return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
+ }
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Returns the AXMLS Schema Version of the provided XML schema string.
+ *
+ * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
+ * @see SchemaFileVersion()
+ *
+ * @param string $xmlstring XML schema string
+ * @return string Schema version number or FALSE on error
+ */
+ function SchemaStringVersion( $xmlstring ) {
+ if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
+ return FALSE;
+ }
+
+ if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
+ return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Extracts an XML schema from an existing database.
+ *
+ * Call this method to create an XML schema string from an existing database.
+ * If the data parameter is set to TRUE, AXMLS will include the data from the database
+ * in the schema.
+ *
+ * @param boolean $data Include data in schema dump
+ * @indent string indentation to use
+ * @prefix string extract only tables with given prefix
+ * @stripprefix strip prefix string when storing in XML schema
+ * @return string Generated XML schema
+ */
+ function ExtractSchema( $data = FALSE, $indent = ' ', $prefix = '' , $stripprefix=false) {
+ $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
+
+ $schema = '<?xml version="1.0"?>' . "\n"
+ . '<schema version="' . $this->schemaVersion . '">' . "\n";
+ if( is_array( $tables = $this->db->MetaTables( 'TABLES' ,false ,($prefix) ? str_replace('_','\_',$prefix).'%' : '') ) ) {
+ foreach( $tables as $table ) {
+ $schema .= $indent
+ . '<table name="'
+ . htmlentities( $stripprefix ? str_replace($prefix, '', $table) : $table )
+ . '">' . "\n";
+
+ // grab details from database
+ $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' );
+ $fields = $this->db->MetaColumns( $table );
+ $indexes = $this->db->MetaIndexes( $table );
+
+ if( is_array( $fields ) ) {
+ foreach( $fields as $details ) {
+ $extra = '';
+ $content = array();
+
+ if( isset($details->max_length) && $details->max_length > 0 ) {
+ $extra .= ' size="' . $details->max_length . '"';
+ }
+
+ if( isset($details->primary_key) && $details->primary_key ) {
+ $content[] = '<KEY/>';
+ } elseif( isset($details->not_null) && $details->not_null ) {
+ $content[] = '<NOTNULL/>';
+ }
+
+ if( isset($details->has_default) && $details->has_default ) {
+ $content[] = '<DEFAULT value="' . htmlentities( $details->default_value ) . '"/>';
+ }
+
+ if( isset($details->auto_increment) && $details->auto_increment ) {
+ $content[] = '<AUTOINCREMENT/>';
+ }
+
+ if( isset($details->unsigned) && $details->unsigned ) {
+ $content[] = '<UNSIGNED/>';
+ }
+
+ // this stops the creation of 'R' columns,
+ // AUTOINCREMENT is used to create auto columns
+ $details->primary_key = 0;
+ $type = $rs->MetaType( $details );
+
+ $schema .= str_repeat( $indent, 2 ) . '<field name="' . htmlentities( $details->name ) . '" type="' . $type . '"' . $extra;
+
+ if( !empty( $content ) ) {
+ $schema .= ">\n" . str_repeat( $indent, 3 )
+ . implode( "\n" . str_repeat( $indent, 3 ), $content ) . "\n"
+ . str_repeat( $indent, 2 ) . '</field>' . "\n";
+ } else {
+ $schema .= "/>\n";
+ }
+ }
+ }
+
+ if( is_array( $indexes ) ) {
+ foreach( $indexes as $index => $details ) {
+ $schema .= str_repeat( $indent, 2 ) . '<index name="' . $index . '">' . "\n";
+
+ if( $details['unique'] ) {
+ $schema .= str_repeat( $indent, 3 ) . '<UNIQUE/>' . "\n";
+ }
+
+ foreach( $details['columns'] as $column ) {
+ $schema .= str_repeat( $indent, 3 ) . '<col>' . htmlentities( $column ) . '</col>' . "\n";
+ }
+
+ $schema .= str_repeat( $indent, 2 ) . '</index>' . "\n";
+ }
+ }
+
+ if( $data ) {
+ $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
+
+ if( is_object( $rs ) && !$rs->EOF ) {
+ $schema .= str_repeat( $indent, 2 ) . "<data>\n";
+
+ while( $row = $rs->FetchRow() ) {
+ foreach( $row as $key => $val ) {
+ if ( $val != htmlentities( $val ) ) {
+ $row[$key] = '<![CDATA[' . $val . ']]>';
+ }
+ }
+
+ $schema .= str_repeat( $indent, 3 ) . '<row><f>' . implode( '</f><f>', $row ) . "</f></row>\n";
+ }
+
+ $schema .= str_repeat( $indent, 2 ) . "</data>\n";
+ }
+ }
+
+ $schema .= $indent . "</table>\n";
+ }
+ }
+
+ $this->db->SetFetchMode( $old_mode );
+
+ $schema .= '</schema>';
+ return $schema;
+ }
+
+ /**
+ * Sets a prefix for database objects
+ *
+ * Call this method to set a standard prefix that will be prepended to all database tables
+ * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
+ *
+ * @param string $prefix Prefix that will be prepended.
+ * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
+ * @return boolean TRUE if successful, else FALSE
+ */
+ function SetPrefix( $prefix = '', $underscore = TRUE ) {
+ switch( TRUE ) {
+ // clear prefix
+ case empty( $prefix ):
+ logMsg( 'Cleared prefix' );
+ $this->objectPrefix = '';
+ return TRUE;
+ // prefix too long
+ case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
+ // prefix contains invalid characters
+ case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
+ logMsg( 'Invalid prefix: ' . $prefix );
+ return FALSE;
+ }
+
+ if( $underscore AND substr( $prefix, -1 ) != '_' ) {
+ $prefix .= '_';
+ }
+
+ // prefix valid
+ logMsg( 'Set prefix: ' . $prefix );
+ $this->objectPrefix = $prefix;
+ return TRUE;
+ }
+
+ /**
+ * Returns an object name with the current prefix prepended.
+ *
+ * @param string $name Name
+ * @return string Prefixed name
+ *
+ * @access private
+ */
+ function prefix( $name = '' ) {
+ // if prefix is set
+ if( !empty( $this->objectPrefix ) ) {
+ // Prepend the object prefix to the table name
+ // prepend after quote if used
+ return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
+ }
+
+ // No prefix set. Use name provided.
+ return $name;
+ }
+
+ /**
+ * Checks if element references a specific platform
+ *
+ * @param string $platform Requested platform
+ * @returns boolean TRUE if platform check succeeds
+ *
+ * @access private
+ */
+ function supportedPlatform( $platform = NULL ) {
+ if( !empty( $platform ) ) {
+ $regex = '/(^|\|)' . $this->db->databaseType . '(\||$)/i';
+
+ if( preg_match( '/^- /', $platform ) ) {
+ if (preg_match ( $regex, substr( $platform, 2 ) ) ) {
+ logMsg( 'Platform ' . $platform . ' is NOT supported' );
+ return FALSE;
+ }
+ } else {
+ if( !preg_match ( $regex, $platform ) ) {
+ logMsg( 'Platform ' . $platform . ' is NOT supported' );
+ return FALSE;
+ }
+ }
+ }
+
+ logMsg( 'Platform ' . $platform . ' is supported' );
+ return TRUE;
+ }
+
+ /**
+ * Clears the array of generated SQL.
+ *
+ * @access private
+ */
+ function clearSQL() {
+ $this->sqlArray = array();
+ }
+
+ /**
+ * Adds SQL into the SQL array.
+ *
+ * @param mixed $sql SQL to Add
+ * @return boolean TRUE if successful, else FALSE.
+ *
+ * @access private
+ */
+ function addSQL( $sql = NULL ) {
+ if( is_array( $sql ) ) {
+ foreach( $sql as $line ) {
+ $this->addSQL( $line );
+ }
+
+ return TRUE;
+ }
+
+ if( is_string( $sql ) ) {
+ $this->sqlArray[] = $sql;
+
+ // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
+ if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
+ $saved = $this->db->debug;
+ $this->db->debug = $this->debug;
+ $ok = $this->db->Execute( $sql );
+ $this->db->debug = $saved;
+
+ if( !$ok ) {
+ if( $this->debug ) {
+ ADOConnection::outp( $this->db->ErrorMsg() );
+ }
+
+ $this->success = 1;
+ }
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Gets the SQL array in the specified format.
+ *
+ * @param string $format Format
+ * @return mixed SQL
+ *
+ * @access private
+ */
+ function getSQL( $format = NULL, $sqlArray = NULL ) {
+ if( !is_array( $sqlArray ) ) {
+ $sqlArray = $this->sqlArray;
+ }
+
+ if( !is_array( $sqlArray ) ) {
+ return FALSE;
+ }
+
+ switch( strtolower( $format ) ) {
+ case 'string':
+ case 'text':
+ return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
+ case'html':
+ return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
+ }
+
+ return $this->sqlArray;
+ }
+
+ /**
+ * Destroys an adoSchema object.
+ *
+ * Call this method to clean up after an adoSchema object that is no longer in use.
+ * @deprecated adoSchema now cleans up automatically.
+ */
+ function Destroy() {
+ if ($this->mgq !== false) {
+ ini_set('magic_quotes_runtime', $this->mgq );
+ }
+ }
+}
+
+/**
+* Message logging function
+*
+* @access private
+*/
+function logMsg( $msg, $title = NULL, $force = FALSE ) {
+ if( XMLS_DEBUG or $force ) {
+ echo '<pre>';
+
+ if( isset( $title ) ) {
+ echo '<h3>' . htmlentities( $title ) . '</h3>';
+ }
+
+ if( @is_object( $this ) ) {
+ echo '[' . get_class( $this ) . '] ';
+ }
+
+ print_r( $msg );
+
+ echo '</pre>';
+ }
+}
diff --git a/vendor/adodb/adodb-php/adodb.inc.php b/vendor/adodb/adodb-php/adodb.inc.php
new file mode 100644
index 0000000..99b533d
--- /dev/null
+++ b/vendor/adodb/adodb-php/adodb.inc.php
@@ -0,0 +1,5051 @@
+<?php
+/*
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * This is the main include file for ADOdb.
+ * Database specific drivers are stored in the adodb/drivers/adodb-*.inc.php
+ *
+ * The ADOdb files are formatted so that doxygen can be used to generate documentation.
+ * Doxygen is a documentation generation tool and can be downloaded from http://doxygen.org/
+ */
+
+/**
+ \mainpage
+
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+ Released under both BSD license and Lesser GPL library license. You can choose which license
+ you prefer.
+
+ PHP's database access functions are not standardised. This creates a need for a database
+ class library to hide the differences between the different database API's (encapsulate
+ the differences) so we can easily switch databases.
+
+ We currently support MySQL, Oracle, Microsoft SQL Server, Sybase, Sybase SQL Anywhere, DB2,
+ Informix, PostgreSQL, FrontBase, Interbase (Firebird and Borland variants), Foxpro, Access,
+ ADO, SAP DB, SQLite and ODBC. We have had successful reports of connecting to Progress and
+ other databases via ODBC.
+ */
+
+if (!defined('_ADODB_LAYER')) {
+ define('_ADODB_LAYER',1);
+
+ // The ADOdb extension is no longer maintained and effectively unsupported
+ // since v5.04. The library will not function properly if it is present.
+ if(defined('ADODB_EXTENSION')) {
+ $msg = "Unsupported ADOdb Extension (v" . ADODB_EXTENSION . ") detected! "
+ . "Disable it to use ADOdb";
+
+ $errorfn = defined('ADODB_ERROR_HANDLER') ? ADODB_ERROR_HANDLER : false;
+ if ($errorfn) {
+ $conn = false;
+ $errorfn('ADOdb', basename(__FILE__), -9999, $msg, null, null, $conn);
+ } else {
+ die($msg . PHP_EOL);
+ }
+ }
+
+ //==============================================================================================
+ // CONSTANT DEFINITIONS
+ //==============================================================================================
+
+
+ /**
+ * Set ADODB_DIR to the directory where this file resides...
+ * This constant was formerly called $ADODB_RootPath
+ */
+ if (!defined('ADODB_DIR')) {
+ define('ADODB_DIR',dirname(__FILE__));
+ }
+
+ //==============================================================================================
+ // GLOBAL VARIABLES
+ //==============================================================================================
+
+ GLOBAL
+ $ADODB_vers, // database version
+ $ADODB_COUNTRECS, // count number of records returned - slows down query
+ $ADODB_CACHE_DIR, // directory to cache recordsets
+ $ADODB_CACHE,
+ $ADODB_CACHE_CLASS,
+ $ADODB_EXTENSION, // ADODB extension installed
+ $ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF
+ $ADODB_FETCH_MODE, // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default...
+ $ADODB_GETONE_EOF,
+ $ADODB_QUOTE_FIELDNAMES; // Allows you to force quotes (backticks) around field names in queries generated by getinsertsql and getupdatesql.
+
+ //==============================================================================================
+ // GLOBAL SETUP
+ //==============================================================================================
+
+ $ADODB_EXTENSION = defined('ADODB_EXTENSION');
+
+ // ********************************************************
+ // Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3).
+ // Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi
+ //
+ // 0 = ignore empty fields. All empty fields in array are ignored.
+ // 1 = force null. All empty, php null and string 'null' fields are changed to sql NULL values.
+ // 2 = force empty. All empty, php null and string 'null' fields are changed to sql empty '' or 0 values.
+ // 3 = force value. Value is left as it is. Php null and string 'null' are set to sql NULL values and empty fields '' are set to empty '' sql values.
+
+ define('ADODB_FORCE_IGNORE',0);
+ define('ADODB_FORCE_NULL',1);
+ define('ADODB_FORCE_EMPTY',2);
+ define('ADODB_FORCE_VALUE',3);
+ // ********************************************************
+
+
+ if (!$ADODB_EXTENSION || ADODB_EXTENSION < 4.0) {
+
+ define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>');
+
+ // allow [ ] @ ` " and . in table names
+ define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)');
+
+ // prefetching used by oracle
+ if (!defined('ADODB_PREFETCH_ROWS')) {
+ define('ADODB_PREFETCH_ROWS',10);
+ }
+
+
+ /**
+ * Fetch mode
+ *
+ * Set global variable $ADODB_FETCH_MODE to one of these constants or use
+ * the SetFetchMode() method to control how recordset fields are returned
+ * when fetching data.
+ *
+ * - NUM: array()
+ * - ASSOC: array('id' => 456, 'name' => 'john')
+ * - BOTH: array(0 => 456, 'id' => 456, 1 => 'john', 'name' => 'john')
+ * - DEFAULT: driver-dependent
+ */
+ define('ADODB_FETCH_DEFAULT', 0);
+ define('ADODB_FETCH_NUM', 1);
+ define('ADODB_FETCH_ASSOC', 2);
+ define('ADODB_FETCH_BOTH', 3);
+
+ /**
+ * Associative array case constants
+ *
+ * By defining the ADODB_ASSOC_CASE constant to one of these values, it is
+ * possible to control the case of field names (associative array's keys)
+ * when operating in ADODB_FETCH_ASSOC fetch mode.
+ * - LOWER: $rs->fields['orderid']
+ * - UPPER: $rs->fields['ORDERID']
+ * - NATIVE: $rs->fields['OrderID'] (or whatever the RDBMS will return)
+ *
+ * The default is to use native case-names.
+ *
+ * NOTE: This functionality is not implemented everywhere, it currently
+ * works only with: mssql, odbc, oci8 and ibase derived drivers
+ */
+ define('ADODB_ASSOC_CASE_LOWER', 0);
+ define('ADODB_ASSOC_CASE_UPPER', 1);
+ define('ADODB_ASSOC_CASE_NATIVE', 2);
+
+
+ if (!defined('TIMESTAMP_FIRST_YEAR')) {
+ define('TIMESTAMP_FIRST_YEAR',100);
+ }
+
+ /**
+ * AutoExecute constants
+ * (moved from adodb-pear.inc.php since they are only used in here)
+ */
+ define('DB_AUTOQUERY_INSERT', 1);
+ define('DB_AUTOQUERY_UPDATE', 2);
+
+
+ // PHP's version scheme makes converting to numbers difficult - workaround
+ $_adodb_ver = (float) PHP_VERSION;
+ if ($_adodb_ver >= 5.2) {
+ define('ADODB_PHPVER',0x5200);
+ } else if ($_adodb_ver >= 5.0) {
+ define('ADODB_PHPVER',0x5000);
+ } else {
+ die("PHP5 or later required. You are running ".PHP_VERSION);
+ }
+ unset($_adodb_ver);
+ }
+
+
+ /**
+ Accepts $src and $dest arrays, replacing string $data
+ */
+ function ADODB_str_replace($src, $dest, $data) {
+ if (ADODB_PHPVER >= 0x4050) {
+ return str_replace($src,$dest,$data);
+ }
+
+ $s = reset($src);
+ $d = reset($dest);
+ while ($s !== false) {
+ $data = str_replace($s,$d,$data);
+ $s = next($src);
+ $d = next($dest);
+ }
+ return $data;
+ }
+
+ function ADODB_Setup() {
+ GLOBAL
+ $ADODB_vers, // database version
+ $ADODB_COUNTRECS, // count number of records returned - slows down query
+ $ADODB_CACHE_DIR, // directory to cache recordsets
+ $ADODB_FETCH_MODE,
+ $ADODB_CACHE,
+ $ADODB_CACHE_CLASS,
+ $ADODB_FORCE_TYPE,
+ $ADODB_GETONE_EOF,
+ $ADODB_QUOTE_FIELDNAMES;
+
+ if (empty($ADODB_CACHE_CLASS)) {
+ $ADODB_CACHE_CLASS = 'ADODB_Cache_File' ;
+ }
+ $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT;
+ $ADODB_FORCE_TYPE = ADODB_FORCE_VALUE;
+ $ADODB_GETONE_EOF = null;
+
+ if (!isset($ADODB_CACHE_DIR)) {
+ $ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp';
+ } else {
+ // do not accept url based paths, eg. http:/ or ftp:/
+ if (strpos($ADODB_CACHE_DIR,'://') !== false) {
+ die("Illegal path http:// or ftp://");
+ }
+ }
+
+
+ // Initialize random number generator for randomizing cache flushes
+ // -- note Since PHP 4.2.0, the seed becomes optional and defaults to a random value if omitted.
+ srand(((double)microtime())*1000000);
+
+ /**
+ * ADODB version as a string.
+ */
+ $ADODB_vers = 'v5.20.14 06-Jan-2019';
+
+ /**
+ * Determines whether recordset->RecordCount() is used.
+ * Set to false for highest performance -- RecordCount() will always return -1 then
+ * for databases that provide "virtual" recordcounts...
+ */
+ if (!isset($ADODB_COUNTRECS)) {
+ $ADODB_COUNTRECS = true;
+ }
+ }
+
+
+ //==============================================================================================
+ // CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB
+ //==============================================================================================
+
+ ADODB_Setup();
+
+ //==============================================================================================
+ // CLASS ADOFieldObject
+ //==============================================================================================
+ /**
+ * Helper class for FetchFields -- holds info on a column
+ */
+ class ADOFieldObject {
+ var $name = '';
+ var $max_length=0;
+ var $type="";
+/*
+ // additional fields by dannym... (danny_milo@yahoo.com)
+ var $not_null = false;
+ // actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^
+ // so we can as well make not_null standard (leaving it at "false" does not harm anyways)
+
+ var $has_default = false; // this one I have done only in mysql and postgres for now ...
+ // others to come (dannym)
+ var $default_value; // default, if any, and supported. Check has_default first.
+*/
+ }
+
+
+ function _adodb_safedate($s) {
+ return str_replace(array("'", '\\'), '', $s);
+ }
+
+ // parse date string to prevent injection attack
+ // date string will have one quote at beginning e.g. '3434343'
+ function _adodb_safedateq($s) {
+ $len = strlen($s);
+ if ($s[0] !== "'") {
+ $s2 = "'".$s[0];
+ } else {
+ $s2 = "'";
+ }
+ for($i=1; $i<$len; $i++) {
+ $ch = $s[$i];
+ if ($ch === '\\') {
+ $s2 .= "'";
+ break;
+ } elseif ($ch === "'") {
+ $s2 .= $ch;
+ break;
+ }
+
+ $s2 .= $ch;
+ }
+
+ return strlen($s2) == 0 ? 'null' : $s2;
+ }
+
+
+ // for transaction handling
+
+ function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) {
+ //print "Errorno ($fn errno=$errno m=$errmsg) ";
+ $thisConnection->_transOK = false;
+ if ($thisConnection->_oldRaiseFn) {
+ $fn = $thisConnection->_oldRaiseFn;
+ $fn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection);
+ }
+ }
+
+ //------------------
+ // class for caching
+ class ADODB_Cache_File {
+
+ var $createdir = true; // requires creation of temp dirs
+
+ function __construct() {
+ global $ADODB_INCLUDED_CSV;
+ if (empty($ADODB_INCLUDED_CSV)) {
+ include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
+ }
+ }
+
+ // write serialised recordset to cache item/file
+ function writecache($filename, $contents, $debug, $secs2cache) {
+ return adodb_write_file($filename, $contents,$debug);
+ }
+
+ // load serialised recordset and unserialise it
+ function &readcache($filename, &$err, $secs2cache, $rsClass) {
+ $rs = csv2rs($filename,$err,$secs2cache,$rsClass);
+ return $rs;
+ }
+
+ // flush all items in cache
+ function flushall($debug=false) {
+ global $ADODB_CACHE_DIR;
+
+ $rez = false;
+
+ if (strlen($ADODB_CACHE_DIR) > 1) {
+ $rez = $this->_dirFlush($ADODB_CACHE_DIR);
+ if ($debug) {
+ ADOConnection::outp( "flushall: $ADODB_CACHE_DIR<br><pre>\n". $rez."</pre>");
+ }
+ }
+ return $rez;
+ }
+
+ // flush one file in cache
+ function flushcache($f, $debug=false) {
+ if (!@unlink($f)) {
+ if ($debug) {
+ ADOConnection::outp( "flushcache: failed for $f");
+ }
+ }
+ }
+
+ function getdirname($hash) {
+ global $ADODB_CACHE_DIR;
+ if (!isset($this->notSafeMode)) {
+ $this->notSafeMode = !ini_get('safe_mode');
+ }
+ return ($this->notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($hash,0,2) : $ADODB_CACHE_DIR;
+ }
+
+ // create temp directories
+ function createdir($hash, $debug) {
+ global $ADODB_CACHE_PERMS;
+
+ $dir = $this->getdirname($hash);
+ if ($this->notSafeMode && !file_exists($dir)) {
+ $oldu = umask(0);
+ if (!@mkdir($dir, empty($ADODB_CACHE_PERMS) ? 0771 : $ADODB_CACHE_PERMS)) {
+ if(!is_dir($dir) && $debug) {
+ ADOConnection::outp("Cannot create $dir");
+ }
+ }
+ umask($oldu);
+ }
+
+ return $dir;
+ }
+
+ /**
+ * Private function to erase all of the files and subdirectories in a directory.
+ *
+ * Just specify the directory, and tell it if you want to delete the directory or just clear it out.
+ * Note: $kill_top_level is used internally in the function to flush subdirectories.
+ */
+ function _dirFlush($dir, $kill_top_level = false) {
+ if(!$dh = @opendir($dir)) return;
+
+ while (($obj = readdir($dh))) {
+ if($obj=='.' || $obj=='..') continue;
+ $f = $dir.'/'.$obj;
+
+ if (strpos($obj,'.cache')) {
+ @unlink($f);
+ }
+ if (is_dir($f)) {
+ $this->_dirFlush($f, true);
+ }
+ }
+ if ($kill_top_level === true) {
+ @rmdir($dir);
+ }
+ return true;
+ }
+ }
+
+ //==============================================================================================
+ // CLASS ADOConnection
+ //==============================================================================================
+
+ /**
+ * Connection object. For connecting to databases, and executing queries.
+ */
+ abstract class ADOConnection {
+ //
+ // PUBLIC VARS
+ //
+ var $dataProvider = 'native';
+ var $databaseType = ''; /// RDBMS currently in use, eg. odbc, mysql, mssql
+ var $database = ''; /// Name of database to be used.
+ var $host = ''; /// The hostname of the database server
+ var $port = ''; /// The port of the database server
+ var $user = ''; /// The username which is used to connect to the database server.
+ var $password = ''; /// Password for the username. For security, we no longer store it.
+ var $debug = false; /// if set to true will output sql statements
+ var $maxblobsize = 262144; /// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro
+ var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase
+ var $substr = 'substr'; /// substring operator
+ var $length = 'length'; /// string length ofperator
+ var $random = 'rand()'; /// random function
+ var $upperCase = 'upper'; /// uppercase function
+ var $fmtDate = "'Y-m-d'"; /// used by DBDate() as the default date format used by the database
+ var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt.
+ var $true = '1'; /// string that represents TRUE for a database
+ var $false = '0'; /// string that represents FALSE for a database
+ var $replaceQuote = "\\'"; /// string to use to replace quotes
+ var $nameQuote = '"'; /// string to use to quote identifiers and names
+ var $charSet=false; /// character set to use - only for interbase, postgres and oci8
+ var $metaDatabasesSQL = '';
+ var $metaTablesSQL = '';
+ var $uniqueOrderBy = false; /// All order by columns have to be unique
+ var $emptyDate = '&nbsp;';
+ var $emptyTimeStamp = '&nbsp;';
+ var $lastInsID = false;
+ //--
+ var $hasInsertID = false; /// supports autoincrement ID?
+ var $hasAffectedRows = false; /// supports affected rows for update/delete?
+ var $hasTop = false; /// support mssql/access SELECT TOP 10 * FROM TABLE
+ var $hasLimit = false; /// support pgsql/mysql SELECT * FROM TABLE LIMIT 10
+ var $readOnly = false; /// this is a readonly database - used by phpLens
+ var $hasMoveFirst = false; /// has ability to run MoveFirst(), scrolling backwards
+ var $hasGenID = false; /// can generate sequences using GenID();
+ var $hasTransactions = true; /// has transactions
+ //--
+ var $genID = 0; /// sequence id used by GenID();
+ var $raiseErrorFn = false; /// error function to call
+ var $isoDates = false; /// accepts dates in ISO format
+ var $cacheSecs = 3600; /// cache for 1 hour
+
+ // memcache
+ var $memCache = false; /// should we use memCache instead of caching in files
+ var $memCacheHost; /// memCache host
+ var $memCachePort = 11211; /// memCache port
+ var $memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+
+ var $sysDate = false; /// name of function that returns the current date
+ var $sysTimeStamp = false; /// name of function that returns the current timestamp
+ var $sysUTimeStamp = false; // name of function that returns the current timestamp accurate to the microsecond or nearest fraction
+ var $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets
+
+ var $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' '
+ var $numCacheHits = 0;
+ var $numCacheMisses = 0;
+ var $pageExecuteCountRows = true;
+ var $uniqueSort = false; /// indicates that all fields in order by must be unique
+ var $leftOuter = false; /// operator to use for left outer join in WHERE clause
+ var $rightOuter = false; /// operator to use for right outer join in WHERE clause
+ var $ansiOuter = false; /// whether ansi outer join syntax supported
+ var $autoRollback = false; // autoRollback on PConnect().
+ var $poorAffectedRows = false; // affectedRows not working or unreliable
+
+ var $fnExecute = false;
+ var $fnCacheExecute = false;
+ var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char
+ var $rsPrefix = "ADORecordSet_";
+
+ var $autoCommit = true; /// do not modify this yourself - actually private
+ var $transOff = 0; /// temporarily disable transactions
+ var $transCnt = 0; /// count of nested transactions
+
+ var $fetchMode=false;
+
+ var $null2null = 'null'; // in autoexecute/getinsertsql/getupdatesql, this value will be converted to a null
+ var $bulkBind = false; // enable 2D Execute array
+ //
+ // PRIVATE VARS
+ //
+ var $_oldRaiseFn = false;
+ var $_transOK = null;
+ var $_connectionID = false; /// The returned link identifier whenever a successful database connection is made.
+ var $_errorMsg = false; /// A variable which was used to keep the returned last error message. The value will
+ /// then returned by the errorMsg() function
+ var $_errorCode = false; /// Last error code, not guaranteed to be used - only by oci8
+ var $_queryID = false; /// This variable keeps the last created result link identifier
+
+ var $_isPersistentConnection = false; /// A boolean variable to state whether its a persistent connection or normal connection. */
+ var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters.
+ var $_evalAll = false;
+ var $_affected = false;
+ var $_logsql = false;
+ var $_transmode = ''; // transaction mode
+
+ /*
+ * Additional parameters that may be passed to drivers in the connect string
+ * Driver must be coded to accept the parameters
+ */
+ protected $connectionParameters = array();
+
+ /**
+ * Adds a parameter to the connection string.
+ *
+ * These parameters are added to the connection string when connecting,
+ * if the driver is coded to use it.
+ *
+ * @param string $parameter The name of the parameter to set
+ * @param string $value The value of the parameter
+ *
+ * @return null
+ *
+ * @example, for mssqlnative driver ('CharacterSet','UTF-8')
+ */
+ final public function setConnectionParameter($parameter,$value)
+ {
+
+ $this->connectionParameters[$parameter] = $value;
+
+ }
+
+ static function Version() {
+ global $ADODB_vers;
+
+ // Semantic Version number matching regex
+ $regex = '^[vV]?(\d+\.\d+\.\d+' // Version number (X.Y.Z) with optional 'V'
+ . '(?:-(?:' // Optional preprod version: a '-'
+ . 'dev|' // followed by 'dev'
+ . '(?:(?:alpha|beta|rc)(?:\.\d+))' // or a preprod suffix and version number
+ . '))?)(?:\s|$)'; // Whitespace or end of string
+
+ if (!preg_match("/$regex/", $ADODB_vers, $matches)) {
+ // This should normally not happen... Return whatever is between the start
+ // of the string and the first whitespace (or the end of the string).
+ self::outp("Invalid version number: '$ADODB_vers'", 'Version');
+ $regex = '^[vV]?(.*?)(?:\s|$)';
+ preg_match("/$regex/", $ADODB_vers, $matches);
+ }
+ return $matches[1];
+ }
+
+ /**
+ Get server version info...
+
+ @returns An array with 2 elements: $arr['string'] is the description string,
+ and $arr[version] is the version (also a string).
+ */
+ function ServerInfo() {
+ return array('description' => '', 'version' => '');
+ }
+
+ function IsConnected() {
+ return !empty($this->_connectionID);
+ }
+
+ function _findvers($str) {
+ if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) {
+ return $arr[1];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * All error messages go through this bottleneck function.
+ * You can define your own handler by defining the function name in ADODB_OUTP.
+ */
+ static function outp($msg,$newline=true) {
+ global $ADODB_FLUSH,$ADODB_OUTP;
+
+ if (defined('ADODB_OUTP')) {
+ $fn = ADODB_OUTP;
+ $fn($msg,$newline);
+ return;
+ } else if (isset($ADODB_OUTP)) {
+ $fn = $ADODB_OUTP;
+ $fn($msg,$newline);
+ return;
+ }
+
+ if ($newline) {
+ $msg .= "<br>\n";
+ }
+
+ if (isset($_SERVER['HTTP_USER_AGENT']) || !$newline) {
+ echo $msg;
+ } else {
+ echo strip_tags($msg);
+ }
+
+
+ if (!empty($ADODB_FLUSH) && ob_get_length() !== false) {
+ flush(); // do not flush if output buffering enabled - useless - thx to Jesse Mullan
+ }
+
+ }
+
+ function Time() {
+ $rs = $this->_Execute("select $this->sysTimeStamp");
+ if ($rs && !$rs->EOF) {
+ return $this->UnixTimeStamp(reset($rs->fields));
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses the hostname to extract the port.
+ * Overwrites $this->host and $this->port, only if a port is specified.
+ * The Hostname can be fully or partially qualified,
+ * ie: "db.mydomain.com:5432" or "ldaps://ldap.mydomain.com:636"
+ * Any specified scheme such as ldap:// or ldaps:// is maintained.
+ */
+ protected function parseHostNameAndPort() {
+ $parsed_url = parse_url($this->host);
+ if (is_array($parsed_url) && isset($parsed_url['host']) && isset($parsed_url['port'])) {
+ if ( isset($parsed_url['scheme']) ) {
+ // If scheme is specified (ie: ldap:// or ldaps://, make sure we retain that.
+ $this->host = $parsed_url['scheme'] . "://" . $parsed_url['host'];
+ } else {
+ $this->host = $parsed_url['host'];
+ }
+ $this->port = $parsed_url['port'];
+ }
+ }
+
+ /**
+ * Connect to database
+ *
+ * @param [argHostname] Host to connect to
+ * @param [argUsername] Userid to login
+ * @param [argPassword] Associated password
+ * @param [argDatabaseName] database
+ * @param [forceNew] force new connection
+ *
+ * @return true or false
+ */
+ function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) {
+ if ($argHostname != "") {
+ $this->host = $argHostname;
+ }
+ // Overwrites $this->host and $this->port if a port is specified.
+ $this->parseHostNameAndPort();
+
+ if ($argUsername != "") {
+ $this->user = $argUsername;
+ }
+ if ($argPassword != "") {
+ $this->password = 'not stored'; // not stored for security reasons
+ }
+ if ($argDatabaseName != "") {
+ $this->database = $argDatabaseName;
+ }
+
+ $this->_isPersistentConnection = false;
+
+ if ($forceNew) {
+ if ($rez=$this->_nconnect($this->host, $this->user, $argPassword, $this->database)) {
+ return true;
+ }
+ } else {
+ if ($rez=$this->_connect($this->host, $this->user, $argPassword, $this->database)) {
+ return true;
+ }
+ }
+ if (isset($rez)) {
+ $err = $this->ErrorMsg();
+ $errno = $this->ErrorNo();
+ if (empty($err)) {
+ $err = "Connection error to server '$argHostname' with user '$argUsername'";
+ }
+ } else {
+ $err = "Missing extension for ".$this->dataProvider;
+ $errno = 0;
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType, 'CONNECT', $errno, $err, $this->host, $this->database, $this);
+ }
+
+ $this->_connectionID = false;
+ if ($this->debug) {
+ ADOConnection::outp( $this->host.': '.$err);
+ }
+ return false;
+ }
+
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
+ }
+
+
+ /**
+ * Always force a new connection to database - currently only works with oracle
+ *
+ * @param [argHostname] Host to connect to
+ * @param [argUsername] Userid to login
+ * @param [argPassword] Associated password
+ * @param [argDatabaseName] database
+ *
+ * @return true or false
+ */
+ function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
+ return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true);
+ }
+
+ /**
+ * Establish persistent connect to database
+ *
+ * @param [argHostname] Host to connect to
+ * @param [argUsername] Userid to login
+ * @param [argPassword] Associated password
+ * @param [argDatabaseName] database
+ *
+ * @return return true or false
+ */
+ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
+
+ if (defined('ADODB_NEVER_PERSIST')) {
+ return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName);
+ }
+
+ if ($argHostname != "") {
+ $this->host = $argHostname;
+ }
+ // Overwrites $this->host and $this->port if a port is specified.
+ $this->parseHostNameAndPort();
+
+ if ($argUsername != "") {
+ $this->user = $argUsername;
+ }
+ if ($argPassword != "") {
+ $this->password = 'not stored';
+ }
+ if ($argDatabaseName != "") {
+ $this->database = $argDatabaseName;
+ }
+
+ $this->_isPersistentConnection = true;
+
+ if ($rez = $this->_pconnect($this->host, $this->user, $argPassword, $this->database)) {
+ return true;
+ }
+ if (isset($rez)) {
+ $err = $this->ErrorMsg();
+ if (empty($err)) {
+ $err = "Connection error to server '$argHostname' with user '$argUsername'";
+ }
+ $ret = false;
+ } else {
+ $err = "Missing extension for ".$this->dataProvider;
+ $ret = 0;
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
+ }
+
+ $this->_connectionID = false;
+ if ($this->debug) {
+ ADOConnection::outp( $this->host.': '.$err);
+ }
+ return $ret;
+ }
+
+ function outp_throw($msg,$src='WARN',$sql='') {
+ if (defined('ADODB_ERROR_HANDLER') && ADODB_ERROR_HANDLER == 'adodb_throw') {
+ adodb_throw($this->databaseType,$src,-9999,$msg,$sql,false,$this);
+ return;
+ }
+ ADOConnection::outp($msg);
+ }
+
+ // create cache class. Code is backward compat with old memcache implementation
+ function _CreateCache() {
+ global $ADODB_CACHE, $ADODB_CACHE_CLASS;
+
+ if ($this->memCache) {
+ global $ADODB_INCLUDED_MEMCACHE;
+
+ if (empty($ADODB_INCLUDED_MEMCACHE)) {
+ include_once(ADODB_DIR.'/adodb-memcache.lib.inc.php');
+ }
+ $ADODB_CACHE = new ADODB_Cache_MemCache($this);
+ } else {
+ $ADODB_CACHE = new $ADODB_CACHE_CLASS($this);
+ }
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false) {
+ if (!$col) {
+ $col = $this->sysDate;
+ }
+ return $col; // child class implement
+ }
+
+ /**
+ * Should prepare the sql statement and return the stmt resource.
+ * For databases that do not support this, we return the $sql. To ensure
+ * compatibility with databases that do not support prepare:
+ *
+ * $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
+ * $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
+ * $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
+ *
+ * @param sql SQL to send to database
+ *
+ * @return return FALSE, or the prepared statement, or the original sql if
+ * if the database does not support prepare.
+ *
+ */
+ function Prepare($sql) {
+ return $sql;
+ }
+
+ /**
+ * Some databases, eg. mssql require a different function for preparing
+ * stored procedures. So we cannot use Prepare().
+ *
+ * Should prepare the stored procedure and return the stmt resource.
+ * For databases that do not support this, we return the $sql. To ensure
+ * compatibility with databases that do not support prepare:
+ *
+ * @param sql SQL to send to database
+ *
+ * @return return FALSE, or the prepared statement, or the original sql if
+ * if the database does not support prepare.
+ *
+ */
+ function PrepareSP($sql,$param=true) {
+ return $this->Prepare($sql,$param);
+ }
+
+ /**
+ * PEAR DB Compat
+ */
+ function Quote($s) {
+ return $this->qstr($s,false);
+ }
+
+ /**
+ * Requested by "Karsten Dambekalns" <k.dambekalns@fishfarm.de>
+ */
+ function QMagic($s) {
+ return $this->qstr($s,get_magic_quotes_gpc());
+ }
+
+ function q(&$s) {
+ //if (!empty($this->qNull && $s == 'null') {
+ // return $s;
+ //}
+ $s = $this->qstr($s,false);
+ }
+
+ /**
+ * PEAR DB Compat - do not use internally.
+ */
+ function ErrorNative() {
+ return $this->ErrorNo();
+ }
+
+
+ /**
+ * PEAR DB Compat - do not use internally.
+ */
+ function nextId($seq_name) {
+ return $this->GenID($seq_name);
+ }
+
+ /**
+ * Lock a row, will escalate and lock the table if row locking not supported
+ * will normally free the lock at the end of the transaction
+ *
+ * @param $table name of table to lock
+ * @param $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
+ */
+ function RowLock($table,$where,$col='1 as adodbignore') {
+ return false;
+ }
+
+ function CommitLock($table) {
+ return $this->CommitTrans();
+ }
+
+ function RollbackLock($table) {
+ return $this->RollbackTrans();
+ }
+
+ /**
+ * PEAR DB Compat - do not use internally.
+ *
+ * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
+ * for easy porting :-)
+ *
+ * @param mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
+ * @returns The previous fetch mode
+ */
+ function SetFetchMode($mode) {
+ $old = $this->fetchMode;
+ $this->fetchMode = $mode;
+
+ if ($old === false) {
+ global $ADODB_FETCH_MODE;
+ return $ADODB_FETCH_MODE;
+ }
+ return $old;
+ }
+
+
+ /**
+ * PEAR DB Compat - do not use internally.
+ */
+ function Query($sql, $inputarr=false) {
+ $rs = $this->Execute($sql, $inputarr);
+ if (!$rs && defined('ADODB_PEAR')) {
+ return ADODB_PEAR_Error();
+ }
+ return $rs;
+ }
+
+
+ /**
+ * PEAR DB Compat - do not use internally
+ */
+ function LimitQuery($sql, $offset, $count, $params=false) {
+ $rs = $this->SelectLimit($sql, $count, $offset, $params);
+ if (!$rs && defined('ADODB_PEAR')) {
+ return ADODB_PEAR_Error();
+ }
+ return $rs;
+ }
+
+
+ /**
+ * PEAR DB Compat - do not use internally
+ */
+ function Disconnect() {
+ return $this->Close();
+ }
+
+ /**
+ * Returns a placeholder for query parameters
+ * e.g. $DB->Param('a') will return
+ * - '?' for most databases
+ * - ':a' for Oracle
+ * - '$1', '$2', etc. for PostgreSQL
+ * @param string $name parameter's name, false to force a reset of the
+ * number to 1 (for databases that require positioned
+ * params such as PostgreSQL; note that ADOdb will
+ * automatically reset this when executing a query )
+ * @param string $type (unused)
+ * @return string query parameter placeholder
+ */
+ function Param($name,$type='C') {
+ return '?';
+ }
+
+ /*
+ InParameter and OutParameter are self-documenting versions of Parameter().
+ */
+ function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
+ return $this->Parameter($stmt,$var,$name,false,$maxLen,$type);
+ }
+
+ /*
+ */
+ function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
+ return $this->Parameter($stmt,$var,$name,true,$maxLen,$type);
+
+ }
+
+
+ /*
+ Usage in oracle
+ $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
+ $db->Parameter($stmt,$id,'myid');
+ $db->Parameter($stmt,$group,'group',64);
+ $db->Execute();
+
+ @param $stmt Statement returned by Prepare() or PrepareSP().
+ @param $var PHP variable to bind to
+ @param $name Name of stored procedure variable name to bind to.
+ @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8.
+ @param [$maxLen] Holds an maximum length of the variable.
+ @param [$type] The data type of $var. Legal values depend on driver.
+
+ */
+ function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) {
+ return false;
+ }
+
+
+ function IgnoreErrors($saveErrs=false) {
+ if (!$saveErrs) {
+ $saveErrs = array($this->raiseErrorFn,$this->_transOK);
+ $this->raiseErrorFn = false;
+ return $saveErrs;
+ } else {
+ $this->raiseErrorFn = $saveErrs[0];
+ $this->_transOK = $saveErrs[1];
+ }
+ }
+
+ /**
+ * Improved method of initiating a transaction. Used together with CompleteTrans().
+ * Advantages include:
+ *
+ * a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans.
+ * Only the outermost block is treated as a transaction.<br>
+ * b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br>
+ * c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block
+ * are disabled, making it backward compatible.
+ */
+ function StartTrans($errfn = 'ADODB_TransMonitor') {
+ if ($this->transOff > 0) {
+ $this->transOff += 1;
+ return true;
+ }
+
+ $this->_oldRaiseFn = $this->raiseErrorFn;
+ $this->raiseErrorFn = $errfn;
+ $this->_transOK = true;
+
+ if ($this->debug && $this->transCnt > 0) {
+ ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans");
+ }
+ $ok = $this->BeginTrans();
+ $this->transOff = 1;
+ return $ok;
+ }
+
+
+ /**
+ Used together with StartTrans() to end a transaction. Monitors connection
+ for sql errors, and will commit or rollback as appropriate.
+
+ @autoComplete if true, monitor sql errors and commit and rollback as appropriate,
+ and if set to false force rollback even if no SQL error detected.
+ @returns true on commit, false on rollback.
+ */
+ function CompleteTrans($autoComplete = true) {
+ if ($this->transOff > 1) {
+ $this->transOff -= 1;
+ return true;
+ }
+ $this->raiseErrorFn = $this->_oldRaiseFn;
+
+ $this->transOff = 0;
+ if ($this->_transOK && $autoComplete) {
+ if (!$this->CommitTrans()) {
+ $this->_transOK = false;
+ if ($this->debug) {
+ ADOConnection::outp("Smart Commit failed");
+ }
+ } else {
+ if ($this->debug) {
+ ADOConnection::outp("Smart Commit occurred");
+ }
+ }
+ } else {
+ $this->_transOK = false;
+ $this->RollbackTrans();
+ if ($this->debug) {
+ ADOCOnnection::outp("Smart Rollback occurred");
+ }
+ }
+
+ return $this->_transOK;
+ }
+
+ /*
+ At the end of a StartTrans/CompleteTrans block, perform a rollback.
+ */
+ function FailTrans() {
+ if ($this->debug)
+ if ($this->transOff == 0) {
+ ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans");
+ } else {
+ ADOConnection::outp("FailTrans was called");
+ adodb_backtrace();
+ }
+ $this->_transOK = false;
+ }
+
+ /**
+ Check if transaction has failed, only for Smart Transactions.
+ */
+ function HasFailedTrans() {
+ if ($this->transOff > 0) {
+ return $this->_transOK == false;
+ }
+ return false;
+ }
+
+ /**
+ * Execute SQL
+ *
+ * @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
+ * @param [inputarr] holds the input data to bind to. Null elements will be set to null.
+ * @return RecordSet or false
+ */
+ function Execute($sql,$inputarr=false) {
+ if ($this->fnExecute) {
+ $fn = $this->fnExecute;
+ $ret = $fn($this,$sql,$inputarr);
+ if (isset($ret)) {
+ return $ret;
+ }
+ }
+ if ($inputarr !== false) {
+ if (!is_array($inputarr)) {
+ $inputarr = array($inputarr);
+ }
+
+ $element0 = reset($inputarr);
+ # is_object check because oci8 descriptors can be passed in
+ $array_2d = $this->bulkBind && is_array($element0) && !is_object(reset($element0));
+
+ //remove extra memory copy of input -mikefedyk
+ unset($element0);
+
+ if (!is_array($sql) && !$this->_bindInputArray) {
+ // @TODO this would consider a '?' within a string as a parameter...
+ $sqlarr = explode('?',$sql);
+ $nparams = sizeof($sqlarr)-1;
+
+ if (!$array_2d) {
+ // When not Bind Bulk - convert to array of arguments list
+ $inputarr = array($inputarr);
+ } else {
+ // Bulk bind - Make sure all list of params have the same number of elements
+ $countElements = array_map('count', $inputarr);
+ if (1 != count(array_unique($countElements))) {
+ $this->outp_throw(
+ "[bulk execute] Input array has different number of params [" . print_r($countElements, true) . "].",
+ 'Execute'
+ );
+ return false;
+ }
+ unset($countElements);
+ }
+ // Make sure the number of parameters provided in the input
+ // array matches what the query expects
+ $element0 = reset($inputarr);
+ if ($nparams != count($element0)) {
+ $this->outp_throw(
+ "Input array has " . count($element0) .
+ " params, does not match query: '" . htmlspecialchars($sql) . "'",
+ 'Execute'
+ );
+ return false;
+ }
+
+ // clean memory
+ unset($element0);
+
+ foreach($inputarr as $arr) {
+ $sql = ''; $i = 0;
+ foreach ($arr as $v) {
+ $sql .= $sqlarr[$i];
+ // from Ron Baldwin <ron.baldwin#sourceprose.com>
+ // Only quote string types
+ $typ = gettype($v);
+ if ($typ == 'string') {
+ //New memory copy of input created here -mikefedyk
+ $sql .= $this->qstr($v);
+ } else if ($typ == 'double') {
+ $sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1
+ } else if ($typ == 'boolean') {
+ $sql .= $v ? $this->true : $this->false;
+ } else if ($typ == 'object') {
+ if (method_exists($v, '__toString')) {
+ $sql .= $this->qstr($v->__toString());
+ } else {
+ $sql .= $this->qstr((string) $v);
+ }
+ } else if ($v === null) {
+ $sql .= 'NULL';
+ } else {
+ $sql .= $v;
+ }
+ $i += 1;
+
+ if ($i == $nparams) {
+ break;
+ }
+ } // while
+ if (isset($sqlarr[$i])) {
+ $sql .= $sqlarr[$i];
+ if ($i+1 != sizeof($sqlarr)) {
+ $this->outp_throw( "Input Array does not match ?: ".htmlspecialchars($sql),'Execute');
+ }
+ } else if ($i != sizeof($sqlarr)) {
+ $this->outp_throw( "Input array does not match ?: ".htmlspecialchars($sql),'Execute');
+ }
+
+ $ret = $this->_Execute($sql);
+ if (!$ret) {
+ return $ret;
+ }
+ }
+ } else {
+ if ($array_2d) {
+ if (is_string($sql)) {
+ $stmt = $this->Prepare($sql);
+ } else {
+ $stmt = $sql;
+ }
+
+ foreach($inputarr as $arr) {
+ $ret = $this->_Execute($stmt,$arr);
+ if (!$ret) {
+ return $ret;
+ }
+ }
+ } else {
+ $ret = $this->_Execute($sql,$inputarr);
+ }
+ }
+ } else {
+ $ret = $this->_Execute($sql,false);
+ }
+
+ return $ret;
+ }
+
+ function _Execute($sql,$inputarr=false) {
+ // ExecuteCursor() may send non-string queries (such as arrays),
+ // so we need to ignore those.
+ if( is_string($sql) ) {
+ // Strips keyword used to help generate SELECT COUNT(*) queries
+ // from SQL if it exists.
+ $sql = ADODB_str_replace( '_ADODB_COUNT', '', $sql );
+ }
+
+ if ($this->debug) {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ $this->_queryID = _adodb_debug_execute($this, $sql,$inputarr);
+ } else {
+ $this->_queryID = @$this->_query($sql,$inputarr);
+ }
+
+ // ************************
+ // OK, query executed
+ // ************************
+
+ // error handling if query fails
+ if ($this->_queryID === false) {
+ if ($this->debug == 99) {
+ adodb_backtrace(true,5);
+ }
+ $fn = $this->raiseErrorFn;
+ if ($fn) {
+ $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
+ }
+ return false;
+ }
+
+ // return simplified recordset for inserts/updates/deletes with lower overhead
+ if ($this->_queryID === true) {
+ $rsclass = $this->rsPrefix.'empty';
+ $rs = (class_exists($rsclass)) ? new $rsclass(): new ADORecordSet_empty();
+
+ return $rs;
+ }
+
+ // return real recordset from select statement
+ $rsclass = $this->rsPrefix.$this->databaseType;
+ $rs = new $rsclass($this->_queryID,$this->fetchMode);
+ $rs->connection = $this; // Pablo suggestion
+ $rs->Init();
+ if (is_array($sql)) {
+ $rs->sql = $sql[0];
+ } else {
+ $rs->sql = $sql;
+ }
+ if ($rs->_numOfRows <= 0) {
+ global $ADODB_COUNTRECS;
+ if ($ADODB_COUNTRECS) {
+ if (!$rs->EOF) {
+ $rs = $this->_rs2rs($rs,-1,-1,!is_array($sql));
+ $rs->_queryID = $this->_queryID;
+ } else
+ $rs->_numOfRows = 0;
+ }
+ }
+ return $rs;
+ }
+
+ function CreateSequence($seqname='adodbseq',$startID=1) {
+ if (empty($this->_genSeqSQL)) {
+ return false;
+ }
+ return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
+ }
+
+ function DropSequence($seqname='adodbseq') {
+ if (empty($this->_dropSeqSQL)) {
+ return false;
+ }
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ /**
+ * Generates a sequence id and stores it in $this->genID;
+ * GenID is only available if $this->hasGenID = true;
+ *
+ * @param seqname name of sequence to use
+ * @param startID if sequence does not exist, start at this ID
+ * @return 0 if not supported, otherwise a sequence id
+ */
+ function GenID($seqname='adodbseq',$startID=1) {
+ if (!$this->hasGenID) {
+ return 0; // formerly returns false pre 1.60
+ }
+
+ $getnext = sprintf($this->_genIDSQL,$seqname);
+
+ $holdtransOK = $this->_transOK;
+
+ $save_handler = $this->raiseErrorFn;
+ $this->raiseErrorFn = '';
+ @($rs = $this->Execute($getnext));
+ $this->raiseErrorFn = $save_handler;
+
+ if (!$rs) {
+ $this->_transOK = $holdtransOK; //if the status was ok before reset
+ $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
+ $rs = $this->Execute($getnext);
+ }
+ if ($rs && !$rs->EOF) {
+ $this->genID = reset($rs->fields);
+ } else {
+ $this->genID = 0; // false
+ }
+
+ if ($rs) {
+ $rs->Close();
+ }
+
+ return $this->genID;
+ }
+
+ /**
+ * @param $table string name of the table, not needed by all databases (eg. mysql), default ''
+ * @param $column string name of the column, not needed by all databases (eg. mysql), default ''
+ * @return the last inserted ID. Not all databases support this.
+ */
+ function Insert_ID($table='',$column='') {
+ if ($this->_logsql && $this->lastInsID) {
+ return $this->lastInsID;
+ }
+ if ($this->hasInsertID) {
+ return $this->_insertid($table,$column);
+ }
+ if ($this->debug) {
+ ADOConnection::outp( '<p>Insert_ID error</p>');
+ adodb_backtrace();
+ }
+ return false;
+ }
+
+
+ /**
+ * Portable Insert ID. Pablo Roca <pabloroca#mvps.org>
+ *
+ * @return the last inserted ID. All databases support this. But aware possible
+ * problems in multiuser environments. Heavy test this before deploying.
+ */
+ function PO_Insert_ID($table="", $id="") {
+ if ($this->hasInsertID){
+ return $this->Insert_ID($table,$id);
+ } else {
+ return $this->GetOne("SELECT MAX($id) FROM $table");
+ }
+ }
+
+ /**
+ * @return # rows affected by UPDATE/DELETE
+ */
+ function Affected_Rows() {
+ if ($this->hasAffectedRows) {
+ if ($this->fnExecute === 'adodb_log_sql') {
+ if ($this->_logsql && $this->_affected !== false) {
+ return $this->_affected;
+ }
+ }
+ $val = $this->_affectedrows();
+ return ($val < 0) ? false : $val;
+ }
+
+ if ($this->debug) {
+ ADOConnection::outp( '<p>Affected_Rows error</p>',false);
+ }
+ return false;
+ }
+
+
+ /**
+ * @return the last error message
+ */
+ function ErrorMsg() {
+ if ($this->_errorMsg) {
+ return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
+ } else {
+ return '';
+ }
+ }
+
+
+ /**
+ * @return the last error number. Normally 0 means no error.
+ */
+ function ErrorNo() {
+ return ($this->_errorMsg) ? -1 : 0;
+ }
+
+ function MetaError($err=false) {
+ include_once(ADODB_DIR."/adodb-error.inc.php");
+ if ($err === false) {
+ $err = $this->ErrorNo();
+ }
+ return adodb_error($this->dataProvider,$this->databaseType,$err);
+ }
+
+ function MetaErrorMsg($errno) {
+ include_once(ADODB_DIR."/adodb-error.inc.php");
+ return adodb_errormsg($errno);
+ }
+
+ /**
+ * @returns an array with the primary key columns in it.
+ */
+ function MetaPrimaryKeys($table, $owner=false) {
+ // owner not used in base class - see oci8
+ $p = array();
+ $objs = $this->MetaColumns($table);
+ if ($objs) {
+ foreach($objs as $v) {
+ if (!empty($v->primary_key)) {
+ $p[] = $v->name;
+ }
+ }
+ }
+ if (sizeof($p)) {
+ return $p;
+ }
+ if (function_exists('ADODB_VIEW_PRIMARYKEYS')) {
+ return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner);
+ }
+ return false;
+ }
+
+ /**
+ * @returns assoc array where keys are tables, and values are foreign keys
+ */
+ function MetaForeignKeys($table, $owner=false, $upper=false) {
+ return false;
+ }
+ /**
+ * Choose a database to connect to. Many databases do not support this.
+ *
+ * @param dbName is the name of the database to select
+ * @return true or false
+ */
+ function SelectDB($dbName) {return false;}
+
+
+ /**
+ * Will select, getting rows from $offset (1-based), for $nrows.
+ * This simulates the MySQL "select * from table limit $offset,$nrows" , and
+ * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
+ * MySQL and PostgreSQL parameter ordering is the opposite of the other.
+ * eg.
+ * SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
+ * SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
+ *
+ * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set)
+ * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
+ *
+ * @param sql
+ * @param [offset] is the row to start calculations from (1-based)
+ * @param [nrows] is the number of rows to get
+ * @param [inputarr] array of bind variables
+ * @param [secs2cache] is a private parameter only used by jlim
+ * @return the recordset ($rs->databaseType == 'array')
+ */
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) {
+ $nrows = (int)$nrows;
+ $offset = (int)$offset;
+
+ if ($this->hasTop && $nrows > 0) {
+ // suggested by Reinhard Balling. Access requires top after distinct
+ // Informix requires first before distinct - F Riosa
+ $ismssql = (strpos($this->databaseType,'mssql') !== false);
+ if ($ismssql) {
+ $isaccess = false;
+ } else {
+ $isaccess = (strpos($this->databaseType,'access') !== false);
+ }
+
+ if ($offset <= 0) {
+ // access includes ties in result
+ if ($isaccess) {
+ $sql = preg_replace(
+ '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
+
+ if ($secs2cache != 0) {
+ $ret = $this->CacheExecute($secs2cache, $sql,$inputarr);
+ } else {
+ $ret = $this->Execute($sql,$inputarr);
+ }
+ return $ret; // PHP5 fix
+ } else if ($ismssql){
+ $sql = preg_replace(
+ '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
+ } else {
+ $sql = preg_replace(
+ '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
+ }
+ } else {
+ $nn = $nrows + $offset;
+ if ($isaccess || $ismssql) {
+ $sql = preg_replace(
+ '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
+ } else {
+ $sql = preg_replace(
+ '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
+ }
+ }
+ }
+
+ // if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer rows
+ // 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS.
+ global $ADODB_COUNTRECS;
+
+ $savec = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+
+
+ if ($secs2cache != 0) {
+ $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
+ } else {
+ $rs = $this->Execute($sql,$inputarr);
+ }
+
+ $ADODB_COUNTRECS = $savec;
+ if ($rs && !$rs->EOF) {
+ $rs = $this->_rs2rs($rs,$nrows,$offset);
+ }
+ //print_r($rs);
+ return $rs;
+ }
+
+ /**
+ * Create serializable recordset. Breaks rs link to connection.
+ *
+ * @param rs the recordset to serialize
+ */
+ function SerializableRS(&$rs) {
+ $rs2 = $this->_rs2rs($rs);
+ $ignore = false;
+ $rs2->connection = $ignore;
+
+ return $rs2;
+ }
+
+ /**
+ * Convert database recordset to an array recordset
+ * input recordset's cursor should be at beginning, and
+ * old $rs will be closed.
+ *
+ * @param rs the recordset to copy
+ * @param [nrows] number of rows to retrieve (optional)
+ * @param [offset] offset by number of rows (optional)
+ * @return the new recordset
+ */
+ function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) {
+ if (! $rs) {
+ return false;
+ }
+ $dbtype = $rs->databaseType;
+ if (!$dbtype) {
+ $rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ?
+ return $rs;
+ }
+ if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) {
+ $rs->MoveFirst();
+ $rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ?
+ return $rs;
+ }
+ $flds = array();
+ for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
+ $flds[] = $rs->FetchField($i);
+ }
+
+ $arr = $rs->GetArrayLimit($nrows,$offset);
+ //print_r($arr);
+ if ($close) {
+ $rs->Close();
+ }
+
+ $arrayClass = $this->arrayClass;
+
+ $rs2 = new $arrayClass();
+ $rs2->connection = $this;
+ $rs2->sql = $rs->sql;
+ $rs2->dataProvider = $this->dataProvider;
+ $rs2->InitArrayFields($arr,$flds);
+ $rs2->fetchMode = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
+ return $rs2;
+ }
+
+ /*
+ * Return all rows. Compat with PEAR DB
+ */
+ function GetAll($sql, $inputarr=false) {
+ $arr = $this->GetArray($sql,$inputarr);
+ return $arr;
+ }
+
+ function GetAssoc($sql, $inputarr=false,$force_array = false, $first2cols = false) {
+ $rs = $this->Execute($sql, $inputarr);
+ if (!$rs) {
+ return false;
+ }
+ $arr = $rs->GetAssoc($force_array,$first2cols);
+ return $arr;
+ }
+
+ function CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = false, $first2cols = false) {
+ if (!is_numeric($secs2cache)) {
+ $first2cols = $force_array;
+ $force_array = $inputarr;
+ }
+ $rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
+ if (!$rs) {
+ return false;
+ }
+ $arr = $rs->GetAssoc($force_array,$first2cols);
+ return $arr;
+ }
+
+ /**
+ * Return first element of first row of sql statement. Recordset is disposed
+ * for you.
+ *
+ * @param sql SQL statement
+ * @param [inputarr] input bind array
+ */
+ function GetOne($sql,$inputarr=false) {
+ global $ADODB_COUNTRECS,$ADODB_GETONE_EOF;
+
+ $crecs = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+
+ $ret = false;
+ $rs = $this->Execute($sql,$inputarr);
+ if ($rs) {
+ if ($rs->EOF) {
+ $ret = $ADODB_GETONE_EOF;
+ } else {
+ $ret = reset($rs->fields);
+ }
+
+ $rs->Close();
+ }
+ $ADODB_COUNTRECS = $crecs;
+ return $ret;
+ }
+
+ // $where should include 'WHERE fld=value'
+ function GetMedian($table, $field,$where = '') {
+ $total = $this->GetOne("select count(*) from $table $where");
+ if (!$total) {
+ return false;
+ }
+
+ $midrow = (integer) ($total/2);
+ $rs = $this->SelectLimit("select $field from $table $where order by 1",1,$midrow);
+ if ($rs && !$rs->EOF) {
+ return reset($rs->fields);
+ }
+ return false;
+ }
+
+
+ function CacheGetOne($secs2cache,$sql=false,$inputarr=false) {
+ global $ADODB_GETONE_EOF;
+
+ $ret = false;
+ $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
+ if ($rs) {
+ if ($rs->EOF) {
+ $ret = $ADODB_GETONE_EOF;
+ } else {
+ $ret = reset($rs->fields);
+ }
+ $rs->Close();
+ }
+
+ return $ret;
+ }
+
+ function GetCol($sql, $inputarr = false, $trim = false) {
+
+ $rs = $this->Execute($sql, $inputarr);
+ if ($rs) {
+ $rv = array();
+ if ($trim) {
+ while (!$rs->EOF) {
+ $rv[] = trim(reset($rs->fields));
+ $rs->MoveNext();
+ }
+ } else {
+ while (!$rs->EOF) {
+ $rv[] = reset($rs->fields);
+ $rs->MoveNext();
+ }
+ }
+ $rs->Close();
+ } else {
+ $rv = false;
+ }
+ return $rv;
+ }
+
+ function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) {
+ $rs = $this->CacheExecute($secs, $sql, $inputarr);
+ if ($rs) {
+ $rv = array();
+ if ($trim) {
+ while (!$rs->EOF) {
+ $rv[] = trim(reset($rs->fields));
+ $rs->MoveNext();
+ }
+ } else {
+ while (!$rs->EOF) {
+ $rv[] = reset($rs->fields);
+ $rs->MoveNext();
+ }
+ }
+ $rs->Close();
+ } else
+ $rv = false;
+
+ return $rv;
+ }
+
+ function Transpose(&$rs,$addfieldnames=true) {
+ $rs2 = $this->_rs2rs($rs);
+ if (!$rs2) {
+ return false;
+ }
+
+ $rs2->_transpose($addfieldnames);
+ return $rs2;
+ }
+
+ /*
+ Calculate the offset of a date for a particular database and generate
+ appropriate SQL. Useful for calculating future/past dates and storing
+ in a database.
+
+ If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour.
+ */
+ function OffsetDate($dayFraction,$date=false) {
+ if (!$date) {
+ $date = $this->sysDate;
+ }
+ return '('.$date.'+'.$dayFraction.')';
+ }
+
+
+ /**
+ *
+ * @param sql SQL statement
+ * @param [inputarr] input bind array
+ */
+ function GetArray($sql,$inputarr=false) {
+ global $ADODB_COUNTRECS;
+
+ $savec = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+ $rs = $this->Execute($sql,$inputarr);
+ $ADODB_COUNTRECS = $savec;
+ if (!$rs)
+ if (defined('ADODB_PEAR')) {
+ $cls = ADODB_PEAR_Error();
+ return $cls;
+ } else {
+ return false;
+ }
+ $arr = $rs->GetArray();
+ $rs->Close();
+ return $arr;
+ }
+
+ function CacheGetAll($secs2cache,$sql=false,$inputarr=false) {
+ $arr = $this->CacheGetArray($secs2cache,$sql,$inputarr);
+ return $arr;
+ }
+
+ function CacheGetArray($secs2cache,$sql=false,$inputarr=false) {
+ global $ADODB_COUNTRECS;
+
+ $savec = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+ $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
+ $ADODB_COUNTRECS = $savec;
+
+ if (!$rs)
+ if (defined('ADODB_PEAR')) {
+ $cls = ADODB_PEAR_Error();
+ return $cls;
+ } else {
+ return false;
+ }
+ $arr = $rs->GetArray();
+ $rs->Close();
+ return $arr;
+ }
+
+ function GetRandRow($sql, $arr= false) {
+ $rezarr = $this->GetAll($sql, $arr);
+ $sz = sizeof($rezarr);
+ return $rezarr[abs(rand()) % $sz];
+ }
+
+ /**
+ * Return one row of sql statement. Recordset is disposed for you.
+ * Note that SelectLimit should not be called.
+ *
+ * @param sql SQL statement
+ * @param [inputarr] input bind array
+ */
+ function GetRow($sql,$inputarr=false) {
+ global $ADODB_COUNTRECS;
+
+ $crecs = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+
+ $rs = $this->Execute($sql,$inputarr);
+
+ $ADODB_COUNTRECS = $crecs;
+ if ($rs) {
+ if (!$rs->EOF) {
+ $arr = $rs->fields;
+ } else {
+ $arr = array();
+ }
+ $rs->Close();
+ return $arr;
+ }
+
+ return false;
+ }
+
+ function CacheGetRow($secs2cache,$sql=false,$inputarr=false) {
+ $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
+ if ($rs) {
+ if (!$rs->EOF) {
+ $arr = $rs->fields;
+ } else {
+ $arr = array();
+ }
+
+ $rs->Close();
+ return $arr;
+ }
+ return false;
+ }
+
+ /**
+ * Insert or replace a single record. Note: this is not the same as MySQL's replace.
+ * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
+ * Also note that no table locking is done currently, so it is possible that the
+ * record be inserted twice by two programs...
+ *
+ * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
+ *
+ * $table table name
+ * $fieldArray associative array of data (you must quote strings yourself).
+ * $keyCol the primary key field name or if compound key, array of field names
+ * autoQuote set to true to use a hueristic to quote strings. Works with nulls and numbers
+ * but does not work with dates nor SQL functions.
+ * has_autoinc the primary key is an auto-inc field, so skip in insert.
+ *
+ * Currently blob replace not supported
+ *
+ * returns 0 = fail, 1 = update, 2 = insert
+ */
+
+ function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+
+ return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc);
+ }
+
+
+ /**
+ * Will select, getting rows from $offset (1-based), for $nrows.
+ * This simulates the MySQL "select * from table limit $offset,$nrows" , and
+ * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
+ * MySQL and PostgreSQL parameter ordering is the opposite of the other.
+ * eg.
+ * CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
+ * CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
+ *
+ * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
+ *
+ * @param [secs2cache] seconds to cache data, set to 0 to force query. This is optional
+ * @param sql
+ * @param [offset] is the row to start calculations from (1-based)
+ * @param [nrows] is the number of rows to get
+ * @param [inputarr] array of bind variables
+ * @return the recordset ($rs->databaseType == 'array')
+ */
+ function CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) {
+ if (!is_numeric($secs2cache)) {
+ if ($sql === false) {
+ $sql = -1;
+ }
+ if ($offset == -1) {
+ $offset = false;
+ }
+ // sql, nrows, offset,inputarr
+ $rs = $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs);
+ } else {
+ if ($sql === false) {
+ $this->outp_throw("Warning: \$sql missing from CacheSelectLimit()",'CacheSelectLimit');
+ }
+ $rs = $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ }
+ return $rs;
+ }
+
+ /**
+ * Flush cached recordsets that match a particular $sql statement.
+ * If $sql == false, then we purge all files in the cache.
+ */
+ function CacheFlush($sql=false,$inputarr=false) {
+ global $ADODB_CACHE_DIR, $ADODB_CACHE;
+
+ # Create cache if it does not exist
+ if (empty($ADODB_CACHE)) {
+ $this->_CreateCache();
+ }
+
+ if (!$sql) {
+ $ADODB_CACHE->flushall($this->debug);
+ return;
+ }
+
+ $f = $this->_gencachename($sql.serialize($inputarr),false);
+ return $ADODB_CACHE->flushcache($f, $this->debug);
+ }
+
+
+ /**
+ * Private function to generate filename for caching.
+ * Filename is generated based on:
+ *
+ * - sql statement
+ * - database type (oci8, ibase, ifx, etc)
+ * - database name
+ * - userid
+ * - setFetchMode (adodb 4.23)
+ *
+ * When not in safe mode, we create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
+ * Assuming that we can have 50,000 files per directory with good performance,
+ * then we can scale to 12.8 million unique cached recordsets. Wow!
+ */
+ function _gencachename($sql,$createdir) {
+ global $ADODB_CACHE, $ADODB_CACHE_DIR;
+
+ if ($this->fetchMode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ } else {
+ $mode = $this->fetchMode;
+ }
+ $m = md5($sql.$this->databaseType.$this->database.$this->user.$mode);
+ if (!$ADODB_CACHE->createdir) {
+ return $m;
+ }
+ if (!$createdir) {
+ $dir = $ADODB_CACHE->getdirname($m);
+ } else {
+ $dir = $ADODB_CACHE->createdir($m, $this->debug);
+ }
+
+ return $dir.'/adodb_'.$m.'.cache';
+ }
+
+
+ /**
+ * Execute SQL, caching recordsets.
+ *
+ * @param [secs2cache] seconds to cache data, set to 0 to force query.
+ * This is an optional parameter.
+ * @param sql SQL statement to execute
+ * @param [inputarr] holds the input data to bind to
+ * @return RecordSet or false
+ */
+ function CacheExecute($secs2cache,$sql=false,$inputarr=false) {
+ global $ADODB_CACHE;
+
+ if (empty($ADODB_CACHE)) {
+ $this->_CreateCache();
+ }
+
+ if (!is_numeric($secs2cache)) {
+ $inputarr = $sql;
+ $sql = $secs2cache;
+ $secs2cache = $this->cacheSecs;
+ }
+
+ if (is_array($sql)) {
+ $sqlparam = $sql;
+ $sql = $sql[0];
+ } else
+ $sqlparam = $sql;
+
+
+ $md5file = $this->_gencachename($sql.serialize($inputarr),true);
+ $err = '';
+
+ if ($secs2cache > 0){
+ $rs = $ADODB_CACHE->readcache($md5file,$err,$secs2cache,$this->arrayClass);
+ $this->numCacheHits += 1;
+ } else {
+ $err='Timeout 1';
+ $rs = false;
+ $this->numCacheMisses += 1;
+ }
+
+ if (!$rs) {
+ // no cached rs found
+ if ($this->debug) {
+ if (get_magic_quotes_runtime() && !$this->memCache) {
+ ADOConnection::outp("Please disable magic_quotes_runtime - it corrupts cache files :(");
+ }
+ if ($this->debug !== -1) {
+ ADOConnection::outp( " $md5file cache failure: $err (this is a notice and not an error)");
+ }
+ }
+
+ $rs = $this->Execute($sqlparam,$inputarr);
+
+ if ($rs) {
+ $eof = $rs->EOF;
+ $rs = $this->_rs2rs($rs); // read entire recordset into memory immediately
+ $rs->timeCreated = time(); // used by caching
+ $txt = _rs2serialize($rs,false,$sql); // serialize
+
+ $ok = $ADODB_CACHE->writecache($md5file,$txt,$this->debug, $secs2cache);
+ if (!$ok) {
+ if ($ok === false) {
+ $em = 'Cache write error';
+ $en = -32000;
+
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'CacheExecute', $en, $em, $md5file,$sql,$this);
+ }
+ } else {
+ $em = 'Cache file locked warning';
+ $en = -32001;
+ // do not call error handling for just a warning
+ }
+
+ if ($this->debug) {
+ ADOConnection::outp( " ".$em);
+ }
+ }
+ if ($rs->EOF && !$eof) {
+ $rs->MoveFirst();
+ //$rs = csv2rs($md5file,$err);
+ $rs->connection = $this; // Pablo suggestion
+ }
+
+ } else if (!$this->memCache) {
+ $ADODB_CACHE->flushcache($md5file);
+ }
+ } else {
+ $this->_errorMsg = '';
+ $this->_errorCode = 0;
+
+ if ($this->fnCacheExecute) {
+ $fn = $this->fnCacheExecute;
+ $fn($this, $secs2cache, $sql, $inputarr);
+ }
+ // ok, set cached object found
+ $rs->connection = $this; // Pablo suggestion
+ if ($this->debug){
+ if ($this->debug == 99) {
+ adodb_backtrace();
+ }
+ $inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
+ $ttl = $rs->timeCreated + $secs2cache - time();
+ $s = is_array($sql) ? $sql[0] : $sql;
+ if ($inBrowser) {
+ $s = '<i>'.htmlspecialchars($s).'</i>';
+ }
+
+ ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]");
+ }
+ }
+ return $rs;
+ }
+
+
+ /*
+ Similar to PEAR DB's autoExecute(), except that
+ $mode can be 'INSERT' or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+ If $mode == 'UPDATE', then $where is compulsory as a safety measure.
+
+ $forceUpdate means that even if the data has not changed, perform update.
+ */
+ function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magicq = false) {
+ if ($where === false && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */) ) {
+ $this->outp_throw('AutoExecute: Illegal mode=UPDATE with empty WHERE clause', 'AutoExecute');
+ return false;
+ }
+
+ $sql = "SELECT * FROM $table";
+ $rs = $this->SelectLimit($sql, 1);
+ if (!$rs) {
+ return false; // table does not exist
+ }
+
+ $rs->tableName = $table;
+ if ($where !== false) {
+ $sql .= " WHERE $where";
+ }
+ $rs->sql = $sql;
+
+ switch($mode) {
+ case 'UPDATE':
+ case DB_AUTOQUERY_UPDATE:
+ $sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate, $magicq);
+ break;
+ case 'INSERT':
+ case DB_AUTOQUERY_INSERT:
+ $sql = $this->GetInsertSQL($rs, $fields_values, $magicq);
+ break;
+ default:
+ $this->outp_throw("AutoExecute: Unknown mode=$mode", 'AutoExecute');
+ return false;
+ }
+ return $sql && $this->Execute($sql);
+ }
+
+
+ /**
+ * Generates an Update Query based on an existing recordset.
+ * $arrFields is an associative array of fields with the value
+ * that should be assigned.
+ *
+ * Note: This function should only be used on a recordset
+ * that is run against a single table and sql should only
+ * be a simple select stmt with no groupby/orderby/limit
+ *
+ * "Jonathan Younger" <jyounger@unilab.com>
+ */
+ function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=null) {
+ global $ADODB_INCLUDED_LIB;
+
+ // ********************************************************
+ // This is here to maintain compatibility
+ // with older adodb versions. Sets force type to force nulls if $forcenulls is set.
+ if (!isset($force)) {
+ global $ADODB_FORCE_TYPE;
+ $force = $ADODB_FORCE_TYPE;
+ }
+ // ********************************************************
+
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq,$force);
+ }
+
+ /**
+ * Generates an Insert Query based on an existing recordset.
+ * $arrFields is an associative array of fields with the value
+ * that should be assigned.
+ *
+ * Note: This function should only be used on a recordset
+ * that is run against a single table.
+ */
+ function GetInsertSQL(&$rs, $arrFields,$magicq=false,$force=null) {
+ global $ADODB_INCLUDED_LIB;
+ if (!isset($force)) {
+ global $ADODB_FORCE_TYPE;
+ $force = $ADODB_FORCE_TYPE;
+ }
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ return _adodb_getinsertsql($this,$rs,$arrFields,$magicq,$force);
+ }
+
+
+ /**
+ * Update a blob column, given a where clause. There are more sophisticated
+ * blob handling functions that we could have implemented, but all require
+ * a very complex API. Instead we have chosen something that is extremely
+ * simple to understand and use.
+ *
+ * Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course.
+ *
+ * Usage to update a $blobvalue which has a primary key blob_id=1 into a
+ * field blobtable.blobcolumn:
+ *
+ * UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1');
+ *
+ * Insert example:
+ *
+ * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ * $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') {
+ return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
+ }
+
+ /**
+ * Usage:
+ * UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1');
+ *
+ * $blobtype supports 'BLOB' and 'CLOB'
+ *
+ * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ * $conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1');
+ */
+ function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') {
+ $fd = fopen($path,'rb');
+ if ($fd === false) {
+ return false;
+ }
+ $val = fread($fd,filesize($path));
+ fclose($fd);
+ return $this->UpdateBlob($table,$column,$val,$where,$blobtype);
+ }
+
+ function BlobDecode($blob) {
+ return $blob;
+ }
+
+ function BlobEncode($blob) {
+ return $blob;
+ }
+
+ function GetCharSet() {
+ return $this->charSet;
+ }
+
+ function SetCharSet($charset) {
+ $this->charSet = $charset;
+ return true;
+ }
+
+ function IfNull( $field, $ifNull ) {
+ return " CASE WHEN $field is null THEN $ifNull ELSE $field END ";
+ }
+
+ function LogSQL($enable=true) {
+ include_once(ADODB_DIR.'/adodb-perf.inc.php');
+
+ if ($enable) {
+ $this->fnExecute = 'adodb_log_sql';
+ } else {
+ $this->fnExecute = false;
+ }
+
+ $old = $this->_logsql;
+ $this->_logsql = $enable;
+ if ($enable && !$old) {
+ $this->_affected = false;
+ }
+ return $old;
+ }
+
+ /**
+ * Usage:
+ * UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
+ *
+ * $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
+ * $conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
+ */
+ function UpdateClob($table,$column,$val,$where) {
+ return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
+ }
+
+ // not the fastest implementation - quick and dirty - jlim
+ // for best performance, use the actual $rs->MetaType().
+ function MetaType($t,$len=-1,$fieldobj=false) {
+
+ if (empty($this->_metars)) {
+ $rsclass = $this->rsPrefix.$this->databaseType;
+ $this->_metars = new $rsclass(false,$this->fetchMode);
+ $this->_metars->connection = $this;
+ }
+ return $this->_metars->MetaType($t,$len,$fieldobj);
+ }
+
+
+ /**
+ * Change the SQL connection locale to a specified locale.
+ * This is used to get the date formats written depending on the client locale.
+ */
+ function SetDateLocale($locale = 'En') {
+ $this->locale = $locale;
+ switch (strtoupper($locale))
+ {
+ case 'EN':
+ $this->fmtDate="'Y-m-d'";
+ $this->fmtTimeStamp = "'Y-m-d H:i:s'";
+ break;
+
+ case 'US':
+ $this->fmtDate = "'m-d-Y'";
+ $this->fmtTimeStamp = "'m-d-Y H:i:s'";
+ break;
+
+ case 'PT_BR':
+ case 'NL':
+ case 'FR':
+ case 'RO':
+ case 'IT':
+ $this->fmtDate="'d-m-Y'";
+ $this->fmtTimeStamp = "'d-m-Y H:i:s'";
+ break;
+
+ case 'GE':
+ $this->fmtDate="'d.m.Y'";
+ $this->fmtTimeStamp = "'d.m.Y H:i:s'";
+ break;
+
+ default:
+ $this->fmtDate="'Y-m-d'";
+ $this->fmtTimeStamp = "'Y-m-d H:i:s'";
+ break;
+ }
+ }
+
+ /**
+ * GetActiveRecordsClass Performs an 'ALL' query
+ *
+ * @param mixed $class This string represents the class of the current active record
+ * @param mixed $table Table used by the active record object
+ * @param mixed $whereOrderBy Where, order, by clauses
+ * @param mixed $bindarr
+ * @param mixed $primkeyArr
+ * @param array $extra Query extras: limit, offset...
+ * @param mixed $relations Associative array: table's foreign name, "hasMany", "belongsTo"
+ * @access public
+ * @return void
+ */
+ function GetActiveRecordsClass(
+ $class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false,
+ $extra=array(),
+ $relations=array())
+ {
+ global $_ADODB_ACTIVE_DBS;
+ ## reduce overhead of adodb.inc.php -- moved to adodb-active-record.inc.php
+ ## if adodb-active-recordx is loaded -- should be no issue as they will probably use Find()
+ if (!isset($_ADODB_ACTIVE_DBS)) {
+ include_once(ADODB_DIR.'/adodb-active-record.inc.php');
+ }
+ return adodb_GetActiveRecordsClass($this, $class, $table, $whereOrderBy, $bindarr, $primkeyArr, $extra, $relations);
+ }
+
+ function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) {
+ $arr = $this->GetActiveRecordsClass('ADODB_Active_Record', $table, $where, $bindarr, $primkeyArr);
+ return $arr;
+ }
+
+ /**
+ * Close Connection
+ */
+ function Close() {
+ $rez = $this->_close();
+ $this->_queryID = false;
+ $this->_connectionID = false;
+ return $rez;
+ }
+
+ /**
+ * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
+ *
+ * @return true if succeeded or false if database does not support transactions
+ */
+ function BeginTrans() {
+ if ($this->debug) {
+ ADOConnection::outp("BeginTrans: Transactions not supported for this driver");
+ }
+ return false;
+ }
+
+ /* set transaction mode */
+ function SetTransactionMode( $transaction_mode ) {
+ $transaction_mode = $this->MetaTransaction($transaction_mode, $this->dataProvider);
+ $this->_transmode = $transaction_mode;
+ }
+/*
+http://msdn2.microsoft.com/en-US/ms173763.aspx
+http://dev.mysql.com/doc/refman/5.0/en/innodb-transaction-isolation.html
+http://www.postgresql.org/docs/8.1/interactive/sql-set-transaction.html
+http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_10005.htm
+*/
+ function MetaTransaction($mode,$db) {
+ $mode = strtoupper($mode);
+ $mode = str_replace('ISOLATION LEVEL ','',$mode);
+
+ switch($mode) {
+
+ case 'READ UNCOMMITTED':
+ switch($db) {
+ case 'oci8':
+ case 'oracle':
+ return 'ISOLATION LEVEL READ COMMITTED';
+ default:
+ return 'ISOLATION LEVEL READ UNCOMMITTED';
+ }
+ break;
+
+ case 'READ COMMITTED':
+ return 'ISOLATION LEVEL READ COMMITTED';
+ break;
+
+ case 'REPEATABLE READ':
+ switch($db) {
+ case 'oci8':
+ case 'oracle':
+ return 'ISOLATION LEVEL SERIALIZABLE';
+ default:
+ return 'ISOLATION LEVEL REPEATABLE READ';
+ }
+ break;
+
+ case 'SERIALIZABLE':
+ return 'ISOLATION LEVEL SERIALIZABLE';
+ break;
+
+ default:
+ return $mode;
+ }
+ }
+
+ /**
+ * If database does not support transactions, always return true as data always commited
+ *
+ * @param $ok set to false to rollback transaction, true to commit
+ *
+ * @return true/false.
+ */
+ function CommitTrans($ok=true) {
+ return true;
+ }
+
+
+ /**
+ * If database does not support transactions, rollbacks always fail, so return false
+ *
+ * @return true/false.
+ */
+ function RollbackTrans() {
+ return false;
+ }
+
+
+ /**
+ * return the databases that the driver can connect to.
+ * Some databases will return an empty array.
+ *
+ * @return an array of database names.
+ */
+ function MetaDatabases() {
+ global $ADODB_FETCH_MODE;
+
+ if ($this->metaDatabasesSQL) {
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+
+ $arr = $this->GetCol($this->metaDatabasesSQL);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $arr;
+ }
+
+ return false;
+ }
+
+ /**
+ * List procedures or functions in an array.
+ * @param procedureNamePattern a procedure name pattern; must match the procedure name as it is stored in the database
+ * @param catalog a catalog name; must match the catalog name as it is stored in the database;
+ * @param schemaPattern a schema name pattern;
+ *
+ * @return array of procedures on current database.
+ *
+ * Array(
+ * [name_of_procedure] => Array(
+ * [type] => PROCEDURE or FUNCTION
+ * [catalog] => Catalog_name
+ * [schema] => Schema_name
+ * [remarks] => explanatory comment on the procedure
+ * )
+ * )
+ */
+ function MetaProcedures($procedureNamePattern = null, $catalog = null, $schemaPattern = null) {
+ return false;
+ }
+
+
+ /**
+ * @param ttype can either be 'VIEW' or 'TABLE' or false.
+ * If false, both views and tables are returned.
+ * "VIEW" returns only views
+ * "TABLE" returns only tables
+ * @param showSchema returns the schema/user with the table name, eg. USER.TABLE
+ * @param mask is the input mask - only supported by oci8 and postgresql
+ *
+ * @return array of tables for current database.
+ */
+ function MetaTables($ttype=false,$showSchema=false,$mask=false) {
+ global $ADODB_FETCH_MODE;
+
+ if ($mask) {
+ return false;
+ }
+ if ($this->metaTablesSQL) {
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+
+ $rs = $this->Execute($this->metaTablesSQL);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rs === false) {
+ return false;
+ }
+ $arr = $rs->GetArray();
+ $arr2 = array();
+
+ if ($hast = ($ttype && isset($arr[0][1]))) {
+ $showt = strncmp($ttype,'T',1);
+ }
+
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($hast) {
+ if ($showt == 0) {
+ if (strncmp($arr[$i][1],'T',1) == 0) {
+ $arr2[] = trim($arr[$i][0]);
+ }
+ } else {
+ if (strncmp($arr[$i][1],'V',1) == 0) {
+ $arr2[] = trim($arr[$i][0]);
+ }
+ }
+ } else
+ $arr2[] = trim($arr[$i][0]);
+ }
+ $rs->Close();
+ return $arr2;
+ }
+ return false;
+ }
+
+
+ function _findschema(&$table,&$schema) {
+ if (!$schema && ($at = strpos($table,'.')) !== false) {
+ $schema = substr($table,0,$at);
+ $table = substr($table,$at+1);
+ }
+ }
+
+ /**
+ * List columns in a database as an array of ADOFieldObjects.
+ * See top of file for definition of object.
+ *
+ * @param $table table name to query
+ * @param $normalize makes table name case-insensitive (required by some databases)
+ * @schema is optional database schema to use - not supported by all databases.
+ *
+ * @return array of ADOFieldObjects for current table.
+ */
+ function MetaColumns($table,$normalize=true) {
+ global $ADODB_FETCH_MODE;
+
+ if (!empty($this->metaColumnsSQL)) {
+ $schema = false;
+ $this->_findschema($table,$schema);
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,($normalize)?strtoupper($table):$table));
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ if ($rs === false || $rs->EOF) {
+ return false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF) { //print_r($rs->fields);
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ if (isset($rs->fields[3]) && $rs->fields[3]) {
+ if ($rs->fields[3]>0) {
+ $fld->max_length = $rs->fields[3];
+ }
+ $fld->scale = $rs->fields[4];
+ if ($fld->scale>0) {
+ $fld->max_length += 1;
+ }
+ } else {
+ $fld->max_length = $rs->fields[2];
+ }
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ return $retarr;
+ }
+ return false;
+ }
+
+ /**
+ * List indexes on a table as an array.
+ * @param table table name to query
+ * @param primary true to only show primary keys. Not actually used for most databases
+ *
+ * @return array of indexes on current table. Each element represents an index, and is itself an associative array.
+ *
+ * Array(
+ * [name_of_index] => Array(
+ * [unique] => true or false
+ * [columns] => Array(
+ * [0] => firstname
+ * [1] => lastname
+ * )
+ * )
+ * )
+ */
+ function MetaIndexes($table, $primary = false, $owner = false) {
+ return false;
+ }
+
+ /**
+ * List columns names in a table as an array.
+ * @param table table name to query
+ *
+ * @return array of column names for current table.
+ */
+ function MetaColumnNames($table, $numIndexes=false,$useattnum=false /* only for postgres */) {
+ $objarr = $this->MetaColumns($table);
+ if (!is_array($objarr)) {
+ return false;
+ }
+ $arr = array();
+ if ($numIndexes) {
+ $i = 0;
+ if ($useattnum) {
+ foreach($objarr as $v)
+ $arr[$v->attnum] = $v->name;
+
+ } else
+ foreach($objarr as $v) $arr[$i++] = $v->name;
+ } else
+ foreach($objarr as $v) $arr[strtoupper($v->name)] = $v->name;
+
+ return $arr;
+ }
+
+ /**
+ * Different SQL databases used different methods to combine strings together.
+ * This function provides a wrapper.
+ *
+ * param s variable number of string parameters
+ *
+ * Usage: $db->Concat($str1,$str2);
+ *
+ * @return concatenated string
+ */
+ function Concat() {
+ $arr = func_get_args();
+ return implode($this->concat_operator, $arr);
+ }
+
+
+ /**
+ * Converts a date "d" to a string that the database can understand.
+ *
+ * @param d a date in Unix date time format.
+ *
+ * @return date string in database date format
+ */
+ function DBDate($d, $isfld=false) {
+ if (empty($d) && $d !== 0) {
+ return 'null';
+ }
+ if ($isfld) {
+ return $d;
+ }
+ if (is_object($d)) {
+ return $d->format($this->fmtDate);
+ }
+
+ if (is_string($d) && !is_numeric($d)) {
+ if ($d === 'null') {
+ return $d;
+ }
+ if (strncmp($d,"'",1) === 0) {
+ $d = _adodb_safedateq($d);
+ return $d;
+ }
+ if ($this->isoDates) {
+ return "'$d'";
+ }
+ $d = ADOConnection::UnixDate($d);
+ }
+
+ return adodb_date($this->fmtDate,$d);
+ }
+
+ function BindDate($d) {
+ $d = $this->DBDate($d);
+ if (strncmp($d,"'",1)) {
+ return $d;
+ }
+
+ return substr($d,1,strlen($d)-2);
+ }
+
+ function BindTimeStamp($d) {
+ $d = $this->DBTimeStamp($d);
+ if (strncmp($d,"'",1)) {
+ return $d;
+ }
+
+ return substr($d,1,strlen($d)-2);
+ }
+
+
+ /**
+ * Converts a timestamp "ts" to a string that the database can understand.
+ *
+ * @param ts a timestamp in Unix date time format.
+ *
+ * @return timestamp string in database timestamp format
+ */
+ function DBTimeStamp($ts,$isfld=false) {
+ if (empty($ts) && $ts !== 0) {
+ return 'null';
+ }
+ if ($isfld) {
+ return $ts;
+ }
+ if (is_object($ts)) {
+ return $ts->format($this->fmtTimeStamp);
+ }
+
+ # strlen(14) allows YYYYMMDDHHMMSS format
+ if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) {
+ return adodb_date($this->fmtTimeStamp,$ts);
+ }
+
+ if ($ts === 'null') {
+ return $ts;
+ }
+ if ($this->isoDates && strlen($ts) !== 14) {
+ $ts = _adodb_safedate($ts);
+ return "'$ts'";
+ }
+ $ts = ADOConnection::UnixTimeStamp($ts);
+ return adodb_date($this->fmtTimeStamp,$ts);
+ }
+
+ /**
+ * Also in ADORecordSet.
+ * @param $v is a date string in YYYY-MM-DD format
+ *
+ * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
+ */
+ static function UnixDate($v) {
+ if (is_object($v)) {
+ // odbtp support
+ //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
+ return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
+ }
+
+ if (is_numeric($v) && strlen($v) !== 8) {
+ return $v;
+ }
+ if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", $v, $rr)) {
+ return false;
+ }
+
+ if ($rr[1] <= TIMESTAMP_FIRST_YEAR) {
+ return 0;
+ }
+
+ // h-m-s-MM-DD-YY
+ return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
+ }
+
+
+ /**
+ * Also in ADORecordSet.
+ * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
+ *
+ * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
+ */
+ static function UnixTimeStamp($v) {
+ if (is_object($v)) {
+ // odbtp support
+ //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
+ return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
+ }
+
+ if (!preg_match(
+ "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
+ ($v), $rr)) return false;
+
+ if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) {
+ return 0;
+ }
+
+ // h-m-s-MM-DD-YY
+ if (!isset($rr[5])) {
+ return adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
+ }
+ return @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
+ }
+
+ /**
+ * Also in ADORecordSet.
+ *
+ * Format database date based on user defined format.
+ *
+ * @param v is the character date in YYYY-MM-DD format, returned by database
+ * @param fmt is the format to apply to it, using date()
+ *
+ * @return a date formated as user desires
+ */
+ function UserDate($v,$fmt='Y-m-d',$gmt=false) {
+ $tt = $this->UnixDate($v);
+
+ // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
+ if (($tt === false || $tt == -1) && $v != false) {
+ return $v;
+ } else if ($tt == 0) {
+ return $this->emptyDate;
+ } else if ($tt == -1) {
+ // pre-TIMESTAMP_FIRST_YEAR
+ }
+
+ return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
+
+ }
+
+ /**
+ *
+ * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format
+ * @param fmt is the format to apply to it, using date()
+ *
+ * @return a timestamp formated as user desires
+ */
+ function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) {
+ if (!isset($v)) {
+ return $this->emptyTimeStamp;
+ }
+ # strlen(14) allows YYYYMMDDHHMMSS format
+ if (is_numeric($v) && strlen($v)<14) {
+ return ($gmt) ? adodb_gmdate($fmt,$v) : adodb_date($fmt,$v);
+ }
+ $tt = $this->UnixTimeStamp($v);
+ // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
+ if (($tt === false || $tt == -1) && $v != false) {
+ return $v;
+ }
+ if ($tt == 0) {
+ return $this->emptyTimeStamp;
+ }
+ return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
+ }
+
+ function escape($s,$magic_quotes=false) {
+ return $this->addq($s,$magic_quotes);
+ }
+
+ /**
+ * Quotes a string, without prefixing nor appending quotes.
+ */
+ function addq($s,$magic_quotes=false) {
+ if (!$magic_quotes) {
+ if ($this->replaceQuote[0] == '\\') {
+ // only since php 4.0.5
+ $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
+ //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
+ }
+ return str_replace("'",$this->replaceQuote,$s);
+ }
+
+ // undo magic quotes for "
+ $s = str_replace('\\"','"',$s);
+
+ if ($this->replaceQuote == "\\'" || ini_get('magic_quotes_sybase')) {
+ // ' already quoted, no need to change anything
+ return $s;
+ } else {
+ // change \' to '' for sybase/mssql
+ $s = str_replace('\\\\','\\',$s);
+ return str_replace("\\'",$this->replaceQuote,$s);
+ }
+ }
+
+ /**
+ * Correctly quotes a string so that all strings are escaped. We prefix and append
+ * to the string single-quotes.
+ * An example is $db->qstr("Don't bother",magic_quotes_runtime());
+ *
+ * @param s the string to quote
+ * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc().
+ * This undoes the stupidity of magic quotes for GPC.
+ *
+ * @return quoted string to be sent back to database
+ */
+ function qstr($s,$magic_quotes=false) {
+ if (!$magic_quotes) {
+ if ($this->replaceQuote[0] == '\\'){
+ // only since php 4.0.5
+ $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
+ //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
+ }
+ return "'".str_replace("'",$this->replaceQuote,$s)."'";
+ }
+
+ // undo magic quotes for "
+ $s = str_replace('\\"','"',$s);
+
+ if ($this->replaceQuote == "\\'" || ini_get('magic_quotes_sybase')) {
+ // ' already quoted, no need to change anything
+ return "'$s'";
+ } else {
+ // change \' to '' for sybase/mssql
+ $s = str_replace('\\\\','\\',$s);
+ return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
+ }
+ }
+
+
+ /**
+ * Will select the supplied $page number from a recordset, given that it is paginated in pages of
+ * $nrows rows per page. It also saves two boolean values saying if the given page is the first
+ * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
+ *
+ * See docs-adodb.htm#ex8 for an example of usage.
+ *
+ * @param sql
+ * @param nrows is the number of rows per page to get
+ * @param page is the page number to get (1-based)
+ * @param [inputarr] array of bind variables
+ * @param [secs2cache] is a private parameter only used by jlim
+ * @return the recordset ($rs->databaseType == 'array')
+ *
+ * NOTE: phpLens uses a different algorithm and does not use PageExecute().
+ *
+ */
+ function PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ if ($this->pageExecuteCountRows) {
+ $rs = _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache);
+ } else {
+ $rs = _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache);
+ }
+ return $rs;
+ }
+
+
+ /**
+ * Will select the supplied $page number from a recordset, given that it is paginated in pages of
+ * $nrows rows per page. It also saves two boolean values saying if the given page is the first
+ * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
+ *
+ * @param secs2cache seconds to cache data, set to 0 to force query
+ * @param sql
+ * @param nrows is the number of rows per page to get
+ * @param page is the page number to get (1-based)
+ * @param [inputarr] array of bind variables
+ * @return the recordset ($rs->databaseType == 'array')
+ */
+ function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) {
+ /*switch($this->dataProvider) {
+ case 'postgres':
+ case 'mysql':
+ break;
+ default: $secs2cache = 0; break;
+ }*/
+ $rs = $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache);
+ return $rs;
+ }
+
+ /**
+ * Get the last error recorded by PHP and clear the message.
+ *
+ * By clearing the message, it becomes possible to detect whether a new error
+ * has occurred, even when it is the same error as before being repeated.
+ *
+ * @return array|null Array if an error has previously occurred. Null otherwise.
+ */
+ protected function resetLastError() {
+ $error = error_get_last();
+
+ if (is_array($error)) {
+ $error['message'] = '';
+ }
+
+ return $error;
+ }
+
+ /**
+ * Compare a previously stored error message with the last error recorded by PHP
+ * to determine whether a new error has occured.
+ *
+ * @param array|null $old Optional. Previously stored return value of error_get_last().
+ *
+ * @return string The error message if a new error has occured
+ * or an empty string if no (new) errors have occured..
+ */
+ protected function getChangedErrorMsg($old = null) {
+ $new = error_get_last();
+
+ if (is_null($new)) {
+ // No error has occured yet at all.
+ return '';
+ }
+
+ if (is_null($old)) {
+ // First error recorded.
+ return $new['message'];
+ }
+
+ $changed = false;
+ foreach($new as $key => $value) {
+ if ($new[$key] !== $old[$key]) {
+ $changed = true;
+ break;
+ }
+ }
+
+ if ($changed === true) {
+ return $new['message'];
+ }
+
+ return '';
+ }
+
+} // end class ADOConnection
+
+
+
+ //==============================================================================================
+ // CLASS ADOFetchObj
+ //==============================================================================================
+
+ /**
+ * Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
+ */
+ class ADOFetchObj {
+ };
+
+ //==============================================================================================
+ // CLASS ADORecordSet_empty
+ //==============================================================================================
+
+ class ADODB_Iterator_empty implements Iterator {
+
+ private $rs;
+
+ function __construct($rs) {
+ $this->rs = $rs;
+ }
+
+ function rewind() {}
+
+ function valid() {
+ return !$this->rs->EOF;
+ }
+
+ function key() {
+ return false;
+ }
+
+ function current() {
+ return false;
+ }
+
+ function next() {}
+
+ function __call($func, $params) {
+ return call_user_func_array(array($this->rs, $func), $params);
+ }
+
+ function hasMore() {
+ return false;
+ }
+
+ }
+
+
+ /**
+ * Lightweight recordset when there are no records to be returned
+ */
+ class ADORecordSet_empty implements IteratorAggregate
+ {
+ var $dataProvider = 'empty';
+ var $databaseType = false;
+ var $EOF = true;
+ var $_numOfRows = 0;
+ var $fields = false;
+ var $connection = false;
+
+ function RowCount() {
+ return 0;
+ }
+
+ function RecordCount() {
+ return 0;
+ }
+
+ function PO_RecordCount() {
+ return 0;
+ }
+
+ function Close() {
+ return true;
+ }
+
+ function FetchRow() {
+ return false;
+ }
+
+ function FieldCount() {
+ return 0;
+ }
+
+ function Init() {}
+
+ function getIterator() {
+ return new ADODB_Iterator_empty($this);
+ }
+
+ function GetAssoc() {
+ return array();
+ }
+
+ function GetArray() {
+ return array();
+ }
+
+ function GetAll() {
+ return array();
+ }
+
+ function GetArrayLimit() {
+ return array();
+ }
+
+ function GetRows() {
+ return array();
+ }
+
+ function GetRowAssoc() {
+ return array();
+ }
+
+ function MaxRecordCount() {
+ return 0;
+ }
+
+ function NumRows() {
+ return 0;
+ }
+
+ function NumCols() {
+ return 0;
+ }
+ }
+
+ //==============================================================================================
+ // DATE AND TIME FUNCTIONS
+ //==============================================================================================
+ if (!defined('ADODB_DATE_VERSION')) {
+ include(ADODB_DIR.'/adodb-time.inc.php');
+ }
+
+ //==============================================================================================
+ // CLASS ADORecordSet
+ //==============================================================================================
+
+ class ADODB_Iterator implements Iterator {
+
+ private $rs;
+
+ function __construct($rs) {
+ $this->rs = $rs;
+ }
+
+ function rewind() {
+ $this->rs->MoveFirst();
+ }
+
+ function valid() {
+ return !$this->rs->EOF;
+ }
+
+ function key() {
+ return $this->rs->_currentRow;
+ }
+
+ function current() {
+ return $this->rs->fields;
+ }
+
+ function next() {
+ $this->rs->MoveNext();
+ }
+
+ function __call($func, $params) {
+ return call_user_func_array(array($this->rs, $func), $params);
+ }
+
+ function hasMore() {
+ return !$this->rs->EOF;
+ }
+
+ }
+
+
+ /**
+ * RecordSet class that represents the dataset returned by the database.
+ * To keep memory overhead low, this class holds only the current row in memory.
+ * No prefetching of data is done, so the RecordCount() can return -1 ( which
+ * means recordcount not known).
+ */
+ class ADORecordSet implements IteratorAggregate {
+
+ /**
+ * public variables
+ */
+ var $dataProvider = "native";
+ var $fields = false; /// holds the current row data
+ var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob
+ /// in other words, we use a text area for editing.
+ var $canSeek = false; /// indicates that seek is supported
+ var $sql; /// sql text
+ var $EOF = false; /// Indicates that the current record position is after the last record in a Recordset object.
+
+ var $emptyTimeStamp = '&nbsp;'; /// what to display when $time==0
+ var $emptyDate = '&nbsp;'; /// what to display when $time==0
+ var $debug = false;
+ var $timeCreated=0; /// datetime in Unix format rs created -- for cached recordsets
+
+ var $bind = false; /// used by Fields() to hold array - should be private?
+ var $fetchMode; /// default fetch mode
+ var $connection = false; /// the parent connection
+
+ /**
+ * private variables
+ */
+ var $_numOfRows = -1; /** number of rows, or -1 */
+ var $_numOfFields = -1; /** number of fields in recordset */
+ var $_queryID = -1; /** This variable keeps the result link identifier. */
+ var $_currentRow = -1; /** This variable keeps the current row in the Recordset. */
+ var $_closed = false; /** has recordset been closed */
+ var $_inited = false; /** Init() should only be called once */
+ var $_obj; /** Used by FetchObj */
+ var $_names; /** Used by FetchObj */
+
+ var $_currentPage = -1; /** Added by Iván Oliva to implement recordset pagination */
+ var $_atFirstPage = false; /** Added by Iván Oliva to implement recordset pagination */
+ var $_atLastPage = false; /** Added by Iván Oliva to implement recordset pagination */
+ var $_lastPageNo = -1;
+ var $_maxRecordCount = 0;
+ var $datetime = false;
+
+ /**
+ * Constructor
+ *
+ * @param queryID this is the queryID returned by ADOConnection->_query()
+ *
+ */
+ function __construct($queryID) {
+ $this->_queryID = $queryID;
+ }
+
+ function __destruct() {
+ $this->Close();
+ }
+
+ function getIterator() {
+ return new ADODB_Iterator($this);
+ }
+
+ /* this is experimental - i don't really know what to return... */
+ function __toString() {
+ include_once(ADODB_DIR.'/toexport.inc.php');
+ return _adodb_export($this,',',',',false,true);
+ }
+
+ function Init() {
+ if ($this->_inited) {
+ return;
+ }
+ $this->_inited = true;
+ if ($this->_queryID) {
+ @$this->_initrs();
+ } else {
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ }
+ if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) {
+ $this->_currentRow = 0;
+ if ($this->EOF = ($this->_fetch() === false)) {
+ $this->_numOfRows = 0; // _numOfRows could be -1
+ }
+ } else {
+ $this->EOF = true;
+ }
+ }
+
+
+ /**
+ * Generate a SELECT tag string from a recordset, and return the string.
+ * If the recordset has 2 cols, we treat the 1st col as the containing
+ * the text to display to the user, and 2nd col as the return value. Default
+ * strings are compared with the FIRST column.
+ *
+ * @param name name of SELECT tag
+ * @param [defstr] the value to hilite. Use an array for multiple hilites for listbox.
+ * @param [blank1stItem] true to leave the 1st item in list empty
+ * @param [multiple] true for listbox, false for popup
+ * @param [size] #rows to show for listbox. not used by popup
+ * @param [selectAttr] additional attributes to defined for SELECT tag.
+ * useful for holding javascript onChange='...' handlers.
+ & @param [compareFields0] when we have 2 cols in recordset, we compare the defstr with
+ * column 0 (1st col) if this is true. This is not documented.
+ *
+ * @return HTML
+ *
+ * changes by glen.davies@cce.ac.nz to support multiple hilited items
+ */
+ function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false,
+ $size=0, $selectAttr='',$compareFields0=true)
+ {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple,
+ $size, $selectAttr,$compareFields0);
+ }
+
+
+
+ /**
+ * Generate a SELECT tag string from a recordset, and return the string.
+ * If the recordset has 2 cols, we treat the 1st col as the containing
+ * the text to display to the user, and 2nd col as the return value. Default
+ * strings are compared with the SECOND column.
+ *
+ */
+ function GetMenu2($name,$defstr='',$blank1stItem=true,$multiple=false,$size=0, $selectAttr='') {
+ return $this->GetMenu($name,$defstr,$blank1stItem,$multiple,
+ $size, $selectAttr,false);
+ }
+
+ /*
+ Grouped Menu
+ */
+ function GetMenu3($name,$defstr='',$blank1stItem=true,$multiple=false,
+ $size=0, $selectAttr='')
+ {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ return _adodb_getmenu_gp($this, $name,$defstr,$blank1stItem,$multiple,
+ $size, $selectAttr,false);
+ }
+
+ /**
+ * return recordset as a 2-dimensional array.
+ *
+ * @param [nRows] is the number of rows to return. -1 means every row.
+ *
+ * @return an array indexed by the rows (0-based) from the recordset
+ */
+ function GetArray($nRows = -1) {
+ global $ADODB_EXTENSION; if ($ADODB_EXTENSION) {
+ $results = adodb_getall($this,$nRows);
+ return $results;
+ }
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nRows != $cnt) {
+ $results[] = $this->fields;
+ $this->MoveNext();
+ $cnt++;
+ }
+ return $results;
+ }
+
+ function GetAll($nRows = -1) {
+ $arr = $this->GetArray($nRows);
+ return $arr;
+ }
+
+ /*
+ * Some databases allow multiple recordsets to be returned. This function
+ * will return true if there is a next recordset, or false if no more.
+ */
+ function NextRecordSet() {
+ return false;
+ }
+
+ /**
+ * return recordset as a 2-dimensional array.
+ * Helper function for ADOConnection->SelectLimit()
+ *
+ * @param offset is the row to start calculations from (1-based)
+ * @param [nrows] is the number of rows to return
+ *
+ * @return an array indexed by the rows (0-based) from the recordset
+ */
+ function GetArrayLimit($nrows,$offset=-1) {
+ if ($offset <= 0) {
+ $arr = $this->GetArray($nrows);
+ return $arr;
+ }
+
+ $this->Move($offset);
+
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+
+ /**
+ * Synonym for GetArray() for compatibility with ADO.
+ *
+ * @param [nRows] is the number of rows to return. -1 means every row.
+ *
+ * @return an array indexed by the rows (0-based) from the recordset
+ */
+ function GetRows($nRows = -1) {
+ $arr = $this->GetArray($nRows);
+ return $arr;
+ }
+
+ /**
+ * return whole recordset as a 2-dimensional associative array if there are more than 2 columns.
+ * The first column is treated as the key and is not included in the array.
+ * If there is only 2 columns, it will return a 1 dimensional array of key-value pairs unless
+ * $force_array == true.
+ *
+ * @param [force_array] has only meaning if we have 2 data columns. If false, a 1 dimensional
+ * array is returned, otherwise a 2 dimensional array is returned. If this sounds confusing,
+ * read the source.
+ *
+ * @param [first2cols] means if there are more than 2 cols, ignore the remaining cols and
+ * instead of returning array[col0] => array(remaining cols), return array[col0] => col1
+ *
+ * @return an associative array indexed by the first column of the array,
+ * or false if the data has less than 2 cols.
+ */
+ function GetAssoc($force_array = false, $first2cols = false) {
+ global $ADODB_EXTENSION;
+
+ $cols = $this->_numOfFields;
+ if ($cols < 2) {
+ return false;
+ }
+
+ // Empty recordset
+ if (!$this->fields) {
+ return array();
+ }
+
+ // Determine whether the array is associative or 0-based numeric
+ $numIndex = array_keys($this->fields) == range(0, count($this->fields) - 1);
+
+ $results = array();
+
+ if (!$first2cols && ($cols > 2 || $force_array)) {
+ if ($ADODB_EXTENSION) {
+ if ($numIndex) {
+ while (!$this->EOF) {
+ $results[trim($this->fields[0])] = array_slice($this->fields, 1);
+ adodb_movenext($this);
+ }
+ } else {
+ while (!$this->EOF) {
+ // Fix for array_slice re-numbering numeric associative keys
+ $keys = array_slice(array_keys($this->fields), 1);
+ $sliced_array = array();
+
+ foreach($keys as $key) {
+ $sliced_array[$key] = $this->fields[$key];
+ }
+
+ $results[trim(reset($this->fields))] = $sliced_array;
+ adodb_movenext($this);
+ }
+ }
+ } else {
+ if ($numIndex) {
+ while (!$this->EOF) {
+ $results[trim($this->fields[0])] = array_slice($this->fields, 1);
+ $this->MoveNext();
+ }
+ } else {
+ while (!$this->EOF) {
+ // Fix for array_slice re-numbering numeric associative keys
+ $keys = array_slice(array_keys($this->fields), 1);
+ $sliced_array = array();
+
+ foreach($keys as $key) {
+ $sliced_array[$key] = $this->fields[$key];
+ }
+
+ $results[trim(reset($this->fields))] = $sliced_array;
+ $this->MoveNext();
+ }
+ }
+ }
+ } else {
+ if ($ADODB_EXTENSION) {
+ // return scalar values
+ if ($numIndex) {
+ while (!$this->EOF) {
+ // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
+ $results[trim(($this->fields[0]))] = $this->fields[1];
+ adodb_movenext($this);
+ }
+ } else {
+ while (!$this->EOF) {
+ // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
+ $v1 = trim(reset($this->fields));
+ $v2 = ''.next($this->fields);
+ $results[$v1] = $v2;
+ adodb_movenext($this);
+ }
+ }
+ } else {
+ if ($numIndex) {
+ while (!$this->EOF) {
+ // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
+ $results[trim(($this->fields[0]))] = $this->fields[1];
+ $this->MoveNext();
+ }
+ } else {
+ while (!$this->EOF) {
+ // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
+ $v1 = trim(reset($this->fields));
+ $v2 = ''.next($this->fields);
+ $results[$v1] = $v2;
+ $this->MoveNext();
+ }
+ }
+ }
+ }
+
+ $ref = $results; # workaround accelerator incompat with PHP 4.4 :(
+ return $ref;
+ }
+
+
+ /**
+ *
+ * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format
+ * @param fmt is the format to apply to it, using date()
+ *
+ * @return a timestamp formated as user desires
+ */
+ function UserTimeStamp($v,$fmt='Y-m-d H:i:s') {
+ if (is_numeric($v) && strlen($v)<14) {
+ return adodb_date($fmt,$v);
+ }
+ $tt = $this->UnixTimeStamp($v);
+ // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
+ if (($tt === false || $tt == -1) && $v != false) {
+ return $v;
+ }
+ if ($tt === 0) {
+ return $this->emptyTimeStamp;
+ }
+ return adodb_date($fmt,$tt);
+ }
+
+
+ /**
+ * @param v is the character date in YYYY-MM-DD format, returned by database
+ * @param fmt is the format to apply to it, using date()
+ *
+ * @return a date formated as user desires
+ */
+ function UserDate($v,$fmt='Y-m-d') {
+ $tt = $this->UnixDate($v);
+ // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
+ if (($tt === false || $tt == -1) && $v != false) {
+ return $v;
+ } else if ($tt == 0) {
+ return $this->emptyDate;
+ } else if ($tt == -1) {
+ // pre-TIMESTAMP_FIRST_YEAR
+ }
+ return adodb_date($fmt,$tt);
+ }
+
+
+ /**
+ * @param $v is a date string in YYYY-MM-DD format
+ *
+ * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
+ */
+ static function UnixDate($v) {
+ return ADOConnection::UnixDate($v);
+ }
+
+
+ /**
+ * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
+ *
+ * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
+ */
+ static function UnixTimeStamp($v) {
+ return ADOConnection::UnixTimeStamp($v);
+ }
+
+
+ /**
+ * PEAR DB Compat - do not use internally
+ */
+ function Free() {
+ return $this->Close();
+ }
+
+
+ /**
+ * PEAR DB compat, number of rows
+ */
+ function NumRows() {
+ return $this->_numOfRows;
+ }
+
+
+ /**
+ * PEAR DB compat, number of cols
+ */
+ function NumCols() {
+ return $this->_numOfFields;
+ }
+
+ /**
+ * Fetch a row, returning false if no more rows.
+ * This is PEAR DB compat mode.
+ *
+ * @return false or array containing the current record
+ */
+ function FetchRow() {
+ if ($this->EOF) {
+ return false;
+ }
+ $arr = $this->fields;
+ $this->_currentRow++;
+ if (!$this->_fetch()) {
+ $this->EOF = true;
+ }
+ return $arr;
+ }
+
+
+ /**
+ * Fetch a row, returning PEAR_Error if no more rows.
+ * This is PEAR DB compat mode.
+ *
+ * @return DB_OK or error object
+ */
+ function FetchInto(&$arr) {
+ if ($this->EOF) {
+ return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false;
+ }
+ $arr = $this->fields;
+ $this->MoveNext();
+ return 1; // DB_OK
+ }
+
+
+ /**
+ * Move to the first row in the recordset. Many databases do NOT support this.
+ *
+ * @return true or false
+ */
+ function MoveFirst() {
+ if ($this->_currentRow == 0) {
+ return true;
+ }
+ return $this->Move(0);
+ }
+
+
+ /**
+ * Move to the last row in the recordset.
+ *
+ * @return true or false
+ */
+ function MoveLast() {
+ if ($this->_numOfRows >= 0) {
+ return $this->Move($this->_numOfRows-1);
+ }
+ if ($this->EOF) {
+ return false;
+ }
+ while (!$this->EOF) {
+ $f = $this->fields;
+ $this->MoveNext();
+ }
+ $this->fields = $f;
+ $this->EOF = false;
+ return true;
+ }
+
+
+ /**
+ * Move to next record in the recordset.
+ *
+ * @return true if there still rows available, or false if there are no more rows (EOF).
+ */
+ function MoveNext() {
+ if (!$this->EOF) {
+ $this->_currentRow++;
+ if ($this->_fetch()) {
+ return true;
+ }
+ }
+ $this->EOF = true;
+ /* -- tested error handling when scrolling cursor -- seems useless.
+ $conn = $this->connection;
+ if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) {
+ $fn = $conn->raiseErrorFn;
+ $fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database);
+ }
+ */
+ return false;
+ }
+
+
+ /**
+ * Random access to a specific row in the recordset. Some databases do not support
+ * access to previous rows in the databases (no scrolling backwards).
+ *
+ * @param rowNumber is the row to move to (0-based)
+ *
+ * @return true if there still rows available, or false if there are no more rows (EOF).
+ */
+ function Move($rowNumber = 0) {
+ $this->EOF = false;
+ if ($rowNumber == $this->_currentRow) {
+ return true;
+ }
+ if ($rowNumber >= $this->_numOfRows) {
+ if ($this->_numOfRows != -1) {
+ $rowNumber = $this->_numOfRows-2;
+ }
+ }
+
+ if ($rowNumber < 0) {
+ $this->EOF = true;
+ return false;
+ }
+
+ if ($this->canSeek) {
+ if ($this->_seek($rowNumber)) {
+ $this->_currentRow = $rowNumber;
+ if ($this->_fetch()) {
+ return true;
+ }
+ } else {
+ $this->EOF = true;
+ return false;
+ }
+ } else {
+ if ($rowNumber < $this->_currentRow) {
+ return false;
+ }
+ global $ADODB_EXTENSION;
+ if ($ADODB_EXTENSION) {
+ while (!$this->EOF && $this->_currentRow < $rowNumber) {
+ adodb_movenext($this);
+ }
+ } else {
+ while (! $this->EOF && $this->_currentRow < $rowNumber) {
+ $this->_currentRow++;
+
+ if (!$this->_fetch()) {
+ $this->EOF = true;
+ }
+ }
+ }
+ return !($this->EOF);
+ }
+
+ $this->fields = false;
+ $this->EOF = true;
+ return false;
+ }
+
+
+ /**
+ * Get the value of a field in the current row by column name.
+ * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
+ *
+ * @param colname is the field to access
+ *
+ * @return the value of $colname column
+ */
+ function Fields($colname) {
+ return $this->fields[$colname];
+ }
+
+ /**
+ * Defines the function to use for table fields case conversion
+ * depending on ADODB_ASSOC_CASE
+ * @return string strtolower/strtoupper or false if no conversion needed
+ */
+ protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) {
+ switch($case) {
+ case ADODB_ASSOC_CASE_UPPER:
+ return 'strtoupper';
+ case ADODB_ASSOC_CASE_LOWER:
+ return 'strtolower';
+ case ADODB_ASSOC_CASE_NATIVE:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Builds the bind array associating keys to recordset fields
+ *
+ * @param int $upper Case for the array keys, defaults to uppercase
+ * (see ADODB_ASSOC_CASE_xxx constants)
+ */
+ function GetAssocKeys($upper = ADODB_ASSOC_CASE) {
+ if ($this->bind) {
+ return;
+ }
+ $this->bind = array();
+
+ // Define case conversion function for ASSOC fetch mode
+ $fn_change_case = $this->AssocCaseConvertFunction($upper);
+
+ // Build the bind array
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+
+ // Set the array's key
+ if(is_numeric($o->name)) {
+ // Just use the field ID
+ $key = $i;
+ }
+ elseif( $fn_change_case ) {
+ // Convert the key's case
+ $key = $fn_change_case($o->name);
+ }
+ else {
+ $key = $o->name;
+ }
+
+ $this->bind[$key] = $i;
+ }
+ }
+
+ /**
+ * Use associative array to get fields array for databases that do not support
+ * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it
+ *
+ * @param int $upper Case for the array keys, defaults to uppercase
+ * (see ADODB_ASSOC_CASE_xxx constants)
+ */
+ function GetRowAssoc($upper = ADODB_ASSOC_CASE) {
+ $record = array();
+ $this->GetAssocKeys($upper);
+
+ foreach($this->bind as $k => $v) {
+ if( array_key_exists( $v, $this->fields ) ) {
+ $record[$k] = $this->fields[$v];
+ } elseif( array_key_exists( $k, $this->fields ) ) {
+ $record[$k] = $this->fields[$k];
+ } else {
+ # This should not happen... trigger error ?
+ $record[$k] = null;
+ }
+ }
+ return $record;
+ }
+
+ /**
+ * Clean up recordset
+ *
+ * @return true or false
+ */
+ function Close() {
+ // free connection object - this seems to globally free the object
+ // and not merely the reference, so don't do this...
+ // $this->connection = false;
+ if (!$this->_closed) {
+ $this->_closed = true;
+ return $this->_close();
+ } else
+ return true;
+ }
+
+ /**
+ * synonyms RecordCount and RowCount
+ *
+ * @return the number of rows or -1 if this is not supported
+ */
+ function RecordCount() {
+ return $this->_numOfRows;
+ }
+
+
+ /*
+ * If we are using PageExecute(), this will return the maximum possible rows
+ * that can be returned when paging a recordset.
+ */
+ function MaxRecordCount() {
+ return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount();
+ }
+
+ /**
+ * synonyms RecordCount and RowCount
+ *
+ * @return the number of rows or -1 if this is not supported
+ */
+ function RowCount() {
+ return $this->_numOfRows;
+ }
+
+
+ /**
+ * Portable RecordCount. Pablo Roca <pabloroca@mvps.org>
+ *
+ * @return the number of records from a previous SELECT. All databases support this.
+ *
+ * But aware possible problems in multiuser environments. For better speed the table
+ * must be indexed by the condition. Heavy test this before deploying.
+ */
+ function PO_RecordCount($table="", $condition="") {
+
+ $lnumrows = $this->_numOfRows;
+ // the database doesn't support native recordcount, so we do a workaround
+ if ($lnumrows == -1 && $this->connection) {
+ IF ($table) {
+ if ($condition) {
+ $condition = " WHERE " . $condition;
+ }
+ $resultrows = $this->connection->Execute("SELECT COUNT(*) FROM $table $condition");
+ if ($resultrows) {
+ $lnumrows = reset($resultrows->fields);
+ }
+ }
+ }
+ return $lnumrows;
+ }
+
+
+ /**
+ * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
+ */
+ function CurrentRow() {
+ return $this->_currentRow;
+ }
+
+ /**
+ * synonym for CurrentRow -- for ADO compat
+ *
+ * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
+ */
+ function AbsolutePosition() {
+ return $this->_currentRow;
+ }
+
+ /**
+ * @return the number of columns in the recordset. Some databases will set this to 0
+ * if no records are returned, others will return the number of columns in the query.
+ */
+ function FieldCount() {
+ return $this->_numOfFields;
+ }
+
+
+ /**
+ * Get the ADOFieldObject of a specific column.
+ *
+ * @param fieldoffset is the column position to access(0-based).
+ *
+ * @return the ADOFieldObject for that column, or false.
+ */
+ function FetchField($fieldoffset = -1) {
+ // must be defined by child class
+
+ return false;
+ }
+
+ /**
+ * Get the ADOFieldObjects of all columns in an array.
+ *
+ */
+ function FieldTypesArray() {
+ $arr = array();
+ for ($i=0, $max=$this->_numOfFields; $i < $max; $i++)
+ $arr[] = $this->FetchField($i);
+ return $arr;
+ }
+
+ /**
+ * Return the fields array of the current row as an object for convenience.
+ * The default case is lowercase field names.
+ *
+ * @return the object with the properties set to the fields of the current row
+ */
+ function FetchObj() {
+ $o = $this->FetchObject(false);
+ return $o;
+ }
+
+ /**
+ * Return the fields array of the current row as an object for convenience.
+ * The default case is uppercase.
+ *
+ * @param $isupper to set the object property names to uppercase
+ *
+ * @return the object with the properties set to the fields of the current row
+ */
+ function FetchObject($isupper=true) {
+ if (empty($this->_obj)) {
+ $this->_obj = new ADOFetchObj();
+ $this->_names = array();
+ for ($i=0; $i <$this->_numOfFields; $i++) {
+ $f = $this->FetchField($i);
+ $this->_names[] = $f->name;
+ }
+ }
+ $i = 0;
+ if (PHP_VERSION >= 5) {
+ $o = clone($this->_obj);
+ } else {
+ $o = $this->_obj;
+ }
+
+ for ($i=0; $i <$this->_numOfFields; $i++) {
+ $name = $this->_names[$i];
+ if ($isupper) {
+ $n = strtoupper($name);
+ } else {
+ $n = $name;
+ }
+
+ $o->$n = $this->Fields($name);
+ }
+ return $o;
+ }
+
+ /**
+ * Return the fields array of the current row as an object for convenience.
+ * The default is lower-case field names.
+ *
+ * @return the object with the properties set to the fields of the current row,
+ * or false if EOF
+ *
+ * Fixed bug reported by tim@orotech.net
+ */
+ function FetchNextObj() {
+ $o = $this->FetchNextObject(false);
+ return $o;
+ }
+
+
+ /**
+ * Return the fields array of the current row as an object for convenience.
+ * The default is upper case field names.
+ *
+ * @param $isupper to set the object property names to uppercase
+ *
+ * @return the object with the properties set to the fields of the current row,
+ * or false if EOF
+ *
+ * Fixed bug reported by tim@orotech.net
+ */
+ function FetchNextObject($isupper=true) {
+ $o = false;
+ if ($this->_numOfRows != 0 && !$this->EOF) {
+ $o = $this->FetchObject($isupper);
+ $this->_currentRow++;
+ if ($this->_fetch()) {
+ return $o;
+ }
+ }
+ $this->EOF = true;
+ return $o;
+ }
+
+ /**
+ * Get the metatype of the column. This is used for formatting. This is because
+ * many databases use different names for the same type, so we transform the original
+ * type to our standardised version which uses 1 character codes:
+ *
+ * @param t is the type passed in. Normally is ADOFieldObject->type.
+ * @param len is the maximum length of that field. This is because we treat character
+ * fields bigger than a certain size as a 'B' (blob).
+ * @param fieldobj is the field object returned by the database driver. Can hold
+ * additional info (eg. primary_key for mysql).
+ *
+ * @return the general type of the data:
+ * C for character < 250 chars
+ * X for teXt (>= 250 chars)
+ * B for Binary
+ * N for numeric or floating point
+ * D for date
+ * T for timestamp
+ * L for logical/Boolean
+ * I for integer
+ * R for autoincrement counter/integer
+ *
+ *
+ */
+ function MetaType($t,$len=-1,$fieldobj=false) {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ // changed in 2.32 to hashing instead of switch stmt for speed...
+ static $typeMap = array(
+ 'VARCHAR' => 'C',
+ 'VARCHAR2' => 'C',
+ 'CHAR' => 'C',
+ 'C' => 'C',
+ 'STRING' => 'C',
+ 'NCHAR' => 'C',
+ 'NVARCHAR' => 'C',
+ 'VARYING' => 'C',
+ 'BPCHAR' => 'C',
+ 'CHARACTER' => 'C',
+ 'INTERVAL' => 'C', # Postgres
+ 'MACADDR' => 'C', # postgres
+ 'VAR_STRING' => 'C', # mysql
+ ##
+ 'LONGCHAR' => 'X',
+ 'TEXT' => 'X',
+ 'NTEXT' => 'X',
+ 'M' => 'X',
+ 'X' => 'X',
+ 'CLOB' => 'X',
+ 'NCLOB' => 'X',
+ 'LVARCHAR' => 'X',
+ ##
+ 'BLOB' => 'B',
+ 'IMAGE' => 'B',
+ 'BINARY' => 'B',
+ 'VARBINARY' => 'B',
+ 'LONGBINARY' => 'B',
+ 'B' => 'B',
+ ##
+ 'YEAR' => 'D', // mysql
+ 'DATE' => 'D',
+ 'D' => 'D',
+ ##
+ 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server
+ ##
+ 'SMALLDATETIME' => 'T',
+ 'TIME' => 'T',
+ 'TIMESTAMP' => 'T',
+ 'DATETIME' => 'T',
+ 'DATETIME2' => 'T',
+ 'TIMESTAMPTZ' => 'T',
+ 'T' => 'T',
+ 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql
+ ##
+ 'BOOL' => 'L',
+ 'BOOLEAN' => 'L',
+ 'BIT' => 'L',
+ 'L' => 'L',
+ ##
+ 'COUNTER' => 'R',
+ 'R' => 'R',
+ 'SERIAL' => 'R', // ifx
+ 'INT IDENTITY' => 'R',
+ ##
+ 'INT' => 'I',
+ 'INT2' => 'I',
+ 'INT4' => 'I',
+ 'INT8' => 'I',
+ 'INTEGER' => 'I',
+ 'INTEGER UNSIGNED' => 'I',
+ 'SHORT' => 'I',
+ 'TINYINT' => 'I',
+ 'SMALLINT' => 'I',
+ 'I' => 'I',
+ ##
+ 'LONG' => 'N', // interbase is numeric, oci8 is blob
+ 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
+ 'DECIMAL' => 'N',
+ 'DEC' => 'N',
+ 'REAL' => 'N',
+ 'DOUBLE' => 'N',
+ 'DOUBLE PRECISION' => 'N',
+ 'SMALLFLOAT' => 'N',
+ 'FLOAT' => 'N',
+ 'NUMBER' => 'N',
+ 'NUM' => 'N',
+ 'NUMERIC' => 'N',
+ 'MONEY' => 'N',
+
+ ## informix 9.2
+ 'SQLINT' => 'I',
+ 'SQLSERIAL' => 'I',
+ 'SQLSMINT' => 'I',
+ 'SQLSMFLOAT' => 'N',
+ 'SQLFLOAT' => 'N',
+ 'SQLMONEY' => 'N',
+ 'SQLDECIMAL' => 'N',
+ 'SQLDATE' => 'D',
+ 'SQLVCHAR' => 'C',
+ 'SQLCHAR' => 'C',
+ 'SQLDTIME' => 'T',
+ 'SQLINTERVAL' => 'N',
+ 'SQLBYTES' => 'B',
+ 'SQLTEXT' => 'X',
+ ## informix 10
+ "SQLINT8" => 'I8',
+ "SQLSERIAL8" => 'I8',
+ "SQLNCHAR" => 'C',
+ "SQLNVCHAR" => 'C',
+ "SQLLVARCHAR" => 'X',
+ "SQLBOOL" => 'L'
+ );
+
+ $tmap = false;
+ $t = strtoupper($t);
+ $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : 'N';
+ switch ($tmap) {
+ case 'C':
+ // is the char field is too long, return as text field...
+ if ($this->blobSize >= 0) {
+ if ($len > $this->blobSize) {
+ return 'X';
+ }
+ } else if ($len > 250) {
+ return 'X';
+ }
+ return 'C';
+
+ case 'I':
+ if (!empty($fieldobj->primary_key)) {
+ return 'R';
+ }
+ return 'I';
+
+ case false:
+ return 'N';
+
+ case 'B':
+ if (isset($fieldobj->binary)) {
+ return ($fieldobj->binary) ? 'B' : 'X';
+ }
+ return 'B';
+
+ case 'D':
+ if (!empty($this->connection) && !empty($this->connection->datetime)) {
+ return 'T';
+ }
+ return 'D';
+
+ default:
+ if ($t == 'LONG' && $this->dataProvider == 'oci8') {
+ return 'B';
+ }
+ return $tmap;
+ }
+ }
+
+ /**
+ * Convert case of field names associative array, if needed
+ * @return void
+ */
+ protected function _updatefields()
+ {
+ if( empty($this->fields)) {
+ return;
+ }
+
+ // Determine case conversion function
+ $fn_change_case = $this->AssocCaseConvertFunction();
+ if(!$fn_change_case) {
+ // No conversion needed
+ return;
+ }
+
+ $arr = array();
+
+ // Change the case
+ foreach($this->fields as $k => $v) {
+ if (!is_integer($k)) {
+ $k = $fn_change_case($k);
+ }
+ $arr[$k] = $v;
+ }
+ $this->fields = $arr;
+ }
+
+ function _close() {}
+
+ /**
+ * set/returns the current recordset page when paginating
+ */
+ function AbsolutePage($page=-1) {
+ if ($page != -1) {
+ $this->_currentPage = $page;
+ }
+ return $this->_currentPage;
+ }
+
+ /**
+ * set/returns the status of the atFirstPage flag when paginating
+ */
+ function AtFirstPage($status=false) {
+ if ($status != false) {
+ $this->_atFirstPage = $status;
+ }
+ return $this->_atFirstPage;
+ }
+
+ function LastPageNo($page = false) {
+ if ($page != false) {
+ $this->_lastPageNo = $page;
+ }
+ return $this->_lastPageNo;
+ }
+
+ /**
+ * set/returns the status of the atLastPage flag when paginating
+ */
+ function AtLastPage($status=false) {
+ if ($status != false) {
+ $this->_atLastPage = $status;
+ }
+ return $this->_atLastPage;
+ }
+
+} // end class ADORecordSet
+
+ //==============================================================================================
+ // CLASS ADORecordSet_array
+ //==============================================================================================
+
+ /**
+ * This class encapsulates the concept of a recordset created in memory
+ * as an array. This is useful for the creation of cached recordsets.
+ *
+ * Note that the constructor is different from the standard ADORecordSet
+ */
+ class ADORecordSet_array extends ADORecordSet
+ {
+ var $databaseType = 'array';
+
+ var $_array; // holds the 2-dimensional data array
+ var $_types; // the array of types of each column (C B I L M)
+ var $_colnames; // names of each column in array
+ var $_skiprow1; // skip 1st row because it holds column names
+ var $_fieldobjects; // holds array of field objects
+ var $canSeek = true;
+ var $affectedrows = false;
+ var $insertid = false;
+ var $sql = '';
+ var $compat = false;
+
+ /**
+ * Constructor
+ */
+ function __construct($fakeid=1) {
+ global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH;
+
+ // fetch() on EOF does not delete $this->fields
+ $this->compat = !empty($ADODB_COMPAT_FETCH);
+ parent::__construct($fakeid); // fake queryID
+ $this->fetchMode = $ADODB_FETCH_MODE;
+ }
+
+ function _transpose($addfieldnames=true) {
+ global $ADODB_INCLUDED_LIB;
+
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ $hdr = true;
+
+ $fobjs = $addfieldnames ? $this->_fieldobjects : false;
+ adodb_transpose($this->_array, $newarr, $hdr, $fobjs);
+ //adodb_pr($newarr);
+
+ $this->_skiprow1 = false;
+ $this->_array = $newarr;
+ $this->_colnames = $hdr;
+
+ adodb_probetypes($newarr,$this->_types);
+
+ $this->_fieldobjects = array();
+
+ foreach($hdr as $k => $name) {
+ $f = new ADOFieldObject();
+ $f->name = $name;
+ $f->type = $this->_types[$k];
+ $f->max_length = -1;
+ $this->_fieldobjects[] = $f;
+ }
+ $this->fields = reset($this->_array);
+
+ $this->_initrs();
+
+ }
+
+ /**
+ * Setup the array.
+ *
+ * @param array is a 2-dimensional array holding the data.
+ * The first row should hold the column names
+ * unless paramter $colnames is used.
+ * @param typearr holds an array of types. These are the same types
+ * used in MetaTypes (C,B,L,I,N).
+ * @param [colnames] array of column names. If set, then the first row of
+ * $array should not hold the column names.
+ */
+ function InitArray($array,$typearr,$colnames=false) {
+ $this->_array = $array;
+ $this->_types = $typearr;
+ if ($colnames) {
+ $this->_skiprow1 = false;
+ $this->_colnames = $colnames;
+ } else {
+ $this->_skiprow1 = true;
+ $this->_colnames = $array[0];
+ }
+ $this->Init();
+ }
+ /**
+ * Setup the Array and datatype file objects
+ *
+ * @param array is a 2-dimensional array holding the data.
+ * The first row should hold the column names
+ * unless paramter $colnames is used.
+ * @param fieldarr holds an array of ADOFieldObject's.
+ */
+ function InitArrayFields(&$array,&$fieldarr) {
+ $this->_array = $array;
+ $this->_skiprow1= false;
+ if ($fieldarr) {
+ $this->_fieldobjects = $fieldarr;
+ }
+ $this->Init();
+ }
+
+ function GetArray($nRows=-1) {
+ if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) {
+ return $this->_array;
+ } else {
+ $arr = ADORecordSet::GetArray($nRows);
+ return $arr;
+ }
+ }
+
+ function _initrs() {
+ $this->_numOfRows = sizeof($this->_array);
+ if ($this->_skiprow1) {
+ $this->_numOfRows -= 1;
+ }
+
+ $this->_numOfFields = (isset($this->_fieldobjects))
+ ? sizeof($this->_fieldobjects)
+ : sizeof($this->_types);
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname) {
+ $mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode;
+
+ if ($mode & ADODB_FETCH_ASSOC) {
+ if (!isset($this->fields[$colname]) && !is_null($this->fields[$colname])) {
+ $colname = strtolower($colname);
+ }
+ return $this->fields[$colname];
+ }
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function FetchField($fieldOffset = -1) {
+ if (isset($this->_fieldobjects)) {
+ return $this->_fieldobjects[$fieldOffset];
+ }
+ $o = new ADOFieldObject();
+ $o->name = $this->_colnames[$fieldOffset];
+ $o->type = $this->_types[$fieldOffset];
+ $o->max_length = -1; // length not known
+
+ return $o;
+ }
+
+ function _seek($row) {
+ if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) {
+ $this->_currentRow = $row;
+ if ($this->_skiprow1) {
+ $row += 1;
+ }
+ $this->fields = $this->_array[$row];
+ return true;
+ }
+ return false;
+ }
+
+ function MoveNext() {
+ if (!$this->EOF) {
+ $this->_currentRow++;
+
+ $pos = $this->_currentRow;
+
+ if ($this->_numOfRows <= $pos) {
+ if (!$this->compat) {
+ $this->fields = false;
+ }
+ } else {
+ if ($this->_skiprow1) {
+ $pos += 1;
+ }
+ $this->fields = $this->_array[$pos];
+ return true;
+ }
+ $this->EOF = true;
+ }
+
+ return false;
+ }
+
+ function _fetch() {
+ $pos = $this->_currentRow;
+
+ if ($this->_numOfRows <= $pos) {
+ if (!$this->compat) {
+ $this->fields = false;
+ }
+ return false;
+ }
+ if ($this->_skiprow1) {
+ $pos += 1;
+ }
+ $this->fields = $this->_array[$pos];
+ return true;
+ }
+
+ function _close() {
+ return true;
+ }
+
+ } // ADORecordSet_array
+
+ //==============================================================================================
+ // HELPER FUNCTIONS
+ //==============================================================================================
+
+ /**
+ * Synonym for ADOLoadCode. Private function. Do not use.
+ *
+ * @deprecated
+ */
+ function ADOLoadDB($dbType) {
+ return ADOLoadCode($dbType);
+ }
+
+ /**
+ * Load the code for a specific database driver. Private function. Do not use.
+ */
+ function ADOLoadCode($dbType) {
+ global $ADODB_LASTDB;
+
+ if (!$dbType) {
+ return false;
+ }
+ $db = strtolower($dbType);
+ switch ($db) {
+ case 'ado':
+ if (PHP_VERSION >= 5) {
+ $db = 'ado5';
+ }
+ $class = 'ado';
+ break;
+
+ case 'ifx':
+ case 'maxsql':
+ $class = $db = 'mysqlt';
+ break;
+
+ case 'pgsql':
+ case 'postgres':
+ $class = $db = 'postgres8';
+ break;
+
+ default:
+ $class = $db; break;
+ }
+
+ $file = "drivers/adodb-$db.inc.php";
+ @include_once(ADODB_DIR . '/' . $file);
+ $ADODB_LASTDB = $class;
+ if (class_exists("ADODB_" . $class)) {
+ return $class;
+ }
+
+ //ADOConnection::outp(adodb_pr(get_declared_classes(),true));
+ if (!file_exists($file)) {
+ ADOConnection::outp("Missing file: $file");
+ } else {
+ ADOConnection::outp("Syntax error in file: $file");
+ }
+ return false;
+ }
+
+ /**
+ * synonym for ADONewConnection for people like me who cannot remember the correct name
+ */
+ function NewADOConnection($db='') {
+ $tmp = ADONewConnection($db);
+ return $tmp;
+ }
+
+ /**
+ * Instantiate a new Connection class for a specific database driver.
+ *
+ * @param [db] is the database Connection object to create. If undefined,
+ * use the last database driver that was loaded by ADOLoadCode().
+ *
+ * @return the freshly created instance of the Connection class.
+ */
+ function ADONewConnection($db='') {
+ global $ADODB_NEWCONNECTION, $ADODB_LASTDB;
+
+ if (!defined('ADODB_ASSOC_CASE')) {
+ define('ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_NATIVE);
+ }
+ $errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false;
+ if (($at = strpos($db,'://')) !== FALSE) {
+ $origdsn = $db;
+ $fakedsn = 'fake'.substr($origdsn,$at);
+ if (($at2 = strpos($origdsn,'@/')) !== FALSE) {
+ // special handling of oracle, which might not have host
+ $fakedsn = str_replace('@/','@adodb-fakehost/',$fakedsn);
+ }
+
+ if ((strpos($origdsn, 'sqlite')) !== FALSE && stripos($origdsn, '%2F') === FALSE) {
+ // special handling for SQLite, it only might have the path to the database file.
+ // If you try to connect to a SQLite database using a dsn
+ // like 'sqlite:///path/to/database', the 'parse_url' php function
+ // will throw you an exception with a message such as "unable to parse url"
+ list($scheme, $path) = explode('://', $origdsn);
+ $dsna['scheme'] = $scheme;
+ if ($qmark = strpos($path,'?')) {
+ $dsn['query'] = substr($path,$qmark+1);
+ $path = substr($path,0,$qmark);
+ }
+ $dsna['path'] = '/' . urlencode($path);
+ } else
+ $dsna = @parse_url($fakedsn);
+
+ if (!$dsna) {
+ return false;
+ }
+ $dsna['scheme'] = substr($origdsn,0,$at);
+ if ($at2 !== FALSE) {
+ $dsna['host'] = '';
+ }
+
+ if (strncmp($origdsn,'pdo',3) == 0) {
+ $sch = explode('_',$dsna['scheme']);
+ if (sizeof($sch)>1) {
+ $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
+ if ($sch[1] == 'sqlite') {
+ $dsna['host'] = rawurlencode($sch[1].':'.rawurldecode($dsna['host']));
+ } else {
+ $dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host']));
+ }
+ $dsna['scheme'] = 'pdo';
+ }
+ }
+
+ $db = @$dsna['scheme'];
+ if (!$db) {
+ return false;
+ }
+ $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
+ $dsna['user'] = isset($dsna['user']) ? rawurldecode($dsna['user']) : '';
+ $dsna['pass'] = isset($dsna['pass']) ? rawurldecode($dsna['pass']) : '';
+ $dsna['path'] = isset($dsna['path']) ? rawurldecode(substr($dsna['path'],1)) : ''; # strip off initial /
+
+ if (isset($dsna['query'])) {
+ $opt1 = explode('&',$dsna['query']);
+ foreach($opt1 as $k => $v) {
+ $arr = explode('=',$v);
+ $opt[$arr[0]] = isset($arr[1]) ? rawurldecode($arr[1]) : 1;
+ }
+ } else {
+ $opt = array();
+ }
+ }
+ /*
+ * phptype: Database backend used in PHP (mysql, odbc etc.)
+ * dbsyntax: Database used with regards to SQL syntax etc.
+ * protocol: Communication protocol to use (tcp, unix etc.)
+ * hostspec: Host specification (hostname[:port])
+ * database: Database to use on the DBMS server
+ * username: User name for login
+ * password: Password for login
+ */
+ if (!empty($ADODB_NEWCONNECTION)) {
+ $obj = $ADODB_NEWCONNECTION($db);
+
+ }
+
+ if(empty($obj)) {
+
+ if (!isset($ADODB_LASTDB)) {
+ $ADODB_LASTDB = '';
+ }
+ if (empty($db)) {
+ $db = $ADODB_LASTDB;
+ }
+ if ($db != $ADODB_LASTDB) {
+ $db = ADOLoadCode($db);
+ }
+
+ if (!$db) {
+ if (isset($origdsn)) {
+ $db = $origdsn;
+ }
+ if ($errorfn) {
+ // raise an error
+ $ignore = false;
+ $errorfn('ADONewConnection', 'ADONewConnection', -998,
+ "could not load the database driver for '$db'",
+ $db,false,$ignore);
+ } else {
+ ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false);
+ }
+ return false;
+ }
+
+ $cls = 'ADODB_'.$db;
+ if (!class_exists($cls)) {
+ adodb_backtrace();
+ return false;
+ }
+
+ $obj = new $cls();
+ }
+
+ # constructor should not fail
+ if ($obj) {
+ if ($errorfn) {
+ $obj->raiseErrorFn = $errorfn;
+ }
+ if (isset($dsna)) {
+ if (isset($dsna['port'])) {
+ $obj->port = $dsna['port'];
+ }
+ foreach($opt as $k => $v) {
+ switch(strtolower($k)) {
+ case 'new':
+ $nconnect = true; $persist = true; break;
+ case 'persist':
+ case 'persistent': $persist = $v; break;
+ case 'debug': $obj->debug = (integer) $v; break;
+ #ibase
+ case 'role': $obj->role = $v; break;
+ case 'dialect': $obj->dialect = (integer) $v; break;
+ case 'charset': $obj->charset = $v; $obj->charSet=$v; break;
+ case 'buffers': $obj->buffers = $v; break;
+ case 'fetchmode': $obj->SetFetchMode($v); break;
+ #ado
+ case 'charpage': $obj->charPage = $v; break;
+ #mysql, mysqli
+ case 'clientflags': $obj->clientFlags = $v; break;
+ #mysql, mysqli, postgres
+ case 'port': $obj->port = $v; break;
+ #mysqli
+ case 'socket': $obj->socket = $v; break;
+ #oci8
+ case 'nls_date_format': $obj->NLS_DATE_FORMAT = $v; break;
+ case 'cachesecs': $obj->cacheSecs = $v; break;
+ case 'memcache':
+ $varr = explode(':',$v);
+ $vlen = sizeof($varr);
+ if ($vlen == 0) {
+ break;
+ }
+ $obj->memCache = true;
+ $obj->memCacheHost = explode(',',$varr[0]);
+ if ($vlen == 1) {
+ break;
+ }
+ $obj->memCachePort = $varr[1];
+ if ($vlen == 2) {
+ break;
+ }
+ $obj->memCacheCompress = $varr[2] ? true : false;
+ break;
+ }
+ }
+ if (empty($persist)) {
+ $ok = $obj->Connect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
+ } else if (empty($nconnect)) {
+ $ok = $obj->PConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
+ } else {
+ $ok = $obj->NConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
+ }
+
+ if (!$ok) {
+ return false;
+ }
+ }
+ }
+ return $obj;
+ }
+
+
+
+ // $perf == true means called by NewPerfMonitor(), otherwise for data dictionary
+ function _adodb_getdriver($provider,$drivername,$perf=false) {
+ switch ($provider) {
+ case 'odbtp':
+ if (strncmp('odbtp_',$drivername,6)==0) {
+ return substr($drivername,6);
+ }
+ case 'odbc' :
+ if (strncmp('odbc_',$drivername,5)==0) {
+ return substr($drivername,5);
+ }
+ case 'ado' :
+ if (strncmp('ado_',$drivername,4)==0) {
+ return substr($drivername,4);
+ }
+ case 'native':
+ break;
+ default:
+ return $provider;
+ }
+
+ switch($drivername) {
+ case 'mysqlt':
+ case 'mysqli':
+ $drivername='mysql';
+ break;
+ case 'postgres7':
+ case 'postgres8':
+ $drivername = 'postgres';
+ break;
+ case 'firebird15':
+ $drivername = 'firebird';
+ break;
+ case 'oracle':
+ $drivername = 'oci8';
+ break;
+ case 'access':
+ if ($perf) {
+ $drivername = '';
+ }
+ break;
+ case 'db2' :
+ case 'sapdb' :
+ break;
+ default:
+ $drivername = 'generic';
+ break;
+ }
+ return $drivername;
+ }
+
+ function NewPerfMonitor(&$conn) {
+ $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true);
+ if (!$drivername || $drivername == 'generic') {
+ return false;
+ }
+ include_once(ADODB_DIR.'/adodb-perf.inc.php');
+ @include_once(ADODB_DIR."/perf/perf-$drivername.inc.php");
+ $class = "Perf_$drivername";
+ if (!class_exists($class)) {
+ return false;
+ }
+ $perf = new $class($conn);
+
+ return $perf;
+ }
+
+ function NewDataDictionary(&$conn,$drivername=false) {
+ if (!$drivername) {
+ $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType);
+ }
+
+ include_once(ADODB_DIR.'/adodb-lib.inc.php');
+ include_once(ADODB_DIR.'/adodb-datadict.inc.php');
+ $path = ADODB_DIR."/datadict/datadict-$drivername.inc.php";
+
+ if (!file_exists($path)) {
+ ADOConnection::outp("Dictionary driver '$path' not available");
+ return false;
+ }
+ include_once($path);
+ $class = "ADODB2_$drivername";
+ $dict = new $class();
+ $dict->dataProvider = $conn->dataProvider;
+ $dict->connection = $conn;
+ $dict->upperName = strtoupper($drivername);
+ $dict->quote = $conn->nameQuote;
+ if (!empty($conn->_connectionID)) {
+ $dict->serverInfo = $conn->ServerInfo();
+ }
+
+ return $dict;
+ }
+
+
+
+ /*
+ Perform a print_r, with pre tags for better formatting.
+ */
+ function adodb_pr($var,$as_string=false) {
+ if ($as_string) {
+ ob_start();
+ }
+
+ if (isset($_SERVER['HTTP_USER_AGENT'])) {
+ echo " <pre>\n";print_r($var);echo "</pre>\n";
+ } else {
+ print_r($var);
+ }
+
+ if ($as_string) {
+ $s = ob_get_contents();
+ ob_end_clean();
+ return $s;
+ }
+ }
+
+ /*
+ Perform a stack-crawl and pretty print it.
+
+ @param printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
+ @param levels Number of levels to display
+ */
+ function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) {
+ global $ADODB_INCLUDED_LIB;
+ if (empty($ADODB_INCLUDED_LIB)) {
+ include(ADODB_DIR.'/adodb-lib.inc.php');
+ }
+ return _adodb_backtrace($printOrArr,$levels,0,$ishtml);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/composer.json b/vendor/adodb/adodb-php/composer.json
new file mode 100644
index 0000000..30fcc35
--- /dev/null
+++ b/vendor/adodb/adodb-php/composer.json
@@ -0,0 +1,37 @@
+{
+ "name" : "adodb/adodb-php",
+ "description" : "ADOdb is a PHP database abstraction layer library",
+ "license" : [ "BSD-3-Clause", "LGPL-2.1-or-later" ],
+ "authors" : [
+ {
+ "name": "John Lim",
+ "email" : "jlim@natsoft.com",
+ "role": "Author"
+ },
+ {
+ "name": "Damien Regad",
+ "role": "Current maintainer"
+ },
+ {
+ "name": "Mark Newnham",
+ "role": "Developer"
+ }
+ ],
+
+ "keywords" : [ "database", "abstraction", "layer", "library", "php" ],
+
+ "homepage": "http://adodb.org/",
+ "support" : {
+ "issues" : "https://github.com/ADOdb/ADOdb/issues",
+ "source" : "https://github.com/ADOdb/ADOdb"
+ },
+
+ "require" : {
+ "php" : ">=5.3.2"
+ },
+
+ "autoload" : {
+ "files" : ["adodb.inc.php"]
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/contrib/toxmlrpc.inc.php b/vendor/adodb/adodb-php/contrib/toxmlrpc.inc.php
new file mode 100644
index 0000000..f769cc5
--- /dev/null
+++ b/vendor/adodb/adodb-php/contrib/toxmlrpc.inc.php
@@ -0,0 +1,181 @@
+<?php
+ /**
+ * Helper functions to convert between ADODB recordset objects and XMLRPC values.
+ * Uses John Lim's AdoDB and Edd Dumbill's phpxmlrpc libs
+ *
+ * @author Daniele Baroncelli
+ * @author Gaetano Giunta
+ * @copyright (c) 2003-2004 Giunta/Baroncelli. All rights reserved.
+ *
+ * @todo some more error checking here and there
+ * @todo document the xmlrpc-struct used to encode recordset info
+ * @todo verify if using xmlrpc_encode($rs->GetArray()) would work with:
+ * - ADODB_FETCH_BOTH
+ * - null values
+ */
+
+ /**
+ * Include the main libraries
+ */
+ require_once('xmlrpc.inc');
+ if (!defined('ADODB_DIR')) require_once('adodb.inc.php');
+
+ /**
+ * Builds an xmlrpc struct value out of an AdoDB recordset
+ */
+ function rs2xmlrpcval(&$adodbrs) {
+
+ $header = rs2xmlrpcval_header($adodbrs);
+ $body = rs2xmlrpcval_body($adodbrs);
+
+ // put it all together and build final xmlrpc struct
+ $xmlrpcrs = new xmlrpcval ( array(
+ "header" => $header,
+ "body" => $body,
+ ), "struct");
+
+ return $xmlrpcrs;
+
+ }
+
+ /**
+ * Builds an xmlrpc struct value describing an AdoDB recordset
+ */
+ function rs2xmlrpcval_header($adodbrs)
+ {
+ $numfields = $adodbrs->FieldCount();
+ $numrecords = $adodbrs->RecordCount();
+
+ // build structure holding recordset information
+ $fieldstruct = array();
+ for ($i = 0; $i < $numfields; $i++) {
+ $fld = $adodbrs->FetchField($i);
+ $fieldarray = array();
+ if (isset($fld->name))
+ $fieldarray["name"] = new xmlrpcval ($fld->name);
+ if (isset($fld->type))
+ $fieldarray["type"] = new xmlrpcval ($fld->type);
+ if (isset($fld->max_length))
+ $fieldarray["max_length"] = new xmlrpcval ($fld->max_length, "int");
+ if (isset($fld->not_null))
+ $fieldarray["not_null"] = new xmlrpcval ($fld->not_null, "boolean");
+ if (isset($fld->has_default))
+ $fieldarray["has_default"] = new xmlrpcval ($fld->has_default, "boolean");
+ if (isset($fld->default_value))
+ $fieldarray["default_value"] = new xmlrpcval ($fld->default_value);
+ $fieldstruct[$i] = new xmlrpcval ($fieldarray, "struct");
+ }
+ $fieldcount = new xmlrpcval ($numfields, "int");
+ $recordcount = new xmlrpcval ($numrecords, "int");
+ $sql = new xmlrpcval ($adodbrs->sql);
+ $fieldinfo = new xmlrpcval ($fieldstruct, "array");
+
+ $header = new xmlrpcval ( array(
+ "fieldcount" => $fieldcount,
+ "recordcount" => $recordcount,
+ "sql" => $sql,
+ "fieldinfo" => $fieldinfo
+ ), "struct");
+
+ return $header;
+ }
+
+ /**
+ * Builds an xmlrpc struct value out of an AdoDB recordset
+ * (data values only, no data definition)
+ */
+ function rs2xmlrpcval_body($adodbrs)
+ {
+ $numfields = $adodbrs->FieldCount();
+
+ // build structure containing recordset data
+ $adodbrs->MoveFirst();
+ $rows = array();
+ while (!$adodbrs->EOF) {
+ $columns = array();
+ // This should work on all cases of fetch mode: assoc, num, both or default
+ if ($adodbrs->fetchMode == 'ADODB_FETCH_BOTH' || count($adodbrs->fields) == 2 * $adodbrs->FieldCount())
+ for ($i = 0; $i < $numfields; $i++)
+ if ($adodbrs->fields[$i] === null)
+ $columns[$i] = new xmlrpcval ('');
+ else
+ $columns[$i] = xmlrpc_encode ($adodbrs->fields[$i]);
+ else
+ foreach ($adodbrs->fields as $val)
+ if ($val === null)
+ $columns[] = new xmlrpcval ('');
+ else
+ $columns[] = xmlrpc_encode ($val);
+
+ $rows[] = new xmlrpcval ($columns, "array");
+
+ $adodbrs->MoveNext();
+ }
+ $body = new xmlrpcval ($rows, "array");
+
+ return $body;
+ }
+
+ /**
+ * Returns an xmlrpc struct value as string out of an AdoDB recordset
+ */
+ function rs2xmlrpcstring (&$adodbrs) {
+ $xmlrpc = rs2xmlrpcval ($adodbrs);
+ if ($xmlrpc)
+ return $xmlrpc->serialize();
+ else
+ return null;
+ }
+
+ /**
+ * Given a well-formed xmlrpc struct object returns an AdoDB object
+ *
+ * @todo add some error checking on the input value
+ */
+ function xmlrpcval2rs (&$xmlrpcval) {
+
+ $fields_array = array();
+ $data_array = array();
+
+ // rebuild column information
+ $header = $xmlrpcval->structmem('header');
+
+ $numfields = $header->structmem('fieldcount');
+ $numfields = $numfields->scalarval();
+ $numrecords = $header->structmem('recordcount');
+ $numrecords = $numrecords->scalarval();
+ $sqlstring = $header->structmem('sql');
+ $sqlstring = $sqlstring->scalarval();
+
+ $fieldinfo = $header->structmem('fieldinfo');
+ for ($i = 0; $i < $numfields; $i++) {
+ $temp = $fieldinfo->arraymem($i);
+ $fld = new ADOFieldObject();
+ while (list($key,$value) = $temp->structeach()) {
+ if ($key == "name") $fld->name = $value->scalarval();
+ if ($key == "type") $fld->type = $value->scalarval();
+ if ($key == "max_length") $fld->max_length = $value->scalarval();
+ if ($key == "not_null") $fld->not_null = $value->scalarval();
+ if ($key == "has_default") $fld->has_default = $value->scalarval();
+ if ($key == "default_value") $fld->default_value = $value->scalarval();
+ } // while
+ $fields_array[] = $fld;
+ } // for
+
+ // fetch recordset information into php array
+ $body = $xmlrpcval->structmem('body');
+ for ($i = 0; $i < $numrecords; $i++) {
+ $data_array[$i]= array();
+ $xmlrpcrs_row = $body->arraymem($i);
+ for ($j = 0; $j < $numfields; $j++) {
+ $temp = $xmlrpcrs_row->arraymem($j);
+ $data_array[$i][$j] = $temp->scalarval();
+ } // for j
+ } // for i
+
+ // finally build in-memory recordset object and return it
+ $rs = new ADORecordSet_array();
+ $rs->InitArrayFields($data_array,$fields_array);
+ return $rs;
+
+ }
diff --git a/vendor/adodb/adodb-php/cute_icons_for_site/adodb.gif b/vendor/adodb/adodb-php/cute_icons_for_site/adodb.gif
new file mode 100644
index 0000000..c5e8dfc
--- /dev/null
+++ b/vendor/adodb/adodb-php/cute_icons_for_site/adodb.gif
Binary files differ
diff --git a/vendor/adodb/adodb-php/cute_icons_for_site/adodb2.gif b/vendor/adodb/adodb-php/cute_icons_for_site/adodb2.gif
new file mode 100644
index 0000000..f12ae20
--- /dev/null
+++ b/vendor/adodb/adodb-php/cute_icons_for_site/adodb2.gif
Binary files differ
diff --git a/vendor/adodb/adodb-php/datadict/datadict-access.inc.php b/vendor/adodb/adodb-php/datadict/datadict-access.inc.php
new file mode 100644
index 0000000..490ded0
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-access.inc.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_access extends ADODB_DataDict {
+
+ var $databaseType = 'access';
+ var $seqField = false;
+
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'TEXT';
+ case 'XL':
+ case 'X': return 'MEMO';
+
+ case 'C2': return 'TEXT'; // up to 32K
+ case 'X2': return 'MEMO';
+
+ case 'B': return 'BINARY';
+
+ case 'TS':
+ case 'D': return 'DATETIME';
+ case 'T': return 'DATETIME';
+
+ case 'L': return 'BYTE';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'BYTE';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'INTEGER';
+
+ case 'F': return 'DOUBLE';
+ case 'N': return 'NUMERIC';
+ default:
+ return $meta;
+ }
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname, &$ftype, $fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ if ($fautoinc) {
+ $ftype = 'COUNTER';
+ return '';
+ }
+ if (substr($ftype,0,7) == 'DECIMAL') $ftype = 'DECIMAL';
+ $suffix = '';
+ if (strlen($fdefault)) {
+ //$suffix .= " DEFAULT $fdefault";
+ if ($this->debug) ADOConnection::outp("Warning: Access does not supported DEFAULT values (field $fname)");
+ }
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ function CreateDatabase($dbname,$options=false)
+ {
+ return array();
+ }
+
+
+ function SetSchema($schema)
+ {
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported");
+ return array();
+ }
+
+
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported");
+ return array();
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-db2.inc.php b/vendor/adodb/adodb-php/datadict/datadict-db2.inc.php
new file mode 100644
index 0000000..a9aa34d
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-db2.inc.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_db2 extends ADODB_DataDict {
+
+ var $databaseType = 'db2';
+ var $seqField = false;
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL': return 'CLOB';
+ case 'X': return 'VARCHAR(3600)';
+
+ case 'C2': return 'VARCHAR'; // up to 32K
+ case 'X2': return 'VARCHAR(3600)'; // up to 32000, but default page size too small
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'DOUBLE';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if ($fautoinc) return ' GENERATED ALWAYS AS IDENTITY'; # as identity start with
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported");
+ return array();
+ }
+
+
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported");
+ return array();
+ }
+
+
+ function ChangeTableSQL($tablename, $flds, $tableoptions = false)
+ {
+
+ /**
+ Allow basic table changes to DB2 databases
+ DB2 will fatally reject changes to non character columns
+
+ */
+
+ $validTypes = array("CHAR","VARC");
+ $invalidTypes = array("BIGI","BLOB","CLOB","DATE", "DECI","DOUB", "INTE", "REAL","SMAL", "TIME");
+ // check table exists
+ $cols = $this->MetaColumns($tablename);
+ if ( empty($cols)) {
+ return $this->CreateTableSQL($tablename, $flds, $tableoptions);
+ }
+
+ // already exists, alter table instead
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $alter = 'ALTER TABLE ' . $this->TableName($tablename);
+ $sql = array();
+
+ foreach ( $lines as $id => $v ) {
+ if ( isset($cols[$id]) && is_object($cols[$id]) ) {
+ /**
+ If the first field of $v is the fieldname, and
+ the second is the field type/size, we assume its an
+ attempt to modify the column size, so check that it is allowed
+ $v can have an indeterminate number of blanks between the
+ fields, so account for that too
+ */
+ $vargs = explode(' ' , $v);
+ // assume that $vargs[0] is the field name.
+ $i=0;
+ // Find the next non-blank value;
+ for ($i=1;$i<sizeof($vargs);$i++)
+ if ($vargs[$i] != '')
+ break;
+
+ // if $vargs[$i] is one of the following, we are trying to change the
+ // size of the field, if not allowed, simply ignore the request.
+ if (in_array(substr($vargs[$i],0,4),$invalidTypes))
+ continue;
+ // insert the appropriate DB2 syntax
+ if (in_array(substr($vargs[$i],0,4),$validTypes)) {
+ array_splice($vargs,$i,0,array('SET','DATA','TYPE'));
+ }
+
+ // Now Look for the NOT NULL statement as this is not allowed in
+ // the ALTER table statement. If it is in there, remove it
+ if (in_array('NOT',$vargs) && in_array('NULL',$vargs)) {
+ for ($i=1;$i<sizeof($vargs);$i++)
+ if ($vargs[$i] == 'NOT')
+ break;
+ array_splice($vargs,$i,2,'');
+ }
+ $v = implode(' ',$vargs);
+ $sql[] = $alter . $this->alterCol . ' ' . $v;
+ } else {
+ $sql[] = $alter . $this->addCol . ' ' . $v;
+ }
+ }
+
+ return $sql;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-firebird.inc.php b/vendor/adodb/adodb-php/datadict/datadict-firebird.inc.php
new file mode 100644
index 0000000..cffcca1
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-firebird.inc.php
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+class ADODB2_firebird extends ADODB_DataDict {
+
+ var $databaseType = 'firebird';
+ var $seqField = false;
+ var $seqPrefix = 'gen_';
+ var $blobSize = 40000;
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL': return 'VARCHAR(32000)';
+ case 'X': return 'VARCHAR(4000)';
+
+ case 'C2': return 'VARCHAR'; // up to 32K
+ case 'X2': return 'VARCHAR(4000)';
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'INTEGER';
+
+ case 'F': return 'DOUBLE PRECISION';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+ function NameQuote($name = NULL)
+ {
+ if (!is_string($name)) {
+ return FALSE;
+ }
+
+ $name = trim($name);
+
+ if ( !is_object($this->connection) ) {
+ return $name;
+ }
+
+ $quote = $this->connection->nameQuote;
+
+ // if name is of the form `name`, quote it
+ if ( preg_match('/^`(.+)`$/', $name, $matches) ) {
+ return $quote . $matches[1] . $quote;
+ }
+
+ // if name contains special characters, quote it
+ if ( !preg_match('/^[' . $this->nameRegex . ']+$/', $name) ) {
+ return $quote . $name . $quote;
+ }
+
+ return $quote . $name . $quote;
+ }
+
+ function CreateDatabase($dbname, $options=false)
+ {
+ $options = $this->_Options($options);
+ $sql = array();
+
+ $sql[] = "DECLARE EXTERNAL FUNCTION LOWER CSTRING(80) RETURNS CSTRING(80) FREE_IT ENTRY_POINT 'IB_UDF_lower' MODULE_NAME 'ib_udf'";
+
+ return $sql;
+ }
+
+ function _DropAutoIncrement($t)
+ {
+ if (strpos($t,'.') !== false) {
+ $tarr = explode('.',$t);
+ return 'DROP GENERATOR '.$tarr[0].'."gen_'.$tarr[1].'"';
+ }
+ return 'DROP GENERATOR "GEN_'.$t;
+ }
+
+
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fautoinc) $this->seqField = $fname;
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+
+ return $suffix;
+ }
+
+/*
+CREATE or replace TRIGGER jaddress_insert
+before insert on jaddress
+for each row
+begin
+IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN
+ NEW."seqField" = GEN_ID("GEN_tabname", 1);
+end;
+*/
+ function _Triggers($tabname,$tableoptions)
+ {
+ if (!$this->seqField) return array();
+
+ $tab1 = preg_replace( '/"/', '', $tabname );
+ if ($this->schema) {
+ $t = strpos($tab1,'.');
+ if ($t !== false) $tab = substr($tab1,$t+1);
+ else $tab = $tab1;
+ $seqField = $this->seqField;
+ $seqname = $this->schema.'.'.$this->seqPrefix.$tab;
+ $trigname = $this->schema.'.trig_'.$this->seqPrefix.$tab;
+ } else {
+ $seqField = $this->seqField;
+ $seqname = $this->seqPrefix.$tab1;
+ $trigname = 'trig_'.$seqname;
+ }
+ if (isset($tableoptions['REPLACE']))
+ { $sql[] = "DROP GENERATOR \"$seqname\"";
+ $sql[] = "CREATE GENERATOR \"$seqname\"";
+ $sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END";
+ }
+ else
+ { $sql[] = "CREATE GENERATOR \"$seqname\"";
+ $sql[] = "CREATE TRIGGER \"$trigname\" FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END";
+ }
+
+ $this->seqField = false;
+ return $sql;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-generic.inc.php b/vendor/adodb/adodb-php/datadict/datadict-generic.inc.php
new file mode 100644
index 0000000..183f959
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-generic.inc.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_generic extends ADODB_DataDict {
+
+ var $databaseType = 'generic';
+ var $seqField = false;
+
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL':
+ case 'X': return 'VARCHAR(250)';
+
+ case 'C2': return 'VARCHAR';
+ case 'X2': return 'VARCHAR(250)';
+
+ case 'B': return 'VARCHAR';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'DATE';
+
+ case 'L': return 'DECIMAL(1)';
+ case 'I': return 'DECIMAL(10)';
+ case 'I1': return 'DECIMAL(3)';
+ case 'I2': return 'DECIMAL(5)';
+ case 'I4': return 'DECIMAL(10)';
+ case 'I8': return 'DECIMAL(20)';
+
+ case 'F': return 'DECIMAL(32,8)';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported");
+ return array();
+ }
+
+
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported");
+ return array();
+ }
+
+}
+
+/*
+//db2
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'X': return 'VARCHAR';
+
+ case 'C2': return 'VARCHAR'; // up to 32K
+ case 'X2': return 'VARCHAR';
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'DOUBLE';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+// ifx
+function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';// 255
+ case 'X': return 'TEXT';
+
+ case 'C2': return 'NVARCHAR';
+ case 'X2': return 'TEXT';
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'T': return 'DATETIME';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'DECIMAL(20)';
+
+ case 'F': return 'FLOAT';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+*/
diff --git a/vendor/adodb/adodb-php/datadict/datadict-ibase.inc.php b/vendor/adodb/adodb-php/datadict/datadict-ibase.inc.php
new file mode 100644
index 0000000..257581e
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-ibase.inc.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_ibase extends ADODB_DataDict {
+
+ var $databaseType = 'ibase';
+ var $seqField = false;
+
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL':
+ case 'X': return 'VARCHAR(4000)';
+
+ case 'C2': return 'VARCHAR'; // up to 32K
+ case 'X2': return 'VARCHAR(4000)';
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'INTEGER';
+
+ case 'F': return 'DOUBLE PRECISION';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported");
+ return array();
+ }
+
+
+ function DropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported");
+ return array();
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-informix.inc.php b/vendor/adodb/adodb-php/datadict/datadict-informix.inc.php
new file mode 100644
index 0000000..df03deb
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-informix.inc.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_informix extends ADODB_DataDict {
+
+ var $databaseType = 'informix';
+ var $seqField = false;
+
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';// 255
+ case 'XL':
+ case 'X': return 'TEXT';
+
+ case 'C2': return 'NVARCHAR';
+ case 'X2': return 'TEXT';
+
+ case 'B': return 'BLOB';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'DATETIME YEAR TO SECOND';
+
+ case 'L': return 'SMALLINT';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'DECIMAL(20)';
+
+ case 'F': return 'FLOAT';
+ case 'N': return 'DECIMAL';
+ default:
+ return $meta;
+ }
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported");
+ return array();
+ }
+
+
+ function DropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported");
+ return array();
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname, &$ftype, $fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ if ($fautoinc) {
+ $ftype = 'SERIAL';
+ return '';
+ }
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-mssql.inc.php b/vendor/adodb/adodb-php/datadict/datadict-mssql.inc.php
new file mode 100644
index 0000000..0b6a051
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-mssql.inc.php
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+/*
+In ADOdb, named quotes for MS SQL Server use ". From the MSSQL Docs:
+
+ Note Delimiters are for identifiers only. Delimiters cannot be used for keywords,
+ whether or not they are marked as reserved in SQL Server.
+
+ Quoted identifiers are delimited by double quotation marks ("):
+ SELECT * FROM "Blanks in Table Name"
+
+ Bracketed identifiers are delimited by brackets ([ ]):
+ SELECT * FROM [Blanks In Table Name]
+
+ Quoted identifiers are valid only when the QUOTED_IDENTIFIER option is set to ON. By default,
+ the Microsoft OLE DB Provider for SQL Server and SQL Server ODBC driver set QUOTED_IDENTIFIER ON
+ when they connect.
+
+ In Transact-SQL, the option can be set at various levels using SET QUOTED_IDENTIFIER,
+ the quoted identifier option of sp_dboption, or the user options option of sp_configure.
+
+ When SET ANSI_DEFAULTS is ON, SET QUOTED_IDENTIFIER is enabled.
+
+ Syntax
+
+ SET QUOTED_IDENTIFIER { ON | OFF }
+
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_mssql extends ADODB_DataDict {
+ var $databaseType = 'mssql';
+ var $dropIndex = 'DROP INDEX %2$s.%1$s';
+ var $renameTable = "EXEC sp_rename '%s','%s'";
+ var $renameColumn = "EXEC sp_rename '%s.%s','%s'";
+
+ var $typeX = 'TEXT'; ## Alternatively, set it to VARCHAR(4000)
+ var $typeXL = 'TEXT';
+
+ //var $alterCol = ' ALTER COLUMN ';
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'R':
+ case 'INT':
+ case 'INTEGER': return 'I';
+ case 'BIT':
+ case 'TINYINT': return 'I1';
+ case 'SMALLINT': return 'I2';
+ case 'BIGINT': return 'I8';
+ case 'SMALLDATETIME': return 'T';
+ case 'REAL':
+ case 'FLOAT': return 'F';
+ default: return parent::MetaType($t,$len,$fieldobj);
+ }
+ }
+
+ function ActualType($meta)
+ {
+ switch(strtoupper($meta)) {
+
+ case 'C': return 'VARCHAR';
+ case 'XL': return (isset($this)) ? $this->typeXL : 'TEXT';
+ case 'X': return (isset($this)) ? $this->typeX : 'TEXT'; ## could be varchar(8000), but we want compat with oracle
+ case 'C2': return 'NVARCHAR';
+ case 'X2': return 'NTEXT';
+
+ case 'B': return 'IMAGE';
+
+ case 'D': return 'DATETIME';
+
+ case 'TS':
+ case 'T': return 'DATETIME';
+ case 'L': return 'BIT';
+
+ case 'R':
+ case 'I': return 'INT';
+ case 'I1': return 'TINYINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INT';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'REAL';
+ case 'N': return 'NUMERIC';
+ default:
+ return $meta;
+ }
+ }
+
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $f = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $s = "ALTER TABLE $tabname $this->addCol";
+ foreach($lines as $v) {
+ $f[] = "\n $v";
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ /*
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ foreach($lines as $v) {
+ $sql[] = "ALTER TABLE $tabname $this->alterCol $v";
+ }
+
+ return $sql;
+ }
+ */
+
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ if (!is_array($flds))
+ $flds = explode(',',$flds);
+ $f = array();
+ $s = 'ALTER TABLE ' . $tabname;
+ foreach($flds as $v) {
+ $f[] = "\n$this->dropCol ".$this->NameQuote($v);
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fautoinc) $suffix .= ' IDENTITY(1,1)';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ else if ($suffix == '') $suffix .= ' NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ /*
+CREATE TABLE
+ [ database_name.[ owner ] . | owner. ] table_name
+ ( { < column_definition >
+ | column_name AS computed_column_expression
+ | < table_constraint > ::= [ CONSTRAINT constraint_name ] }
+
+ | [ { PRIMARY KEY | UNIQUE } [ ,...n ]
+ )
+
+[ ON { filegroup | DEFAULT } ]
+[ TEXTIMAGE_ON { filegroup | DEFAULT } ]
+
+< column_definition > ::= { column_name data_type }
+ [ COLLATE < collation_name > ]
+ [ [ DEFAULT constant_expression ]
+ | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ]
+ ]
+ [ ROWGUIDCOL]
+ [ < column_constraint > ] [ ...n ]
+
+< column_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ NULL | NOT NULL ]
+ | [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ [ WITH FILLFACTOR = fillfactor ]
+ [ON {filegroup | DEFAULT} ] ]
+ ]
+ | [ [ FOREIGN KEY ]
+ REFERENCES ref_table [ ( ref_column ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( logical_expression )
+ }
+
+< table_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ { ( column [ ASC | DESC ] [ ,...n ] ) }
+ [ WITH FILLFACTOR = fillfactor ]
+ [ ON { filegroup | DEFAULT } ]
+ ]
+ | FOREIGN KEY
+ [ ( column [ ,...n ] ) ]
+ REFERENCES ref_table [ ( ref_column [ ,...n ] ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( search_conditions )
+ }
+
+
+ */
+
+ /*
+ CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name
+ ON { table | view } ( column [ ASC | DESC ] [ ,...n ] )
+ [ WITH < index_option > [ ,...n] ]
+ [ ON filegroup ]
+ < index_option > :: =
+ { PAD_INDEX |
+ FILLFACTOR = fillfactor |
+ IGNORE_DUP_KEY |
+ DROP_EXISTING |
+ STATISTICS_NORECOMPUTE |
+ SORT_IN_TEMPDB
+ }
+*/
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname, $tabname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
+ $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : '';
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+
+ function _GetSize($ftype, $ty, $fsize, $fprec)
+ {
+ switch ($ftype) {
+ case 'INT':
+ case 'SMALLINT':
+ case 'TINYINT':
+ case 'BIGINT':
+ return $ftype;
+ }
+ if ($ty == 'T') return $ftype;
+ return parent::_GetSize($ftype, $ty, $fsize, $fprec);
+
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-mssqlnative.inc.php b/vendor/adodb/adodb-php/datadict/datadict-mssqlnative.inc.php
new file mode 100644
index 0000000..51f7d2a
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-mssqlnative.inc.php
@@ -0,0 +1,369 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+/*
+In ADOdb, named quotes for MS SQL Server use ". From the MSSQL Docs:
+
+ Note Delimiters are for identifiers only. Delimiters cannot be used for keywords,
+ whether or not they are marked as reserved in SQL Server.
+
+ Quoted identifiers are delimited by double quotation marks ("):
+ SELECT * FROM "Blanks in Table Name"
+
+ Bracketed identifiers are delimited by brackets ([ ]):
+ SELECT * FROM [Blanks In Table Name]
+
+ Quoted identifiers are valid only when the QUOTED_IDENTIFIER option is set to ON. By default,
+ the Microsoft OLE DB Provider for SQL Server and SQL Server ODBC driver set QUOTED_IDENTIFIER ON
+ when they connect.
+
+ In Transact-SQL, the option can be set at various levels using SET QUOTED_IDENTIFIER,
+ the quoted identifier option of sp_dboption, or the user options option of sp_configure.
+
+ When SET ANSI_DEFAULTS is ON, SET QUOTED_IDENTIFIER is enabled.
+
+ Syntax
+
+ SET QUOTED_IDENTIFIER { ON | OFF }
+
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_mssqlnative extends ADODB_DataDict {
+ var $databaseType = 'mssqlnative';
+ var $dropIndex = 'DROP INDEX %1$s ON %2$s';
+ var $renameTable = "EXEC sp_rename '%s','%s'";
+ var $renameColumn = "EXEC sp_rename '%s.%s','%s'";
+ var $typeX = 'TEXT'; ## Alternatively, set it to VARCHAR(4000)
+ var $typeXL = 'TEXT';
+
+ //var $alterCol = ' ALTER COLUMN ';
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ $_typeConversion = array(
+ -155 => 'D',
+ 93 => 'D',
+ -154 => 'D',
+ -2 => 'D',
+ 91 => 'D',
+
+ 12 => 'C',
+ 1 => 'C',
+ -9 => 'C',
+ -8 => 'C',
+
+ -7 => 'L',
+ -6 => 'I2',
+ -5 => 'I8',
+ -11 => 'I',
+ 4 => 'I',
+ 5 => 'I4',
+
+ -1 => 'X',
+ -10 => 'X',
+
+ 2 => 'N',
+ 3 => 'N',
+ 6 => 'N',
+ 7 => 'N',
+
+ -152 => 'X',
+ -151 => 'X',
+ -4 => 'X',
+ -3 => 'X'
+ );
+
+ return $_typeConversion($t);
+
+ }
+
+ function ActualType($meta)
+ {
+ $DATE_TYPE = 'DATETIME';
+
+ switch(strtoupper($meta)) {
+
+ case 'C': return 'VARCHAR';
+ case 'XL': return (isset($this)) ? $this->typeXL : 'TEXT';
+ case 'X': return (isset($this)) ? $this->typeX : 'TEXT'; ## could be varchar(8000), but we want compat with oracle
+ case 'C2': return 'NVARCHAR';
+ case 'X2': return 'NTEXT';
+
+ case 'B': return 'IMAGE';
+
+ case 'D': return $DATE_TYPE;
+ case 'T': return 'TIME';
+ case 'L': return 'BIT';
+
+ case 'R':
+ case 'I': return 'INT';
+ case 'I1': return 'TINYINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INT';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'REAL';
+ case 'N': return 'NUMERIC';
+ default:
+ print "RETURN $meta";
+ return $meta;
+ }
+ }
+
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $f = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $s = "ALTER TABLE $tabname $this->addCol";
+ foreach($lines as $v) {
+ $f[] = "\n $v";
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ function DefaultConstraintname($tabname, $colname)
+ {
+ $constraintname = false;
+ $rs = $this->connection->Execute(
+ "SELECT name FROM sys.default_constraints
+ WHERE object_name(parent_object_id) = '$tabname'
+ AND col_name(parent_object_id, parent_column_id) = '$colname'"
+ );
+ if ( is_object($rs) ) {
+ $row = $rs->FetchRow();
+ $constraintname = $row['name'];
+ }
+ return $constraintname;
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+
+ list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+ $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
+ foreach($lines as $v) {
+ $not_null = false;
+ if ($not_null = preg_match('/NOT NULL/i',$v)) {
+ $v = preg_replace('/NOT NULL/i','',$v);
+ }
+ if (preg_match('/^([^ ]+) .*DEFAULT (\'[^\']+\'|\"[^\"]+\"|[^ ]+)/',$v,$matches)) {
+ list(,$colname,$default) = $matches;
+ $v = preg_replace('/^' . preg_quote($colname) . '\s/', '', $v);
+ $t = trim(str_replace('DEFAULT '.$default,'',$v));
+ if ( $constraintname = $this->DefaultConstraintname($tabname,$colname) ) {
+ $sql[] = 'ALTER TABLE '.$tabname.' DROP CONSTRAINT '. $constraintname;
+ }
+ if ($not_null) {
+ $sql[] = $alter . $colname . ' ' . $t . ' NOT NULL';
+ } else {
+ $sql[] = $alter . $colname . ' ' . $t ;
+ }
+ $sql[] = 'ALTER TABLE ' . $tabname
+ . ' ADD CONSTRAINT DF__' . $tabname . '__' . $colname . '__' . dechex(rand())
+ . ' DEFAULT ' . $default . ' FOR ' . $colname;
+ } else {
+ $colname = strtok($v," ");
+ if ( $constraintname = $this->DefaultConstraintname($tabname,$colname) ) {
+ $sql[] = 'ALTER TABLE '.$tabname.' DROP CONSTRAINT '. $constraintname;
+ }
+ if ($not_null) {
+ $sql[] = $alter . $v . ' NOT NULL';
+ } else {
+ $sql[] = $alter . $v;
+ }
+ }
+ }
+ if (is_array($idxs)) {
+ foreach($idxs as $idx => $idxdef) {
+ $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
+ $sql = array_merge($sql, $sql_idxs);
+ }
+ }
+ return $sql;
+ }
+
+
+ /**
+ * Drop a column, syntax is ALTER TABLE table DROP COLUMN column,column
+ *
+ * @param string $tabname Table Name
+ * @param string[] $flds One, or an array of Fields To Drop
+ * @param string $tableflds Throwaway value to make the function match the parent
+ * @param string $tableoptions Throway value to make the function match the parent
+ *
+ * @return string The SQL necessary to drop the column
+ */
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ if (!is_array($flds))
+ $flds = explode(',',$flds);
+ $f = array();
+ $s = 'ALTER TABLE ' . $tabname;
+ foreach($flds as $v) {
+ if ( $constraintname = $this->DefaultConstraintname($tabname,$v) ) {
+ $sql[] = 'ALTER TABLE ' . $tabname . ' DROP CONSTRAINT ' . $constraintname;
+ }
+ $f[] = ' DROP COLUMN ' . $this->NameQuote($v);
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fautoinc) $suffix .= ' IDENTITY(1,1)';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ else if ($suffix == '') $suffix .= ' NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ /*
+CREATE TABLE
+ [ database_name.[ owner ] . | owner. ] table_name
+ ( { < column_definition >
+ | column_name AS computed_column_expression
+ | < table_constraint > ::= [ CONSTRAINT constraint_name ] }
+
+ | [ { PRIMARY KEY | UNIQUE } [ ,...n ]
+ )
+
+[ ON { filegroup | DEFAULT } ]
+[ TEXTIMAGE_ON { filegroup | DEFAULT } ]
+
+< column_definition > ::= { column_name data_type }
+ [ COLLATE < collation_name > ]
+ [ [ DEFAULT constant_expression ]
+ | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ]
+ ]
+ [ ROWGUIDCOL]
+ [ < column_constraint > ] [ ...n ]
+
+< column_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ NULL | NOT NULL ]
+ | [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ [ WITH FILLFACTOR = fillfactor ]
+ [ON {filegroup | DEFAULT} ] ]
+ ]
+ | [ [ FOREIGN KEY ]
+ REFERENCES ref_table [ ( ref_column ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( logical_expression )
+ }
+
+< table_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ { ( column [ ASC | DESC ] [ ,...n ] ) }
+ [ WITH FILLFACTOR = fillfactor ]
+ [ ON { filegroup | DEFAULT } ]
+ ]
+ | FOREIGN KEY
+ [ ( column [ ,...n ] ) ]
+ REFERENCES ref_table [ ( ref_column [ ,...n ] ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( search_conditions )
+ }
+
+
+ */
+
+ /*
+ CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name
+ ON { table | view } ( column [ ASC | DESC ] [ ,...n ] )
+ [ WITH < index_option > [ ,...n] ]
+ [ ON filegroup ]
+ < index_option > :: =
+ { PAD_INDEX |
+ FILLFACTOR = fillfactor |
+ IGNORE_DUP_KEY |
+ DROP_EXISTING |
+ STATISTICS_NORECOMPUTE |
+ SORT_IN_TEMPDB
+ }
+*/
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname, $tabname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
+ $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : '';
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+
+ function _GetSize($ftype, $ty, $fsize, $fprec)
+ {
+ switch ($ftype) {
+ case 'INT':
+ case 'SMALLINT':
+ case 'TINYINT':
+ case 'BIGINT':
+ return $ftype;
+ }
+ if ($ty == 'T') return $ftype;
+ return parent::_GetSize($ftype, $ty, $fsize, $fprec);
+
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-mysql.inc.php b/vendor/adodb/adodb-php/datadict/datadict-mysql.inc.php
new file mode 100644
index 0000000..defb124
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-mysql.inc.php
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_mysql extends ADODB_DataDict {
+ var $databaseType = 'mysql';
+ var $alterCol = ' MODIFY COLUMN';
+ var $alterTableAddIndex = true;
+ var $dropTable = 'DROP TABLE IF EXISTS %s'; // requires mysql 3.22 or later
+
+ var $dropIndex = 'DROP INDEX %s ON %s';
+ var $renameColumn = 'ALTER TABLE %s CHANGE COLUMN %s %s %s'; // needs column-definition!
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ $is_serial = is_object($fieldobj) && $fieldobj->primary_key && $fieldobj->auto_increment;
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'STRING':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'TINYBLOB':
+ case 'TINYTEXT':
+ case 'ENUM':
+ case 'SET':
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ case 'LONGTEXT':
+ case 'MEDIUMTEXT':
+ return 'X';
+
+ // php_mysql extension always returns 'blob' even if 'text'
+ // so we have to check whether binary...
+ case 'IMAGE':
+ case 'LONGBLOB':
+ case 'BLOB':
+ case 'MEDIUMBLOB':
+ return !empty($fieldobj->binary) ? 'B' : 'X';
+
+ case 'YEAR':
+ case 'DATE': return 'D';
+
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP': return 'T';
+
+ case 'FLOAT':
+ case 'DOUBLE':
+ return 'F';
+
+ case 'INT':
+ case 'INTEGER': return $is_serial ? 'R' : 'I';
+ case 'TINYINT': return $is_serial ? 'R' : 'I1';
+ case 'SMALLINT': return $is_serial ? 'R' : 'I2';
+ case 'MEDIUMINT': return $is_serial ? 'R' : 'I4';
+ case 'BIGINT': return $is_serial ? 'R' : 'I8';
+ default: return 'N';
+ }
+ }
+
+ function ActualType($meta)
+ {
+ switch(strtoupper($meta)) {
+ case 'C': return 'VARCHAR';
+ case 'XL':return 'LONGTEXT';
+ case 'X': return 'TEXT';
+
+ case 'C2': return 'VARCHAR';
+ case 'X2': return 'LONGTEXT';
+
+ case 'B': return 'LONGBLOB';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'DATETIME';
+ case 'L': return 'TINYINT';
+
+ case 'R':
+ case 'I4':
+ case 'I': return 'INTEGER';
+ case 'I1': return 'TINYINT';
+ case 'I2': return 'SMALLINT';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'DOUBLE';
+ case 'N': return 'NUMERIC';
+ default:
+ return $meta;
+ }
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if ($funsigned) $suffix .= ' UNSIGNED';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fautoinc) $suffix .= ' AUTO_INCREMENT';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ /*
+ CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
+ [table_options] [select_statement]
+ create_definition:
+ col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
+ [PRIMARY KEY] [reference_definition]
+ or PRIMARY KEY (index_col_name,...)
+ or KEY [index_name] (index_col_name,...)
+ or INDEX [index_name] (index_col_name,...)
+ or UNIQUE [INDEX] [index_name] (index_col_name,...)
+ or FULLTEXT [INDEX] [index_name] (index_col_name,...)
+ or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
+ [reference_definition]
+ or CHECK (expr)
+ */
+
+ /*
+ CREATE [UNIQUE|FULLTEXT] INDEX index_name
+ ON tbl_name (col_name[(length)],... )
+ */
+
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ if ($this->alterTableAddIndex) $sql[] = "ALTER TABLE $tabname DROP INDEX $idxname";
+ else $sql[] = sprintf($this->dropIndex, $idxname, $tabname);
+
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ if (isset($idxoptions['FULLTEXT'])) {
+ $unique = ' FULLTEXT';
+ } elseif (isset($idxoptions['UNIQUE'])) {
+ $unique = ' UNIQUE';
+ } else {
+ $unique = '';
+ }
+
+ if ( is_array($flds) ) $flds = implode(', ',$flds);
+
+ if ($this->alterTableAddIndex) $s = "ALTER TABLE $tabname ADD $unique INDEX $idxname ";
+ else $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname;
+
+ $s .= ' (' . $flds . ')';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+ $sql[] = $s;
+
+ return $sql;
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-oci8.inc.php b/vendor/adodb/adodb-php/datadict/datadict-oci8.inc.php
new file mode 100644
index 0000000..bef8d61
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-oci8.inc.php
@@ -0,0 +1,300 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_oci8 extends ADODB_DataDict {
+
+ var $databaseType = 'oci8';
+ var $seqField = false;
+ var $seqPrefix = 'SEQ_';
+ var $dropTable = "DROP TABLE %s CASCADE CONSTRAINTS";
+ var $trigPrefix = 'TRIG_';
+ var $alterCol = ' MODIFY ';
+ var $typeX = 'VARCHAR(4000)';
+ var $typeXL = 'CLOB';
+
+ function MetaType($t, $len=-1, $fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ switch (strtoupper($t)) {
+ case 'VARCHAR':
+ case 'VARCHAR2':
+ case 'CHAR':
+ case 'VARBINARY':
+ case 'BINARY':
+ if (isset($this) && $len <= $this->blobSize) return 'C';
+ return 'X';
+
+ case 'NCHAR':
+ case 'NVARCHAR2':
+ case 'NVARCHAR':
+ if (isset($this) && $len <= $this->blobSize) return 'C2';
+ return 'X2';
+
+ case 'NCLOB':
+ case 'CLOB':
+ return 'XL';
+
+ case 'LONG RAW':
+ case 'LONG VARBINARY':
+ case 'BLOB':
+ return 'B';
+
+ case 'TIMESTAMP':
+ return 'TS';
+
+ case 'DATE':
+ return 'T';
+
+ case 'INT':
+ case 'SMALLINT':
+ case 'INTEGER':
+ return 'I';
+
+ default:
+ return 'N';
+ }
+ }
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'X': return $this->typeX;
+ case 'XL': return $this->typeXL;
+
+ case 'C2': return 'NVARCHAR2';
+ case 'X2': return 'NVARCHAR2(4000)';
+
+ case 'B': return 'BLOB';
+
+ case 'TS':
+ return 'TIMESTAMP';
+
+ case 'D':
+ case 'T': return 'DATE';
+ case 'L': return 'NUMBER(1)';
+ case 'I1': return 'NUMBER(3)';
+ case 'I2': return 'NUMBER(5)';
+ case 'I':
+ case 'I4': return 'NUMBER(10)';
+
+ case 'I8': return 'NUMBER(20)';
+ case 'F': return 'NUMBER';
+ case 'N': return 'NUMBER';
+ case 'R': return 'NUMBER(20)';
+ default:
+ return $meta;
+ }
+ }
+
+ function CreateDatabase($dbname, $options=false)
+ {
+ $options = $this->_Options($options);
+ $password = isset($options['PASSWORD']) ? $options['PASSWORD'] : 'tiger';
+ $tablespace = isset($options["TABLESPACE"]) ? " DEFAULT TABLESPACE ".$options["TABLESPACE"] : '';
+ $sql[] = "CREATE USER ".$dbname." IDENTIFIED BY ".$password.$tablespace;
+ $sql[] = "GRANT CREATE SESSION, CREATE TABLE,UNLIMITED TABLESPACE,CREATE SEQUENCE TO $dbname";
+
+ return $sql;
+ }
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName($tabname);
+ $f = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $s = "ALTER TABLE $tabname ADD (";
+ foreach($lines as $v) {
+ $f[] = "\n $v";
+ }
+
+ $s .= implode(', ',$f).')';
+ $sql[] = $s;
+ return $sql;
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ $tabname = $this->TableName($tabname);
+ $f = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $s = "ALTER TABLE $tabname MODIFY(";
+ foreach($lines as $v) {
+ $f[] = "\n $v";
+ }
+ $s .= implode(', ',$f).')';
+ $sql[] = $s;
+ return $sql;
+ }
+
+ function DropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if (!is_array($flds)) $flds = explode(',',$flds);
+ foreach ($flds as $k => $v) $flds[$k] = $this->NameQuote($v);
+
+ $sql = array();
+ $s = "ALTER TABLE $tabname DROP(";
+ $s .= implode(', ',$flds).') CASCADE CONSTRAINTS';
+ $sql[] = $s;
+ return $sql;
+ }
+
+ function _DropAutoIncrement($t)
+ {
+ if (strpos($t,'.') !== false) {
+ $tarr = explode('.',$t);
+ return "drop sequence ".$tarr[0].".seq_".$tarr[1];
+ }
+ return "drop sequence seq_".$t;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+
+ if ($fdefault == "''" && $fnotnull) {// this is null in oracle
+ $fnotnull = false;
+ if ($this->debug) ADOConnection::outp("NOT NULL and DEFAULT='' illegal in Oracle");
+ }
+
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+
+ if ($fautoinc) $this->seqField = $fname;
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+
+ return $suffix;
+ }
+
+/*
+CREATE or replace TRIGGER jaddress_insert
+before insert on jaddress
+for each row
+begin
+select seqaddress.nextval into :new.A_ID from dual;
+end;
+*/
+ function _Triggers($tabname,$tableoptions)
+ {
+ if (!$this->seqField) return array();
+
+ if ($this->schema) {
+ $t = strpos($tabname,'.');
+ if ($t !== false) $tab = substr($tabname,$t+1);
+ else $tab = $tabname;
+ $seqname = $this->schema.'.'.$this->seqPrefix.$tab;
+ $trigname = $this->schema.'.'.$this->trigPrefix.$this->seqPrefix.$tab;
+ } else {
+ $seqname = $this->seqPrefix.$tabname;
+ $trigname = $this->trigPrefix.$seqname;
+ }
+
+ if (strlen($seqname) > 30) {
+ $seqname = $this->seqPrefix.uniqid('');
+ } // end if
+ if (strlen($trigname) > 30) {
+ $trigname = $this->trigPrefix.uniqid('');
+ } // end if
+
+ if (isset($tableoptions['REPLACE'])) $sql[] = "DROP SEQUENCE $seqname";
+ $seqCache = '';
+ if (isset($tableoptions['SEQUENCE_CACHE'])){$seqCache = $tableoptions['SEQUENCE_CACHE'];}
+ $seqIncr = '';
+ if (isset($tableoptions['SEQUENCE_INCREMENT'])){$seqIncr = ' INCREMENT BY '.$tableoptions['SEQUENCE_INCREMENT'];}
+ $seqStart = '';
+ if (isset($tableoptions['SEQUENCE_START'])){$seqIncr = ' START WITH '.$tableoptions['SEQUENCE_START'];}
+ $sql[] = "CREATE SEQUENCE $seqname $seqStart $seqIncr $seqCache";
+ $sql[] = "CREATE OR REPLACE TRIGGER $trigname BEFORE insert ON $tabname FOR EACH ROW WHEN (NEW.$this->seqField IS NULL OR NEW.$this->seqField = 0) BEGIN select $seqname.nextval into :new.$this->seqField from dual; END;";
+
+ $this->seqField = false;
+ return $sql;
+ }
+
+ /*
+ CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
+ [table_options] [select_statement]
+ create_definition:
+ col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
+ [PRIMARY KEY] [reference_definition]
+ or PRIMARY KEY (index_col_name,...)
+ or KEY [index_name] (index_col_name,...)
+ or INDEX [index_name] (index_col_name,...)
+ or UNIQUE [INDEX] [index_name] (index_col_name,...)
+ or FULLTEXT [INDEX] [index_name] (index_col_name,...)
+ or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
+ [reference_definition]
+ or CHECK (expr)
+ */
+
+
+
+ function _IndexSQL($idxname, $tabname, $flds,$idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname, $tabname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ if (isset($idxoptions['BITMAP'])) {
+ $unique = ' BITMAP';
+ } elseif (isset($idxoptions['UNIQUE'])) {
+ $unique = ' UNIQUE';
+ } else {
+ $unique = '';
+ }
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+ if (isset($idxoptions['oci8']))
+ $s .= $idxoptions['oci8'];
+
+
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+ function GetCommentSQL($table,$col)
+ {
+ $table = $this->connection->qstr($table);
+ $col = $this->connection->qstr($col);
+ return "select comments from USER_COL_COMMENTS where TABLE_NAME=$table and COLUMN_NAME=$col";
+ }
+
+ function SetCommentSQL($table,$col,$cmt)
+ {
+ $cmt = $this->connection->qstr($cmt);
+ return "COMMENT ON COLUMN $table.$col IS $cmt";
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-postgres.inc.php b/vendor/adodb/adodb-php/datadict/datadict-postgres.inc.php
new file mode 100644
index 0000000..b1a9355
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-postgres.inc.php
@@ -0,0 +1,484 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_postgres extends ADODB_DataDict {
+
+ var $databaseType = 'postgres';
+ var $seqField = false;
+ var $seqPrefix = 'SEQ_';
+ var $addCol = ' ADD COLUMN';
+ var $quote = '"';
+ var $renameTable = 'ALTER TABLE %s RENAME TO %s'; // at least since 7.1
+ var $dropTable = 'DROP TABLE %s CASCADE';
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ $is_serial = is_object($fieldobj) && !empty($fieldobj->primary_key) && !empty($fieldobj->unique) &&
+ !empty($fieldobj->has_default) && substr($fieldobj->default_value,0,8) == 'nextval(';
+
+ switch (strtoupper($t)) {
+ case 'INTERVAL':
+ case 'CHAR':
+ case 'CHARACTER':
+ case 'VARCHAR':
+ case 'NAME':
+ case 'BPCHAR':
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ return 'X';
+
+ case 'IMAGE': // user defined type
+ case 'BLOB': // user defined type
+ case 'BIT': // This is a bit string, not a single bit, so don't return 'L'
+ case 'VARBIT':
+ case 'BYTEA':
+ return 'B';
+
+ case 'BOOL':
+ case 'BOOLEAN':
+ return 'L';
+
+ case 'DATE':
+ return 'D';
+
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+ case 'TIMESTAMPTZ':
+ return 'T';
+
+ case 'INTEGER': return !$is_serial ? 'I' : 'R';
+ case 'SMALLINT':
+ case 'INT2': return !$is_serial ? 'I2' : 'R';
+ case 'INT4': return !$is_serial ? 'I4' : 'R';
+ case 'BIGINT':
+ case 'INT8': return !$is_serial ? 'I8' : 'R';
+
+ case 'OID':
+ case 'SERIAL':
+ return 'R';
+
+ case 'FLOAT4':
+ case 'FLOAT8':
+ case 'DOUBLE PRECISION':
+ case 'REAL':
+ return 'F';
+
+ default:
+ return 'N';
+ }
+ }
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL':
+ case 'X': return 'TEXT';
+
+ case 'C2': return 'VARCHAR';
+ case 'X2': return 'TEXT';
+
+ case 'B': return 'BYTEA';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'BOOLEAN';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'SMALLINT';
+ case 'I2': return 'INT2';
+ case 'I4': return 'INT4';
+ case 'I8': return 'INT8';
+
+ case 'F': return 'FLOAT8';
+ case 'N': return 'NUMERIC';
+ default:
+ return $meta;
+ }
+ }
+
+ /**
+ * Adding a new Column
+ *
+ * reimplementation of the default function as postgres does NOT allow to set the default in the same statement
+ *
+ * @param string $tabname table-name
+ * @param string $flds column-names and types for the changed columns
+ * @return array with SQL strings
+ */
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ $not_null = false;
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' ';
+ foreach($lines as $v) {
+ if (($not_null = preg_match('/NOT NULL/i',$v))) {
+ $v = preg_replace('/NOT NULL/i','',$v);
+ }
+ if (preg_match('/^([^ ]+) .*DEFAULT (\'[^\']+\'|\"[^\"]+\"|[^ ]+)/',$v,$matches)) {
+ list(,$colname,$default) = $matches;
+ $sql[] = $alter . str_replace('DEFAULT '.$default,'',$v);
+ $sql[] = 'UPDATE '.$tabname.' SET '.$colname.'='.$default;
+ $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET DEFAULT ' . $default;
+ } else {
+ $sql[] = $alter . $v;
+ }
+ if ($not_null) {
+ list($colname) = explode(' ',$v);
+ $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET NOT NULL';
+ }
+ }
+ return $sql;
+ }
+
+
+ function DropIndexSQL ($idxname, $tabname = NULL)
+ {
+ return array(sprintf($this->dropIndex, $this->TableName($idxname), $this->TableName($tabname)));
+ }
+
+ /**
+ * Change the definition of one column
+ *
+ * Postgres can't do that on it's own, you need to supply the complete defintion of the new table,
+ * to allow, recreating the table and copying the content over to the new table
+ * @param string $tabname table-name
+ * @param string $flds column-name and type for the changed column
+ * @param string $tableflds complete defintion of the new table, eg. for postgres, default ''
+ * @param array/ $tableoptions options for the new table see CreateTableSQL, default ''
+ * @return array with SQL strings
+ */
+ /*
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ if (!$tableflds) {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL needs a complete table-definiton for PostgreSQL");
+ return array();
+ }
+ return $this->_recreate_copy_table($tabname,False,$tableflds,$tableoptions);
+ }*/
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ // Check if alter single column datatype available - works with 8.0+
+ $has_alter_column = 8.0 <= (float) @$this->serverInfo['version'];
+
+ if ($has_alter_column) {
+ $tabname = $this->TableName($tabname);
+ $sql = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $set_null = false;
+ foreach($lines as $v) {
+ $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
+ if ($not_null = preg_match('/NOT NULL/i',$v)) {
+ $v = preg_replace('/NOT NULL/i','',$v);
+ }
+ // this next block doesn't work - there is no way that I can see to
+ // explicitly ask a column to be null using $flds
+ else if ($set_null = preg_match('/NULL/i',$v)) {
+ // if they didn't specify not null, see if they explicitely asked for null
+ // Lookbehind pattern covers the case 'fieldname NULL datatype DEFAULT NULL'
+ // only the first NULL should be removed, not the one specifying
+ // the default value
+ $v = preg_replace('/(?<!DEFAULT)\sNULL/i','',$v);
+ }
+
+ if (preg_match('/^([^ ]+) .*DEFAULT (\'[^\']+\'|\"[^\"]+\"|[^ ]+)/',$v,$matches)) {
+ $existing = $this->MetaColumns($tabname);
+ list(,$colname,$default) = $matches;
+ $alter .= $colname;
+ if ($this->connection) {
+ $old_coltype = $this->connection->MetaType($existing[strtoupper($colname)]);
+ }
+ else {
+ $old_coltype = $t;
+ }
+ $v = preg_replace('/^' . preg_quote($colname) . '\s/', '', $v);
+ $t = trim(str_replace('DEFAULT '.$default,'',$v));
+
+ // Type change from bool to int
+ if ( $old_coltype == 'L' && $t == 'INTEGER' ) {
+ $sql[] = $alter . ' DROP DEFAULT';
+ $sql[] = $alter . " TYPE $t USING ($colname::BOOL)::INT";
+ $sql[] = $alter . " SET DEFAULT $default";
+ }
+ // Type change from int to bool
+ else if ( $old_coltype == 'I' && $t == 'BOOLEAN' ) {
+ if( strcasecmp('NULL', trim($default)) != 0 ) {
+ $default = $this->connection->qstr($default);
+ }
+ $sql[] = $alter . ' DROP DEFAULT';
+ $sql[] = $alter . " TYPE $t USING CASE WHEN $colname = 0 THEN false ELSE true END";
+ $sql[] = $alter . " SET DEFAULT $default";
+ }
+ // Any other column types conversion
+ else {
+ $sql[] = $alter . " TYPE $t";
+ $sql[] = $alter . " SET DEFAULT $default";
+ }
+
+ }
+ else {
+ // drop default?
+ preg_match ('/^\s*(\S+)\s+(.*)$/',$v,$matches);
+ list (,$colname,$rest) = $matches;
+ $alter .= $colname;
+ $sql[] = $alter . ' TYPE ' . $rest;
+ }
+
+# list($colname) = explode(' ',$v);
+ if ($not_null) {
+ // this does not error out if the column is already not null
+ $sql[] = $alter . ' SET NOT NULL';
+ }
+ if ($set_null) {
+ // this does not error out if the column is already null
+ $sql[] = $alter . ' DROP NOT NULL';
+ }
+ }
+ return $sql;
+ }
+
+ // does not have alter column
+ if (!$tableflds) {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL needs a complete table-definiton for PostgreSQL");
+ return array();
+ }
+ return $this->_recreate_copy_table($tabname,False,$tableflds,$tableoptions);
+ }
+
+ /**
+ * Drop one column
+ *
+ * Postgres < 7.3 can't do that on it's own, you need to supply the complete defintion of the new table,
+ * to allow, recreating the table and copying the content over to the new table
+ * @param string $tabname table-name
+ * @param string $flds column-name and type for the changed column
+ * @param string $tableflds complete defintion of the new table, eg. for postgres, default ''
+ * @param array/ $tableoptions options for the new table see CreateTableSQL, default ''
+ * @return array with SQL strings
+ */
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $has_drop_column = 7.3 <= (float) @$this->serverInfo['version'];
+ if (!$has_drop_column && !$tableflds) {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL needs complete table-definiton for PostgreSQL < 7.3");
+ return array();
+ }
+ if ($has_drop_column) {
+ return ADODB_DataDict::DropColumnSQL($tabname, $flds);
+ }
+ return $this->_recreate_copy_table($tabname,$flds,$tableflds,$tableoptions);
+ }
+
+ /**
+ * Save the content into a temp. table, drop and recreate the original table and copy the content back in
+ *
+ * We also take care to set the values of the sequenz and recreate the indexes.
+ * All this is done in a transaction, to not loose the content of the table, if something went wrong!
+ * @internal
+ * @param string $tabname table-name
+ * @param string $dropflds column-names to drop
+ * @param string $tableflds complete defintion of the new table, eg. for postgres
+ * @param array/string $tableoptions options for the new table see CreateTableSQL, default ''
+ * @return array with SQL strings
+ */
+ function _recreate_copy_table($tabname,$dropflds,$tableflds,$tableoptions='')
+ {
+ if ($dropflds && !is_array($dropflds)) $dropflds = explode(',',$dropflds);
+ $copyflds = array();
+ foreach($this->MetaColumns($tabname) as $fld) {
+ if (!$dropflds || !in_array($fld->name,$dropflds)) {
+ // we need to explicit convert varchar to a number to be able to do an AlterColumn of a char column to a nummeric one
+ if (preg_match('/'.$fld->name.' (I|I2|I4|I8|N|F)/i',$tableflds,$matches) &&
+ in_array($fld->type,array('varchar','char','text','bytea'))) {
+ $copyflds[] = "to_number($fld->name,'S9999999999999D99')";
+ } else {
+ $copyflds[] = $fld->name;
+ }
+ // identify the sequence name and the fld its on
+ if ($fld->primary_key && $fld->has_default &&
+ preg_match("/nextval\('([^']+)'::text\)/",$fld->default_value,$matches)) {
+ $seq_name = $matches[1];
+ $seq_fld = $fld->name;
+ }
+ }
+ }
+ $copyflds = implode(', ',$copyflds);
+
+ $tempname = $tabname.'_tmp';
+ $aSql[] = 'BEGIN'; // we use a transaction, to make sure not to loose the content of the table
+ $aSql[] = "SELECT * INTO TEMPORARY TABLE $tempname FROM $tabname";
+ $aSql = array_merge($aSql,$this->DropTableSQL($tabname));
+ $aSql = array_merge($aSql,$this->CreateTableSQL($tabname,$tableflds,$tableoptions));
+ $aSql[] = "INSERT INTO $tabname SELECT $copyflds FROM $tempname";
+ if ($seq_name && $seq_fld) { // if we have a sequence we need to set it again
+ $seq_name = $tabname.'_'.$seq_fld.'_seq'; // has to be the name of the new implicit sequence
+ $aSql[] = "SELECT setval('$seq_name',MAX($seq_fld)) FROM $tabname";
+ }
+ $aSql[] = "DROP TABLE $tempname";
+ // recreate the indexes, if they not contain one of the droped columns
+ foreach($this->MetaIndexes($tabname) as $idx_name => $idx_data)
+ {
+ if (substr($idx_name,-5) != '_pkey' && (!$dropflds || !count(array_intersect($dropflds,$idx_data['columns'])))) {
+ $aSql = array_merge($aSql,$this->CreateIndexSQL($idx_name,$tabname,$idx_data['columns'],
+ $idx_data['unique'] ? array('UNIQUE') : False));
+ }
+ }
+ $aSql[] = 'COMMIT';
+ return $aSql;
+ }
+
+ function DropTableSQL($tabname)
+ {
+ $sql = ADODB_DataDict::DropTableSQL($tabname);
+
+ $drop_seq = $this->_DropAutoIncrement($tabname);
+ if ($drop_seq) $sql[] = $drop_seq;
+
+ return $sql;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname, &$ftype, $fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ if ($fautoinc) {
+ $ftype = 'SERIAL';
+ return '';
+ }
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ // search for a sequece for the given table (asumes the seqence-name contains the table-name!)
+ // if yes return sql to drop it
+ // this is still necessary if postgres < 7.3 or the SERIAL was created on an earlier version!!!
+ function _DropAutoIncrement($tabname)
+ {
+ $tabname = $this->connection->quote('%'.$tabname.'%');
+
+ $seq = $this->connection->GetOne("SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relname LIKE $tabname AND relkind='S'");
+
+ // check if a tables depends on the sequenz and it therefor cant and dont need to be droped separatly
+ if (!$seq || $this->connection->GetOne("SELECT relname FROM pg_class JOIN pg_depend ON pg_class.relfilenode=pg_depend.objid WHERE relname='$seq' AND relkind='S' AND deptype='i'")) {
+ return False;
+ }
+ return "DROP SEQUENCE ".$seq;
+ }
+
+ function RenameTableSQL($tabname,$newname)
+ {
+ if (!empty($this->schema)) {
+ $rename_from = $this->TableName($tabname);
+ $schema_save = $this->schema;
+ $this->schema = false;
+ $rename_to = $this->TableName($newname);
+ $this->schema = $schema_save;
+ return array (sprintf($this->renameTable, $rename_from, $rename_to));
+ }
+
+ return array (sprintf($this->renameTable, $this->TableName($tabname),$this->TableName($newname)));
+ }
+
+ /*
+ CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
+ { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
+ | table_constraint } [, ... ]
+ )
+ [ INHERITS ( parent_table [, ... ] ) ]
+ [ WITH OIDS | WITHOUT OIDS ]
+ where column_constraint is:
+ [ CONSTRAINT constraint_name ]
+ { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
+ CHECK (expression) |
+ REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
+ [ ON DELETE action ] [ ON UPDATE action ] }
+ [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ and table_constraint is:
+ [ CONSTRAINT constraint_name ]
+ { UNIQUE ( column_name [, ... ] ) |
+ PRIMARY KEY ( column_name [, ... ] ) |
+ CHECK ( expression ) |
+ FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
+ [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
+ [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ */
+
+
+ /*
+ CREATE [ UNIQUE ] INDEX index_name ON table
+[ USING acc_method ] ( column [ ops_name ] [, ...] )
+[ WHERE predicate ]
+CREATE [ UNIQUE ] INDEX index_name ON table
+[ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
+[ WHERE predicate ]
+ */
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname, $tabname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
+
+ $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' ';
+
+ if (isset($idxoptions['HASH']))
+ $s .= 'USING HASH ';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s .= '(' . $flds . ')';
+ $sql[] = $s;
+
+ return $sql;
+ }
+
+ function _GetSize($ftype, $ty, $fsize, $fprec)
+ {
+ if (strlen($fsize) && $ty != 'X' && $ty != 'B' && $ty != 'I' && strpos($ftype,'(') === false) {
+ $ftype .= "(".$fsize;
+ if (strlen($fprec)) $ftype .= ",".$fprec;
+ $ftype .= ')';
+ }
+ return $ftype;
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-sapdb.inc.php b/vendor/adodb/adodb-php/datadict/datadict-sapdb.inc.php
new file mode 100644
index 0000000..794edf7
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-sapdb.inc.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+ Modified from datadict-generic.inc.php for sapdb by RalfBecker-AT-outdoor-training.de
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_sapdb extends ADODB_DataDict {
+
+ var $databaseType = 'sapdb';
+ var $seqField = false;
+ var $renameColumn = 'RENAME COLUMN %s.%s TO %s';
+
+ function ActualType($meta)
+ {
+ switch($meta) {
+ case 'C': return 'VARCHAR';
+ case 'XL':
+ case 'X': return 'LONG';
+
+ case 'C2': return 'VARCHAR UNICODE';
+ case 'X2': return 'LONG UNICODE';
+
+ case 'B': return 'LONG';
+
+ case 'D': return 'DATE';
+ case 'TS':
+ case 'T': return 'TIMESTAMP';
+
+ case 'L': return 'BOOLEAN';
+ case 'I': return 'INTEGER';
+ case 'I1': return 'FIXED(3)';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INTEGER';
+ case 'I8': return 'FIXED(20)';
+
+ case 'F': return 'FLOAT(38)';
+ case 'N': return 'FIXED';
+ default:
+ return $meta;
+ }
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ static $maxdb_type2adodb = array(
+ 'VARCHAR' => 'C',
+ 'CHARACTER' => 'C',
+ 'LONG' => 'X', // no way to differ between 'X' and 'B' :-(
+ 'DATE' => 'D',
+ 'TIMESTAMP' => 'T',
+ 'BOOLEAN' => 'L',
+ 'INTEGER' => 'I4',
+ 'SMALLINT' => 'I2',
+ 'FLOAT' => 'F',
+ 'FIXED' => 'N',
+ );
+ $type = isset($maxdb_type2adodb[$t]) ? $maxdb_type2adodb[$t] : 'C';
+
+ // convert integer-types simulated with fixed back to integer
+ if ($t == 'FIXED' && !$fieldobj->scale && ($len == 20 || $len == 3)) {
+ $type = $len == 20 ? 'I8' : 'I1';
+ }
+ if ($fieldobj->auto_increment) $type = 'R';
+
+ return $type;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if ($funsigned) $suffix .= ' UNSIGNED';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if ($fautoinc) $suffix .= ' DEFAULT SERIAL';
+ elseif (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ return array( 'ALTER TABLE ' . $tabname . ' ADD (' . implode(', ',$lines) . ')' );
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ return array( 'ALTER TABLE ' . $tabname . ' MODIFY (' . implode(', ',$lines) . ')' );
+ }
+
+ function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ if (!is_array($flds)) $flds = explode(',',$flds);
+ foreach($flds as $k => $v) {
+ $flds[$k] = $this->NameQuote($v);
+ }
+ return array( 'ALTER TABLE ' . $tabname . ' DROP (' . implode(', ',$flds) . ')' );
+ }
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-sqlite.inc.php b/vendor/adodb/adodb-php/datadict/datadict-sqlite.inc.php
new file mode 100644
index 0000000..fc994fe
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-sqlite.inc.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+ SQLite datadict Andrei Besleaga
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_sqlite extends ADODB_DataDict {
+ var $databaseType = 'sqlite';
+ var $seqField = false;
+ var $addCol=' ADD COLUMN';
+ var $dropTable = 'DROP TABLE IF EXISTS %s';
+ var $dropIndex = 'DROP INDEX IF EXISTS %s';
+ var $renameTable = 'ALTER TABLE %s RENAME TO %s';
+
+
+
+ function ActualType($meta)
+ {
+ switch(strtoupper($meta)) {
+ case 'C': return 'VARCHAR'; // TEXT , TEXT affinity
+ case 'XL':return 'LONGTEXT'; // TEXT , TEXT affinity
+ case 'X': return 'TEXT'; // TEXT , TEXT affinity
+
+ case 'C2': return 'VARCHAR'; // TEXT , TEXT affinity
+ case 'X2': return 'LONGTEXT'; // TEXT , TEXT affinity
+
+ case 'B': return 'LONGBLOB'; // TEXT , NONE affinity , BLOB
+
+ case 'D': return 'DATE'; // NUMERIC , NUMERIC affinity
+ case 'T': return 'DATETIME'; // NUMERIC , NUMERIC affinity
+ case 'L': return 'TINYINT'; // NUMERIC , INTEGER affinity
+
+ case 'R':
+ case 'I4':
+ case 'I': return 'INTEGER'; // NUMERIC , INTEGER affinity
+ case 'I1': return 'TINYINT'; // NUMERIC , INTEGER affinity
+ case 'I2': return 'SMALLINT'; // NUMERIC , INTEGER affinity
+ case 'I8': return 'BIGINT'; // NUMERIC , INTEGER affinity
+
+ case 'F': return 'DOUBLE'; // NUMERIC , REAL affinity
+ case 'N': return 'NUMERIC'; // NUMERIC , NUMERIC affinity
+ default:
+ return $meta;
+ }
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if ($funsigned) $suffix .= ' UNSIGNED';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fautoinc) $suffix .= ' AUTOINCREMENT';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported natively by SQLite");
+ return array();
+ }
+
+ function DropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ if ($this->debug) ADOConnection::outp("DropColumnSQL not supported natively by SQLite");
+ return array();
+ }
+
+ function RenameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='')
+ {
+ if ($this->debug) ADOConnection::outp("RenameColumnSQL not supported natively by SQLite");
+ return array();
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/datadict/datadict-sybase.inc.php b/vendor/adodb/adodb-php/datadict/datadict-sybase.inc.php
new file mode 100644
index 0000000..87654ff
--- /dev/null
+++ b/vendor/adodb/adodb-php/datadict/datadict-sybase.inc.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB2_sybase extends ADODB_DataDict {
+ var $databaseType = 'sybase';
+
+ var $dropIndex = 'DROP INDEX %2$s.%1$s';
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+
+ case 'INT':
+ case 'INTEGER': return 'I';
+ case 'BIT':
+ case 'TINYINT': return 'I1';
+ case 'SMALLINT': return 'I2';
+ case 'BIGINT': return 'I8';
+
+ case 'REAL':
+ case 'FLOAT': return 'F';
+ default: return parent::MetaType($t,$len,$fieldobj);
+ }
+ }
+
+ function ActualType($meta)
+ {
+ switch(strtoupper($meta)) {
+ case 'C': return 'VARCHAR';
+ case 'XL':
+ case 'X': return 'TEXT';
+
+ case 'C2': return 'NVARCHAR';
+ case 'X2': return 'NTEXT';
+
+ case 'B': return 'IMAGE';
+
+ case 'D': return 'DATETIME';
+ case 'TS':
+ case 'T': return 'DATETIME';
+ case 'L': return 'BIT';
+
+ case 'I': return 'INT';
+ case 'I1': return 'TINYINT';
+ case 'I2': return 'SMALLINT';
+ case 'I4': return 'INT';
+ case 'I8': return 'BIGINT';
+
+ case 'F': return 'REAL';
+ case 'N': return 'NUMERIC';
+ default:
+ return $meta;
+ }
+ }
+
+
+ function AddColumnSQL($tabname, $flds)
+ {
+ $tabname = $this->TableName ($tabname);
+ $f = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ $s = "ALTER TABLE $tabname $this->addCol";
+ foreach($lines as $v) {
+ $f[] = "\n $v";
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ $tabname = $this->TableName ($tabname);
+ $sql = array();
+ list($lines,$pkey) = $this->_GenFields($flds);
+ foreach($lines as $v) {
+ $sql[] = "ALTER TABLE $tabname $this->alterCol $v";
+ }
+
+ return $sql;
+ }
+
+ function DropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+ {
+ $tabname = $this->TableName($tabname);
+ if (!is_array($flds)) $flds = explode(',',$flds);
+ $f = array();
+ $s = "ALTER TABLE $tabname";
+ foreach($flds as $v) {
+ $f[] = "\n$this->dropCol ".$this->NameQuote($v);
+ }
+ $s .= implode(', ',$f);
+ $sql[] = $s;
+ return $sql;
+ }
+
+ // return string must begin with space
+ function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+ {
+ $suffix = '';
+ if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
+ if ($fautoinc) $suffix .= ' DEFAULT AUTOINCREMENT';
+ if ($fnotnull) $suffix .= ' NOT NULL';
+ else if ($suffix == '') $suffix .= ' NULL';
+ if ($fconstraint) $suffix .= ' '.$fconstraint;
+ return $suffix;
+ }
+
+ /*
+CREATE TABLE
+ [ database_name.[ owner ] . | owner. ] table_name
+ ( { < column_definition >
+ | column_name AS computed_column_expression
+ | < table_constraint > ::= [ CONSTRAINT constraint_name ] }
+
+ | [ { PRIMARY KEY | UNIQUE } [ ,...n ]
+ )
+
+[ ON { filegroup | DEFAULT } ]
+[ TEXTIMAGE_ON { filegroup | DEFAULT } ]
+
+< column_definition > ::= { column_name data_type }
+ [ COLLATE < collation_name > ]
+ [ [ DEFAULT constant_expression ]
+ | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ]
+ ]
+ [ ROWGUIDCOL]
+ [ < column_constraint > ] [ ...n ]
+
+< column_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ NULL | NOT NULL ]
+ | [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ [ WITH FILLFACTOR = fillfactor ]
+ [ON {filegroup | DEFAULT} ] ]
+ ]
+ | [ [ FOREIGN KEY ]
+ REFERENCES ref_table [ ( ref_column ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( logical_expression )
+ }
+
+< table_constraint > ::= [ CONSTRAINT constraint_name ]
+ { [ { PRIMARY KEY | UNIQUE }
+ [ CLUSTERED | NONCLUSTERED ]
+ { ( column [ ASC | DESC ] [ ,...n ] ) }
+ [ WITH FILLFACTOR = fillfactor ]
+ [ ON { filegroup | DEFAULT } ]
+ ]
+ | FOREIGN KEY
+ [ ( column [ ,...n ] ) ]
+ REFERENCES ref_table [ ( ref_column [ ,...n ] ) ]
+ [ ON DELETE { CASCADE | NO ACTION } ]
+ [ ON UPDATE { CASCADE | NO ACTION } ]
+ [ NOT FOR REPLICATION ]
+ | CHECK [ NOT FOR REPLICATION ]
+ ( search_conditions )
+ }
+
+
+ */
+
+ /*
+ CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name
+ ON { table | view } ( column [ ASC | DESC ] [ ,...n ] )
+ [ WITH < index_option > [ ,...n] ]
+ [ ON filegroup ]
+ < index_option > :: =
+ { PAD_INDEX |
+ FILLFACTOR = fillfactor |
+ IGNORE_DUP_KEY |
+ DROP_EXISTING |
+ STATISTICS_NORECOMPUTE |
+ SORT_IN_TEMPDB
+ }
+*/
+ function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
+ {
+ $sql = array();
+
+ if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
+ $sql[] = sprintf ($this->dropIndex, $idxname, $tabname);
+ if ( isset($idxoptions['DROP']) )
+ return $sql;
+ }
+
+ if ( empty ($flds) ) {
+ return $sql;
+ }
+
+ $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
+ $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : '';
+
+ if ( is_array($flds) )
+ $flds = implode(', ',$flds);
+ $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')';
+
+ if ( isset($idxoptions[$this->upperName]) )
+ $s .= $idxoptions[$this->upperName];
+
+ $sql[] = $s;
+
+ return $sql;
+ }
+}
diff --git a/vendor/adodb/adodb-php/docs/README.md b/vendor/adodb/adodb-php/docs/README.md
new file mode 100644
index 0000000..b0ec294
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/README.md
@@ -0,0 +1,17 @@
+# ADOdb Documentation
+
+ADOdb documentation is available in the following locations
+
+- [Online](http://adodb.org/)
+- [Download](https://sourceforge.net/projects/adodb/files/Documentation/) for offline use
+
+## Legacy documentation
+
+The old HTML files are available in
+[GitHub](https://github.com/ADOdb/ADOdb/tree/8b8133771ecbe9c95e57abbe5dc3757f0226bfcd/docs),
+or in the release zip/tarballs for version 5.20 and before on
+[Sourceforge](https://sourceforge.net/projects/adodb/files/adodb-php5-only/).
+
+## Changelog
+
+The full historical [Changelog](changelog.md) is available on GitHub.
diff --git a/vendor/adodb/adodb-php/docs/adodb.gif b/vendor/adodb/adodb-php/docs/adodb.gif
new file mode 100644
index 0000000..c5e8dfc
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/adodb.gif
Binary files differ
diff --git a/vendor/adodb/adodb-php/docs/adodb2.gif b/vendor/adodb/adodb-php/docs/adodb2.gif
new file mode 100644
index 0000000..f12ae20
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/adodb2.gif
Binary files differ
diff --git a/vendor/adodb/adodb-php/docs/changelog.md b/vendor/adodb/adodb-php/docs/changelog.md
new file mode 100644
index 0000000..3dac6a2
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/changelog.md
@@ -0,0 +1,535 @@
+# ADOdb Changelog - v5.x
+
+Older changelogs:
+[v4.x](changelog_v4.x.md),
+[v3.x](changelog_v3.x.md),
+[v2.x](changelog_v2.x.md).
+
+## 5.20.14 - 06-Jan-2019
+
+- security: Denial of service in adodb_date(). #467
+- core: Fix support for getMenu with ADODB_FETCH_ASSOC. #460
+- perf/mysql: fix tables() function incompatible with parent. #435
+- perf/mysql: fix error when logging slow queries. #463
+
+## 5.20.13 - 06-Aug-2018
+
+- core: Fix query execution failures with mismatched quotes. #420
+- ldap: Fix connections using URIs. #340
+- mssql: Fix Time field format, allowing autoExecute() to inserting time. #432
+- mssql: Fix Insert_ID returning null with table name in brackets. #313
+- mssql: Fix count wrapper. #423
+- oci8: Fix prepared statements failure. #318
+- oci8po: Fix incorrect query parameter replacements. #370
+- pdo: fix PHP notice due to uninitialized variable. #437
+
+## 5.20.12 - 30-Mar-2018
+
+- adodb: PHP 7.2 compatibility
+ - Replace each() with foreach. #373
+ - Replace deprecated create_function() calls. #404
+ - Replace $php_errormsg with error_get_last(). #405
+- adodb: Don't call `dl()` when the function is disabled #406
+- adodb: Don't bother with magic quotes when not available #407
+- adodb: fix potential SQL injection vector in SelectLimit(). #190 #311 #401
+
+## 5.20.11 - Withdrawn
+
+This release has been withdrawn as it introduced a regression on PHP 5.x.
+Please use version 5.20.12 or later.
+
+## 5.20.10 - 08-Mar-2018
+
+- Fix year validation in adodb_validdate() #375
+- Release db resource when closing connection #379
+- Avoid full file path disclosure in ADOLoadCode() #389
+- mssql: fix PHP warning in _adodb_getcount() #359
+- mssql: string keys are not allowed in parameters arrays #316
+- mysqli: fix PHP warning on DB connect #348
+- pdo: fix auto-commit error in sqlsrv #347
+- sybase: fix PHP Warning in _connect()/_pconnect #371
+
+## 5.20.9 - 21-Dec-2016
+
+- mssql: fix syntax error in version matching regex #305
+
+## 5.20.8 - 17-Dec-2016
+
+- mssql: support MSSQL Server 2016 and later #294
+- mssql: fix Find() returning no results. #298
+- mssql: fix Sequence name forced to 'adodbseq'. #295, #300
+- mssql: fix GenId() not returning next sequence value with SQL Server 2005/2008. #302
+- mssql: fix drop/alter column with existing default constraint. #290
+- mssql: fix PHP notice in MetaColumns(). #289
+- oci8po: fix inconsistent variable binding in SelectLimit() #288
+- oci8po: fix SelectLimit() with prepared statements #282
+
+## 5.20.7 - 20-Sep-2016
+
+- security: Fix SQL injection in PDO drivers qstr() method (CVE-2016-7405). #226
+- oci8po: prevent segfault on PHP 7. #259
+- pdo/mysql: Fix MetaTables() method. #275
+
+## 5.20.6 - 31-Aug-2016
+
+- security: Fix XSS vulnerability in old test script (CVE-2016-4855). #274
+- adodb: Exit with error/exception when the ADOdb Extension is loaded. #269
+- adodb: Fix truncated exception messages. #273
+
+## 5.20.5 - 10-Aug-2016
+
+- adodb: Fix fatal error when connecting with missing extension. #254
+- adodb: Fix _adodb_getcount(). #236
+- mssql: Destructor fails if recordset already closed. #268
+- mssql: Use SQL server native data types if available. #234
+- mysqli: Fix PHP notice in _close() method. #240
+- pdo: Let driver handle SelectDB() and SQLDate() calls. #242
+- xml: Fix PHP strict warning. #260
+- xml: remove calls to 'unset($this)' (PHP 7.1 compatibility). #257
+
+## 5.20.4 - 31-Mar-2016
+
+- adodb: Fix BulkBind() param count validation. #199
+- mysqli: fix PHP warning in recordset destructor. #217
+- mysqli: cast port number to int when connecting (PHP7 compatibility). #218
+
+## 5.20.3 - 01-Jan-2016
+
+- mssql: PHP warning when closing recordset from destructor not fixed in v5.20.2. #180
+
+## 5.20.2 - 27-Dec-2015
+
+- adodb: Remove a couple leftover PHP 4.x constructors (PHP7 compatibility). #139
+- db2ora: Remove deprecated preg_replace '/e' flag (PHP7 compatibility). #168
+- mysql: MoveNext() now respects ADODB_ASSOC_CASE. #167
+- mssql, mysql, informix: Avoid PHP warning when closing recordset from destructor. #170
+
+## 5.20.1 - 06-Dec-2015
+
+- adodb: Fix regression introduced in 5.20.0, causing a PHP Warning when
+ calling GetAssoc() on an empty recordset. See Github #162
+- ADOConnection::Version() now handles SemVer. See Github #164
+
+## 5.20.0 - 28-Nov-2015
+
+- adodb: Fix regression introduced in v5.19, causing queries to return empty rows. See Github #20, #93, #95
+- adodb: Fix regression introduced in v5.19 in GetAssoc() with ADODB_FETCH_ASSOC mode and '0' as data. See Github #102
+- adodb: AutoExecute correctly handles empty result set in case of updates. See Github #13
+- adodb: Fix regex in Version(). See Github #16
+- adodb: Align method signatures to definition in parent class ADODB_DataDict. See Github #31
+- adodb: Improve compatibility of ADORecordSet_empty, thanks to Sjan Evardsson. See Github #43
+- adodb: fix ADODB_Session::open() failing after successful ADONewConnection() call, thanks to Sjan Evardsson. See Github #44
+- adodb: Only include memcache library once for PHPUnit 4.x, thanks to Alan Farquharson. See Github #74
+- adodb: Move() returns false when given row is < 0, thanks to Mike Benoit.
+- adodb: Add support for pagination with complex queries, thanks to Mike Benoit. See Github #88
+- adodb: Parse port out of hostname if specified in connection parameters, thanks to Andy Theuninck. See Github #63
+- adodb: Fix inability to set values from 0 to null (and vice versa) with Active Record, thanks to Louis Johnson. See Github #71
+- adodb: Fix PHP strict warning in ADODB_Active_Record::Reload(), thanks to Boštjan Žokš. See Github #75
+- adodb: Add mssql's DATETIME2 type to ADOConnection::MetaType(), thanks to MarcelTO. See Github #80
+- adodb: When flushing cache, initialize it if it is not set, thanks to Paul Haggart. See Github #57
+- adodb: Define DB_AUTOQUERY_* constants in main include file. See Github #49
+- adodb: Improve documentation of fetch mode and assoc case
+- adodb: Improve logic to build the assoc case bind array
+- adodb: Strict-standards compliance for function names. See Github #18, #142
+- adodb: Remove old PHP 4.x constructors for compatibility with PHP 7. See Github #139
+- adodb: Initialize charset in ADOConnection::SetCharSet. See Github #39
+- adodb: Fix incorrect handling of input array in Execute(). See Github #146
+- adodb: Release Recordset when raising exception. See Github #143
+- adodb: Added new setConnectionParameter() method, currently implemented in mssqlnative driver only. See Github #158.
+- adodb-lib: Optimize query pagination, thanks to Mike Benoit. See Github #110
+- memcache: use include_once() to avoid issues with PHPUnit. See http://phplens.com/lens/lensforum/msgs.php?id=19489
+- mssql_n: Allow use of prepared statements with driver. See Github #22
+- mssqlnative: Use ADOConnection::outp instead of error_log. See Github #12
+- mssqlnative: fix failure on Insert_ID() if the insert statement contains a semicolon in a value string, thanks to sketule. See Github #96
+- mssqlnative: Fix "invalid parameter was passed to sqlsrv_configure" error, thanks to Ray Morris. See Github #103
+- mssqlnative: Fix insert_ID() failing if server returns more than 1 row, thanks to gitjti. See Github #41
+- mysql: prevent race conditions when creating/dropping sequences, thanks to MikeB. See Github #28
+- mysql: Fix adodb_strip_order_by() bug causing SQL error for subqueries with order/limit clause, thanks to MikeB.
+- mysql: workaround for HHVM behavior, thanks to Mike Benoit.
+- mysqli: Fix qstr() when called without an active connection. See Github #11
+- oci8: Fix broken quoting of table name in AddColumnSQL and AlterColumnSQL, thanks to Andreas Fernandez. see Github #67
+- oci8: Allow oci8 driver to use lowercase field names in assoc mode. See Github #21
+- oci8po: Prevent replacement of '?' within strings, thanks to Mark Newnham. See Github #132
+- pdo: Added missing property (fixes PHP notices). see Github #56
+- pdo: Align method signatures with parent class, thanks to Andy Theuninck. see Github #62
+- pdo: new sqlsrv driver, thanks to MarcelTO. See Github #81
+- pdo/mysql: New methods to make the driver behave more like mysql/mysqli, thanks to Andy Theuninck. see Github #40
+- postgres: Stop using legacy function aliases
+- postgres: Fix AlterColumnSQL when updating multiple columns, thanks to Jouni Ahto. See Github #72
+- postgres: Fix support for HHVM 3.6, thanks to Mike Benoit. See Github #87
+- postgres: Noblob optimization, thanks to Mike Benoit. See Github #112
+- postgres7: fix system warning in MetaColumns() with schema. See http://phplens.com/lens/lensforum/msgs.php?id=19481
+- sqlite3: ServerInfo() now returns driver's version
+- sqlite3: Fix wrong connection parameter in _connect(), thanks to diogotoscano. See Github #51
+- sqlite3: Fix FetchField, thanks to diogotoscano. See Github #53
+- sqlite3: Fix result-less SQL statements executed twice. See Github #99
+- sqlite3: use -1 for _numOfRows. See Github #151
+- xmlschema: Fix ExtractSchema() when given $prefix and $stripprefix parameters, thanks to peterdd. See Github #92
+- Convert languages files to UTF-8, thanks to Marc-Etienne Vargenau. See Github #32.
+
+## 5.19 - 23-Apr-2014
+
+**NOTE:**
+This release suffers from a [known issue with Associative Fetch Mode](https://github.com/ADOdb/ADOdb/issues/20)
+(i.e. when $ADODB_FETCH_MODE is set to ADODB_FETCH_ASSOC).
+It causes recordsets to return empty strings (no data) when using some database drivers.
+The problem has been reported on MSSQL, Interbase and Foxpro, but possibly affects
+other database types as well; all drivers derived from the above are also impacted.
+
+- adodb: GetRowAssoc will return null as required. See http://phplens.com/lens/lensforum/msgs.php?id=19289
+- adodb: Fix GetRowAssoc bug introduced in 5.17, causing function to return data from previous fetch for NULL fields. See http://phplens.com/lens/lensforum/msgs.php?id=17539
+- adodb: GetAssoc will return a zero-based array when 2nd column is null. See https://sourceforge.net/p/adodb/bugs/130/
+- adodb: Execute no longer ignores single parameters evaluating to false. See https://sourceforge.net/p/adodb/patches/32/
+- adodb: Fix LIMIT 1 clause in subquery gets stripped off. See http://phplens.com/lens/lensforum/msgs.php?id=17813
+- adodb-lib: Fix columns quoting bug. See https://sourceforge.net/p/adodb/bugs/127/
+- Added new ADODB_ASSOC_CASE_* constants. Thx to Damien Regad.
+- sessions: changed lob handling to detect all variations of oci8 driver.
+- ads: clear fields before fetching. See http://phplens.com/lens/lensforum/msgs.php?id=17539
+- mssqlnative: fixed many FetchField compat issues. See http://phplens.com/lens/lensforum/msgs.php?id=18464. Also date format changed to remove timezone.
+- mssqlnative: Numerous fixes and improvements by Mark Newnham
+ - Driver supports SQL Server 2005, 2008 and 2012
+ - Bigint data types mapped to I8 instead of I
+ - Reintroduced MetaColumns function
+ - On SQL Server 2012, makes use of new CREATE SEQUENCE statement
+ - FetchField caches metadata at initialization to improve performance
+ - etc.
+- mssqlnative: Fix Insert ID on prepared statement, thanks to Mike Parks. See http://phplens.com/lens/lensforum/msgs.php?id=19079
+- mssql: timestamp format changed to `Y-m-d\TH:i:s` (ISO 8601) to make them independent from DATEFORMAT setting, as recommended on
+ [Microsoft TechNet](http://technet.microsoft.com/en-us/library/ms180878%28v=sql.105%29.aspx#StringLiteralDateandTimeFormats).
+- mysql/mysqli: Fix ability for MetaTables to filter by table name, broken since 5.15. See http://phplens.com/lens/lensforum/msgs.php?id=19359
+- odbc: Fixed MetaTables and MetaPrimaryKeys definitions in odbc driver to match adoconnection class.
+- odbc: clear fields before fetching. See http://phplens.com/lens/lensforum/msgs.php?id=17539
+- oci8: GetRowAssoc now works in ADODB_FETCH_ASSOC fetch mode
+- oci8: MetaType and MetaForeignKeys argument count are now strict-standards compliant
+- oci8: Added trailing `;` on trigger creation for sequence fields, prevents occurence of ORA-24344
+- oci8quercus: new oci8 driver with support for quercus jdbc data types.
+- pdo: Fixed concat recursion bug in 5.3. See http://phplens.com/lens/lensforum/msgs.php?id=19285
+- pgsql: Default driver (postgres/pgsql) is now postgres8
+- pgsql: Fix output of BLOB (bytea) columns with PostgreSQL >= 9.0
+- pgsql: Fix handling of DEFAULT NULL columns in AlterColumnSQL
+- pgsql: Fix mapping of error message to ADOdb error codes
+- pgsql: Reset parameter number in Param() method when $name == false
+- postgres8: New class/type with correct behavior for _insertid(). See Github #8
+- postgres9: Fixed assoc problem. See http://phplens.com/lens/lensforum/msgs.php?id=19296
+- sybase: Removed redundant sybase_connect() call in _connect(). See Github #3
+- sybase: Allow connection on custom port. See Github #9
+- sybase: Fix null values returned with ASSOC fetch mode. See Github #10
+- Added Composer support. See Github #7
+
+## 5.18 - 3 Sep 2012
+
+- datadict-postgres: Fixes bug in ALTER COL. See http://phplens.com/lens/lensforum/msgs.php?id=19202.
+- datadict-postgres: fixed bugs in MetaType() checking $fieldobj properties.
+- GetRowAssoc did not work with null values. Bug in 5.17.
+- postgres9: New driver to better support PostgreSQL 9. Thx Glenn Herteg and Cacti team.
+- sqlite3: Modified to support php 5.4. Thx Günter Weber [built.development#googlemail.com]
+- adodb: When fetch mode is ADODB_FETCH_ASSOC, and we execute `$db->GetAssoc("select 'a','0'");` we get an error. Fixed. See http://phplens.com/lens/lensforum/msgs.php?id=19190
+- adodb: Caching directory permissions now configurable using global variable $ADODB_CACHE_PERMS. Default value is 0771.
+- mysqli: SetCharSet() did not return true (success) or false (fail) correctly. Fixed.
+- mysqli: changed dataProvider to 'mysql' so that MetaError and other shared functions will work.
+- mssqlnative: Prepare() did not work previously. Now calling Prepare() will work but the sql is not actually compiled. Unfortunately bind params are passed to sqlsrv_prepare and not to sqlsrv_execute. make Prepare() and empty function, and we still execute the unprepared stmt.
+- mysql: FetchField(-1), turns it is is not possible to retrieve the max_length. Set to -1.
+- mysql-perf: Fixed "SHOW INNODB STATUS". Change to "SHOW ENGINE INNODB STATUS"
+
+## 5.17 - 18 May 2012
+
+- Active Record: Removed trailing whitespace from adodb-active-record.inc.php.
+- odbc: Added support for $database parameter in odbc Connect() function. E.g. $DB->Connect($dsn_without_db, $user, $pwd, $database).
+ Previously $database had to be left blank and the $dsn was used to pass in this parameter.
+- oci8: Added better empty($rs) error handling to metaindexes().
+- oci8: Changed to use newer oci API to support PHP 5.4.
+- adodb.inc.php: Changed GetRowAssoc to more generic code that will work in all scenarios.
+
+## 5.16 - 26 March 2012
+
+- mysqli: extra mysqli_next_result() in close() removed. See http://phplens.com/lens/lensforum/msgs.php?id=19100
+- datadict-oci8: minor typo in create sequence trigger fixed. See http://phplens.com/lens/lensforum/msgs.php?id=18879.
+- security: safe date parsing changes. Does not impact security, these are code optimisations. Thx Saithis.
+- postgres, oci8, oci8po, db2oci: Param() function parameters inconsistent with base class. $type='C' missing. Fixed.
+- active-record: locked bug fixed. http://phplens.com/lens/lensforum/msgs.php?phplens_forummsg=new&id=19073
+- mysql, mysqli and informix: added MetaProcedures. Metaprocedures allows to retrieve an array list of all procedures in database. http://phplens.com/lens/lensforum/msgs.php?id=18414
+- Postgres7: added support for serial data type in MetaColumns().
+
+## 5.15 - 19 Jan 2012
+
+- pdo: fix ErrorMsg() to detect errors correctly. Thx Jens.
+- mssqlnative: added another check for $this->fields array exists.
+- mssqlnative: bugs in FetchField() fixed. See http://phplens.com/lens/lensforum/msgs.php?id=19024
+- DBDate and DBTimeStamp had sql injection bug. Fixed. Thx Saithis
+- mysql and mysqli: MetaTables() now identifies views and tables correctly.
+- Added function adodb_time() to adodb-time.inc.php. Generates current time in unsigned integer format.
+
+## 5.14 - 8 Sep 2011
+
+- mysqli: fix php compilation bug.
+- postgres: bind variables did not work properly. Fixed.
+- postgres: blob handling bug in _decode. Fixed.
+- ActiveRecord: if a null field was never updated, activerecord would still update the record. Fixed.
+- ActiveRecord: 1 char length string never quoted. Fixed.
+- LDAP: Connection string ldap:// and ldaps:// did not work. Fixed.
+
+## 5.13 - 15 Aug 2011
+
+- Postgres: Fix in 5.12 was wrong. Uses pg_unescape_bytea() correctly now in _decode.
+- GetInsertSQL/GetUpdateSQL: Now $ADODB_QUOTE_FIELDNAMES allows you to define 'NATIVE', 'UPPER', 'LOWER'. If set to true, will default to 'UPPER'.
+- mysqli: added support for persistent connections 'p:'.
+- mssqlnative: ADODB_FETCH_BOTH did not work properly. Fixed.
+- mssqlnative: return values for stored procedures where not returned! Fixed. See http://phplens.com/lens/lensforum/msgs.php?id=18919
+- mssqlnative: timestamp and fetchfield bugs fixed. http ://phplens.com/lens/lensforum/msgs.php?id=18453
+
+## 5.12 - 30 June 2011
+
+- Postgres: Added information_schema support for postgresql.
+- Postgres: Use pg_unescape_bytea() in _decode.
+- Fix bulk binding with oci8. http://phplens.com/lens/lensforum/msgs.php?id=18786
+- oci8 perf: added wait evt monitoring. Also db cache advice now handles multiple buffer pools properly.
+- sessions2: Fixed setFetchMode problem.
+- sqlite: Some DSN connection settings were not parsed correctly.
+- mysqli: now GetOne obeys $ADODB_GETONE_EOF;
+- memcache: compress option did not work. Fixed. See http://phplens.com/lens/lensforum/msgs.php?id=18899
+
+## 5.11 - 5 May 2010
+
+- mysql: Fixed GetOne() to return null if no records returned.
+- oci8 perf: added stats on sga, rman, memory usage, and flash in performance tab.
+- odbtp: Now you can define password in $password field of Connect()/PConnect(), and it will add it to DSN.
+- Datadict: altering columns did not consider the scale of the column. Now it does.
+- mssql: Fixed problem with ADODB_CASE_ASSOC causing multiple versions of column name appearing in recordset fields.
+- oci8: Added missing & to refLob.
+- oci8: Added obj->scale to FetchField().
+- oci8: Now you can get column info of a table in a different schema, e.g. MetaColumns("schema.table") is supported.
+- odbc_mssql: Fixed missing $metaDatabasesSQL.
+- xmlschema: Changed declaration of create() to create($xmls) to fix compat problems. Also changed constructor adoSchema() to pass in variable instead of variable reference.
+- ado5: Fixed ado5 exceptions to only display errors when $this->debug=true;
+- Added DSN support to sessions2.inc.php.
+- adodb-lib.inc.php. Fixed issue with _adodb_getcount() not using $secs2cache parameter.
+- adodb active record. Fixed caching bug. See http://phplens.com/lens/lensforum/msgs.php?id=18288.
+- db2: fixed ServerInfo().
+- adodb_date: Added support for format 'e' for TZ as in adodb_date('e')
+- Active Record: If you have a field which is a string field (with numbers in) and you add preceding 0's to it the adodb library does not pick up the fact that the field has changed because of the way php's == works (dodgily). The end result is that it never gets updated into the database - fix by Matthew Forrester (MediaEquals). [matthew.forrester#mediaequals.com]
+- Fixes RowLock() and MetaIndexes() inconsistencies. See http://phplens.com/lens/lensforum/msgs.php?id=18236
+- Active record support for postgrseql boolean. See http://phplens.com/lens/lensforum/msgs.php?id=18246
+- By default, Execute 2D array is disabled for security reasons. Set $conn->bulkBind = true to enable. See http://phplens.com/lens/lensforum/msgs.php?id=18270. Note this breaks backward compat.
+- MSSQL: fixes for 5.2 compat. http://phplens.com/lens/lensforum/msgs.php?id=18325
+- Changed Version() to return a string instead of a float so it correctly returns 5.10 instead of 5.1.
+
+## 5.10 - 10 Nov 2009
+
+- Fixed memcache to properly support $rs->timeCreated.
+- adodb-ado.inc.php: Added BigInt support for PHP5. Will return float instead to support large numbers. Thx nasb#mail.goo.ne.jp.
+- adodb-mysqli.inc.php: mysqli_multi_query is now turned off by default. To turn it on, use $conn->multiQuery = true; This is because of the risks of sql injection. See http://phplens.com/lens/lensforum/msgs.php?id=18144
+- New db2oci driver for db2 9.7 when using PL/SQL mode. Allows oracle style :0, :1, :2 bind parameters which are remapped to ? ? ?.
+- adodb-db2.inc.php: fixed bugs in MetaTables. SYS owner field not checked properly. Also in $conn->Connect($dsn, null, null, $schema) and PConnect($dsn, null, null, $schema), we do a SET SCHEMA=$schema if successful connection.
+- adodb-mysqli.inc.php: Now $rs->Close() closes all pending next resultsets. Thx Clifton mesmackgod#gmail.com
+- Moved _CreateCache() from PConnect()/Connect() to CacheExecute(). Suggested by Dumka.
+- Many bug fixes to adodb-pdo_sqlite.inc.php and new datadict-sqlite.inc.php. Thx Andrei B. [andreutz#mymail.ro]
+- Removed usage of split (deprecated in php 5.3). Thx david#horizon-nigh.org.
+- Fixed RowLock() parameters to comply with PHP5 strict mode in multiple drivers.
+
+## 5.09 - 25 June 2009
+
+- Active Record: You can force column names to be quoted in INSERT and UPDATE statements, typically because you are using reserved words as column names by setting ADODB_Active_Record::$_quoteNames = true;
+- Added memcache and cachesecs to DSN. e.g.
+
+ ``` php
+ # we have a memcache servers mem1,mem2 on port 8888, compression=off and cachesecs=120
+ $dsn = 'mysql://user:pwd@localhost/mydb?memcache=mem1,mem2:8888:0&cachesecs=120';
+ ```
+
+- Fixed up MetaColumns and MetaPrimaryIndexes() for php 5.3 compat. Thx http://adodb.pastebin.com/m52082b16
+- The postgresql driver's OffsetDate() apparently does not work with postgres 8.3. Fixed.
+- Added support for magic_quotes_sybase in qstr() and addq(). Thanks Eloy and Sam Moffat.
+- The oci8 driver did not handle LOBs properly when binding. Fixed. See http://phplens.com/lens/lensforum/msgs.php?id=17991.
+- Datadict: In order to support TIMESTAMP with subsecond accuracy, added to datadict the new TS type. Supported by mssql, postgresql and oci8 (oracle). Also changed oci8 $conn->sysTimeStamp to use 'SYSTIMESTAMP' instead of 'SYSDATE'. Should be backwards compat.
+- Added support for PHP 5.1+ DateTime objects in DBDate and DBTimeStamp. This means that dates and timestamps will be managed by DateTime objects if you are running PHP 5.1+.
+- Added new property to postgres64 driver to support returning I if type is unique int called $db->uniqueIisR, defaulting to true. See http://phplens.com/lens/lensforum/msgs.php?id=17963
+- Added support for bindarray in adodb_GetActiveRecordsClass with SelectLimit in adodb-active-record.inc.php.
+- Transactions now allowed in ado_access driver. Thx to petar.petrov.georgiev#gmail.com.
+- Sessions2 garbage collection is now much more robust. We perform ORDER BY to prevent deadlock in adodb-sessions2.inc.php.
+- Fixed typo in pdo_sqlite driver.
+
+## 5.08a - 17 Apr 2009
+
+- Fixes wrong version number string.
+- Incorrect + in adodb-datadict.inc.php removed.
+- Fixes missing OffsetDate() function in pdo. Thx paul#mantisforge.org.
+
+## 5.08 - 17 Apr 2009
+
+- adodb-sybase.inc.php driver. Added $conn->charSet support. Thx Luis Henrique Mulinari (luis.mulinari#gmail.com)
+- adodb-ado5.inc.php. Fixed some bind param issues. Thx Jirka Novak.
+- adodb-ado5.inc.php. Now has improved error handling.
+- Fixed typo in adodb-xmlschema03.inc.php. See XMLS_EXISTING_DATA, line 1501. Thx james johnson.
+- Made $inputarr optional for _query() in all drivers.
+- Fixed spelling mistake in flushall() in adodb.inc.ophp.
+- Fixed handling of quotes in adodb_active_record::doquote. Thx Jonathan Hohle (jhohle#godaddy.com).
+- Added new index parameter to adodb_active_record::setdatabaseadaptor. Thx Jonathan Hohle
+- Fixed & readcache() reference compat problem with php 5.3 in adodb.Thx Jonathan Hohle.
+- Some minor $ADODB_CACHE_CLASS definition issues in adodb.inc.php.
+- Added Reset() function to adodb_active_record. Thx marcus.
+- Minor dsn fix for pdo_sqlite in adodb.inc.php. Thx Sergey Chvalyuk.
+- Fixed adodb-datadict _CreateSuffix() inconsistencies. Thx Chris Miller.
+- Option to delete old fields $dropOldFlds in datadict ChangeTableSQL($table, $flds, $tableOptions, $dropOldFlds=false) added. Thx Philipp Niethammer.
+- Memcache caching did not expire properly. Fixed.
+- MetaForeignKeys for postgres7 driver changed from adodb_movenext to $rs->MoveNext (also in 4.99)
+- Added support for ldap and ldaps url format in ldap driver. E.g. ldap://host:port/dn?attributes?scope?filter?extensions
+
+## 5.07 - 26 Dec 2008
+
+- BeginTrans/CommitTrans/RollbackTrans return true/false correctly on success/failure now for mssql, odbc, oci8, mysqlt, mysqli, postgres, pdo.
+- Replace() now quotes all non-null values including numeric ones.
+- Postgresql qstr() now returns booleans as *true* and *false* without quotes.
+- MetaForeignKeys in mysql and mysqli drivers had this problem: A table can have two foreign keys pointing to the same column in the same table. The original code will incorrectly report only the last column. Fixed. https://sourceforge.net/p/adodb/bugs/100/
+- Passing in full ado connection string in $argHostname with ado drivers was failing in adodb5 due to bug. Fixed.
+- Fixed memcachelib flushcache and flushall bugs. Also fixed possible timeCreated = 0 problem in readcache. (Also in adodb 4.992). Thanks AlexB_UK (alexbarnes#hotmail.com).
+- Fixed a notice in adodb-sessions2.inc.php, in _conn(). Thx bober m.derlukiewicz#rocktech.remove_me.pl;
+- ADOdb Active Record: Fixed some issues with incompatible fetch modes (ADODB_FETCH_ASSOC) causing problems in UpdateActiveTable().
+- ADOdb Active Record: Added support for functions that support predefining one-to-many relationships:
+ _ClassHasMany ClassBelongsTo TableHasMany TableBelongsTo TableKeyHasMany TableKeyBelongsTo_.
+- You can also define your child/parent class in these functions, instead of the default ADODB_Active_Record. Thx Arialdo Martini & Chris R for idea.
+- ADOdb Active Record: HasMany hardcoded primary key to "id". Fixed.
+- Many pdo and pdo-sqlite fixes from Sid Dunayer [sdunayer#interserv.com].
+- CacheSelectLimit not working for mssql. Fixed. Thx AlexB.
+- The rs2html function did not display hours in timestamps correctly. Now 24hr clock used.
+- Changed ereg* functions to use preg* functions as ereg* is deprecated in PHP 5.3. Modified sybase and postgresql drivers.
+
+## 5.06 - 16 Oct 2008
+
+- Added driver adodb-pdo_sqlite.inc.php. Thanks Diogo Toscano (diogo#scriptcase.net) for the code.
+- Added support for [one-to-many relationships](docs-active-record.htm#onetomany) with BelongsTo() and HasMany() in adodb_active_record.
+- Added BINARY type to mysql.inc.php (also in 4.991).
+- Added support for SelectLimit($sql,-1,100) in oci8. (also in 4.991).
+- New $conn->GetMedian($table, $field, $where='') to get median account no. (also in 4.991)
+- The rs2html() function in tohtml.inc.php did not handle dates with ':' in it properly. Fixed. (also in 4.991)
+- Added support for connecting to oci8 using `$DB->Connect($ip, $user, $pwd, "SID=$sid");` (also in 4.991)
+- Added mysql type 'VAR_STRING' to MetaType(). (also in 4.991)
+- The session and session2 code supports setfetchmode assoc properly now (also in 4.991).
+- Added concat support to pdo. Thx Andrea Baron.
+- Changed db2 driver to use format `Y-m-d H-i-s` for datetime instead of `Y-m-d-H-i-s` which was legacy from odbc_db2 conversion.
+- Removed vestigal break on adodb_tz_offset in adodb-time.inc.php.
+- MetaForeignKeys did not work for views in MySQL 5. Fixed.
+- Changed error handling in GetActiveRecordsClass.
+- Added better support for using existing driver when $ADODB_NEWCONNECTION function returns false.
+- In _CreateSuffix in adodb-datadict.inc.php, adding unsigned variable for mysql.
+- In adodb-xmlschema03.inc.php, changed addTableOpt to include db name.
+- If bytea blob in postgresql is null, empty string was formerly returned. Now null is returned.
+- Changed db2 driver CreateSequence to support $start parameter.
+- rs2html() now does not add nbsp to end if length of string > 0
+- The oci8po FetchField() now only lowercases field names if ADODB_ASSOC_CASE is set to 0.
+- New mssqlnative drivers for php. TQ Garrett Serack of M'soft. [Download](http://www.microsoft.com/downloads/details.aspx?FamilyId=61BF87E0-D031-466B-B09A-6597C21A2E2A&displaylang=en) mssqlnative extension. Note that this is still in beta.
+- Fixed bugs in memcache support.
+- You can now change the return value of GetOne if no records are found using the global variable $ADODB_GETONE_EOF. The default is null. To change it back to the pre-4.99/5.00 behaviour of false, set $ADODB_GETONE_EOF = false;
+- In Postgresql 8.2/8.3 MetaForeignkeys did not work. Fixed William Kolodny William.Kolodny#gt-t.net
+
+## 5.05 - 11 Jul 2008
+
+Released together with [v4.990](changelog_v4.x.md#4990---11-jul-2008)
+
+- Added support for multiple recordsets in mysqli , thanks to Geisel Sierote geisel#4up.com.br. See http://phplens.com/lens/lensforum/msgs.php?id=15917
+- Malcolm Cook added new Reload() function to Active Record. See http://phplens.com/lens/lensforum/msgs.php?id=17474
+- Thanks Zoltan Monori (monzol#fotoprizma.hu) for bug fixes in iterator, SelectLimit, GetRandRow, etc.
+- Under heavy loads, the performance monitor for oci8 disables Ixora views.
+- Fixed sybase driver SQLDate to use str_replace(). Also for adodb5, changed sybase driver UnixDate and UnixTimeStamp calls to static.
+- Changed oci8 lob handler to use & reference `$this->_refLOBs[$numlob]['VAR'] = &$var`.
+- We now strtolower the get_class() function in PEAR::isError() for php5 compat.
+- CacheExecute did not retrieve cache recordsets properly for 5.04 (worked in 4.98). Fixed.
+- New ADODB_Cache_File class for file caching defined in adodb.inc.php.
+- Farsi language file contribution by Peyman Hooshmandi Raad (phooshmand#gmail.com)
+- New API for creating your custom caching class which is stored in $ADODB_CACHE:
+
+ ``` php
+ include "/path/to/adodb.inc.php";
+ $ADODB_CACHE_CLASS = 'MyCacheClass';
+ class MyCacheClass extends ADODB_Cache_File
+ {
+ function writecache($filename, $contents,$debug=false) {...}
+ function &readcache($filename, &$err, $secs2cache, $rsClass) { ...}
+ :
+ }
+ $DB = NewADOConnection($driver);
+ $DB->Connect(...); ## MyCacheClass created here and stored in $ADODB_CACHE global variable.
+ $data = $rs->CacheGetOne($sql); ## MyCacheClass is used here for caching...
+ ```
+
+- Memcache supports multiple pooled hosts now. Only if none of the pooled servers
+ can be contacted will a connect error be generated. Usage example below:
+
+ ``` php
+ $db = NewADOConnection($driver);
+ $db->memCache = true; /// should we use memCache instead of caching in files
+ $db->memCacheHost = array($ip1, $ip2, $ip3); /// $db->memCacheHost = $ip1; still works
+ $db->memCachePort = 11211; /// this is default memCache port
+ $db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+ $db->Connect(...);
+ $db->CacheExecute($sql);
+ ```
+
+## 5.04 - 13 Feb 2008
+
+Released together with [v4.98](changelog_v4.x.md#498---13-feb-2008)
+
+- Fixed adodb_mktime problem which causes a performance bottleneck in $hrs.
+- Added mysqli support to adodb_getcount().
+- Removed MYSQLI_TYPE_CHAR from MetaType().
+
+## 5.03 - 22 Jan 2008
+
+Released together with [v4.97](changelog_v4.x.md#497---22-jan-2008)
+
+- Active Record: $ADODB_ASSOC_CASE=1 did not work properly. Fixed.
+- Modified Fields() in recordset class to support display null fields in FetchNextObject().
+- In ADOdb5, active record implementation, we now support column names with spaces in them - we autoconvert the spaces to _ using __set(). Thx Daniel Cook. http://phplens.com/lens/lensforum/msgs.php?id=17200
+- Removed $arg3 from mysqli SelectLimit. See http://phplens.com/lens/lensforum/msgs.php?id=16243. Thx Zsolt Szeberenyi.
+- Changed oci8 FetchField, which returns the max_length of BLOB/CLOB/NCLOB as 4000 (incorrectly) to -1.
+- CacheExecute would sometimes return an error on Windows if it was unable to lock the cache file. This is harmless and has been changed to a warning that can be ignored. Also adodb_write_file() code revised.
+- ADOdb perf code changed to only log sql if execution time >= 0.05 seconds. New $ADODB_PERF_MIN variable holds min sql timing. Any SQL with timing value below this and is not causing an error is not logged.
+- Also adodb_backtrace() now traces 1 level deeper as sometimes actual culprit function is not displayed.
+- Fixed a group by problem with adodb_getcount() for db's which are not postgres/oci8 based.
+- Changed mssql driver Parameter() from SQLCHAR to SQLVARCHAR: case 'string': $type = SQLVARCHAR; break.
+- Problem with mssql driver in php5 (for adodb 5.03) because some functions are not static. Fixed.
+
+## 5.02 - 24 Sept 2007
+
+Released together with [v4.96](changelog_v4.x.md#496---24-sept-2007)
+
+- ADOdb perf for oci8 now has non-table-locking code when clearing the sql. Slower but better transparency. Added in 4.96a and 5.02a.
+- Fix adodb count optimisation. Preg_match did not work properly. Also rewrote the ORDER BY stripping code in _adodb_getcount(), adodb-lib.inc.php.
+- SelectLimit for oci8 not optimal for large recordsets when offset=0. Changed $nrows check.
+- Active record optimizations. Added support for assoc arrays in Set().
+- Now GetOne returns null if EOF (no records found), and false if error occurs. Use ErrorMsg()/ErrorNo() to get the error.
+- Also CacheGetRow and CacheGetCol will return false if error occurs, or empty array() if EOF, just like GetRow and GetCol.
+- Datadict now allows changing of types which are not resizable, eg. VARCHAR to TEXT in ChangeTableSQL. -- Mateo Tibaquirá
+- Added BIT data type support to adodb-ado.inc.php and adodb-ado5.inc.php.
+- Ldap driver did not return actual ldap error messages. Fixed.
+- Implemented GetRandRow($sql, $inputarr). Optimized for Oci8.
+- Changed adodb5 active record to use static SetDatabaseAdapter() and removed php4 constructor. Bas van Beek bas.vanbeek#gmail.com.
+- Also in adodb5, changed adodb-session2 to use static function declarations in class. Thx Daniel Berlin.
+- Added "Clear SQL Log" to bottom of Performance screen.
+- Sessions2 code echo'ed directly to the screen in debug mode. Now uses ADOConnection::outp().
+- In mysql/mysqli, qstr(null) will return the string `null` instead of empty quoted string `''`.
+- postgresql optimizeTable in perf-postgres.inc.php added by Daniel Berlin (mail#daniel-berlin.de)
+- Added 5.2.1 compat code for oci8.
+- Changed @@identity to SCOPE_IDENTITY() for multiple mssql drivers. Thx Stefano Nari.
+- Code sanitization introduced in 4.95 caused problems in European locales (as float 3.2 was typecast to 3,2). Now we only sanitize if is_numeric fails.
+- Added support for customizing ADORecordset_empty using $this->rsPrefix.'empty'. By Josh Truwin.
+- Added proper support for ALterColumnSQL for Postgresql in datadict code. Thx. Josh Truwin.
+- Added better support for MetaType() in mysqli when using an array recordset.
+- Changed parser for pgsql error messages in adodb-error.inc.php to case-insensitive regex.
+
+## 5.01 - 17 May 2007
+
+Released together with [v4.95](changelog_v4.x.md#495---17-may-2007)
+
+- CacheFlush debug outp() passed in invalid parameters. Fixed.
+- Added Thai language file for adodb. Thx Trirat Petchsingh rosskouk#gmail.com and Marcos Pont
+- Added zerofill checking support to MetaColumns for mysql and mysqli.
+- CacheFlush no longer deletes all files/directories. Only *.cache files deleted.
+- DB2 timestamp format changed to `var $fmtTimeStamp = "'Y-m-d-H:i:s'";`
+- Added some code sanitization to AutoExecute in adodb-lib.inc.php.
+- Due to typo, all connections in adodb-oracle.inc.php would become persistent, even non-persistent ones. Fixed.
+- Oci8 DBTimeStamp uses 24 hour time for input now, so you can perform string comparisons between 2 DBTimeStamp values.
+- Some PHP4.4 compat issues fixed in adodb-session2.inc.php
+- For ADOdb 5.01, fixed some adodb-datadict.inc.php MetaType compat issues with PHP5.
+- The $argHostname was wiped out in adodb-ado5.inc.php. Fixed.
+- Adodb5 version, added iterator support for adodb_recordset_empty.
+- Adodb5 version,more error checking code now will use exceptions if available.
diff --git a/vendor/adodb/adodb-php/docs/changelog_v2.x.md b/vendor/adodb/adodb-php/docs/changelog_v2.x.md
new file mode 100644
index 0000000..21db47b
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/changelog_v2.x.md
@@ -0,0 +1,531 @@
+# ADOdb old Changelog - v2.x and older
+
+See the [Current Changelog](changelog.md).
+
+
+## 2.91 - 3 Jan 2003
+
+- Revised PHP version checking to use $ADODB_PHPVER with legal values 0x4000, 0x4050, 0x4200, 0x4300.
+- Added support for bytea fields and oid blobs in postgres by allowing BlobDecode() to detect and convert non-oid fields. Also added BlobEncode to postgres when you want to encode oid blobs.
+- Added blobEncodeType property for connections to inform phpLens what encoding method to use for blobs.
+- Added BlobDecode() and BlobEncode() to base ADOConnection class.
+- Added umask() to _gencachename() when creating directories.
+- Added charPage for ado drivers, so you can set the code page.
+ ```
+$conn->charPage = CP_UTF8;
+$conn->Connect($dsn);
+ ```
+- Modified _seek in mysql to check for num rows=0.
+- Added to metatypes new informix types for IDS 9.30\. Thx Fernando Ortiz.
+- _maxrecordcount returned in CachePageExecute $rsreturn
+- Fixed sybase cacheselectlimit( ) problems
+- MetaColumns() max_length should use precision for types X and C for ms access. Fixed.
+- Speedup of odbc non-SELECT sql statements.
+- Added support in MetaColumns for Wide Char types for ODBC. We halve max_length if unicode/wide char.
+- Added 'B' to types handled by GetUpdateSQL/GetInsertSQL.
+- Fixed warning message in oci8 driver with $persist variable when using PConnect.
+
+## 2.90 - 11 Dec 2002
+
+- Mssql and mssqlpo and oci8po now support ADODB_ASSOC_CASE.
+- Now MetaType() can accept a field object as the first parameter.
+- New $arr = $db->ServerInfo( ) function. Returns $arr['description'] which is the string description, and $arr['version'].
+- PostgreSQL and MSSQL speedups for insert/updates.
+- Implemented new SetFetchMode() that removes the need to use $ADODB_FETCH_MODE. Each connection has independant fetchMode.
+- ADODB_ASSOC_CASE now defaults to 2, use native defaults. This is because we would break backward compat for too many applications otherwise.
+- Patched encrypted sessions to use replace()
+- The qstr function supports quoting of nulls when escape character is \
+- Rewrote bits and pieces of session code to check for time synch and improve reliability.
+- Added property ADOConnection::hasTransactions = true/false;
+- Added CreateSequence and DropSequence functions
+- Found misplaced MoveNext() in adodb-postgres.inc.php. Fixed.
+- Sybase SelectLimit not reliable because 'set rowcount' not cached - fixed.
+- Moved ADOConnection to adodb-connection.inc.php and ADORecordSet to adodb-recordset.inc.php. This allows us to use doxygen to generate documentation. Doxygen doesn't like the classes in the main adodb.inc.php file for some mysterious reason.
+
+## 2.50 - 14 Nov 2002
+
+- Added transOff and transCnt properties for disabling (transOff = true) and tracking transaction status (transCnt>0).
+- Added inputarray handling into _adodb_pageexecute_all_rows - "Ross Smith" RossSmith#bnw.com.
+- Fixed postgresql inconsistencies in date handling.
+- Added support for mssql_fetch_assoc.
+- Fixed $ADODB_FETCH_MODE bug in odbc MetaTables() and MetaPrimaryKeys().
+- Accidentally declared UnixDate() twice, making adodb incompatible with php 4.3.0\. Fixed.
+- Fixed pager problems with some databases that returned -1 for _currentRow on MoveLast() by switching to MoveNext() in adodb-lib.inc.php.
+- Also fixed uninited $discard in adodb-lib.inc.php.
+
+## 2.43 - 25 Oct 2002
+
+- Added ADODB_ASSOC_CASE constant to better support ibase and odbc field names.
+- Added support for NConnect() for oracle OCINLogin.
+- Fixed NumCols() bug.
+- Changed session handler to use Replace() on write.
+- Fixed oci8 SelectLimit aggregate function bug again.
+- Rewrote pivoting code.
+
+## 2.42 - 4 Oct 2002
+
+- Fixed ibase_fetch() problem with nulls. Also interbase now does automatic blob decoding, and is backward compatible. Suggested by Heinz Hombergs heinz#hhombergs.de.
+- Fixed postgresql MoveNext() problems when called repeatedly after EOF. Also suggested by Heinz Hombergs.
+- PageExecute() does not rewrite queries if SELECT DISTINCT is used. Requested by hans#velum.net
+- Added additional fixes to oci8 SelectLimit handling with aggregate functions - thx to Christian Bugge for reporting the problem.
+
+## 2.41 - 2 Oct 2002
+
+- Fixed ADODB_COUNTRECS bug in odbc. Thx to Joshua Zoshi jzoshi#hotmail.com.
+- Increased buffers for adodb-csvlib.inc.php for extremely long sql from 8192 to 32000.
+- Revised pivottable.inc.php code. Added better support for aggregate fields.
+- Fixed mysql text/blob types problem in MetaTypes base class - thx to horacio degiorgi.
+- Added SQLDate($fmt,$date) function, which allows an sql date format string to be generated - useful for group by's.
+- Fixed bug in oci8 SelectLimit when offset>100.
+
+## 2.40 - 4 Sept 2002
+
+- Added new NLS_DATE_FORMAT property to oci8\. Suggested by Laurent NAVARRO ln#altidev.com
+- Now use bind parameters in oci8 selectlimit for better performance.
+- Fixed interbase replaceQuote for dialect != 1\. Thx to "BEGUIN Pierre-Henri - INFOCOB" phb#infocob.com.
+- Added white-space check to QA.
+- Changed unixtimestamp to support fractional seconds (we always round down/floor the seconds). Thanks to beezly#beezly.org.uk.
+- Now you can set the trigger_error type your own user-defined type in adodb-errorhandler.inc.php. Suggested by Claudio Bustos clbustos#entelchile.net.
+- Added recordset filters with rsfilter.inc.php.
+ $conn->_rs2rs does not create a new recordset when it detects it is of type array. Some trickery there as there seems to be a bug in Zend Engine
+- Added render_pagelinks to adodb-pager.inc.php. Code by "Pablo Costa" pablo#cbsp.com.br.
+- MetaType() speedup in adodb.inc.php by using hashing instead of switch. Best performance if constant arrays are supported, as they are in PHP5.
+- adodb-session.php now updates only the expiry date if the crc32 check indicates that the data has not been modified.
+
+## 2.31 - 20 Aug 2002
+
+- Made changes to pivottable.inc.php due to daniel lucuzaeu's suggestions (we sum the pivottable column if desired).
+- Fixed ErrorNo() in postgres so it does not depend on _errorMsg property.
+- Robert Tuttle added support for oracle cursors. See ExecuteCursor().
+- Fixed Replace() so it works with mysql when updating record where data has not changed. Reported by Cal Evans (cal#calevans.com).
+
+## 2.30 - 1 Aug 2002
+
+- Added pivottable.inc.php. Thanks to daniel.lucazeau#ajornet.com for the original concept.
+- Added ADOConnection::outp($msg,$newline) to output error and debugging messages. Now you can override this using the ADODB_OUTP constant and use your own output handler.
+- Changed == to === for 'null' comparison. Reported by ericquil#yahoo.com
+- Fixed mssql SelectLimit( ) bug when distinct used.
+
+## 2.30 - 1 Aug 2002
+
+- New GetCol() and CacheGetCol() from ross#bnw.com that returns the first field as a 1 dim array.
+- We have an empty recordset, but RecordCount() could return -1\. Fixed. Reported by "Jonathan Polansky" jonathan#polansky.com.
+- We now check for session variable changes using strlen($sessval).crc32($sessval). Formerly we only used crc32().
+- Informix SelectLimit() problem with $ADODB_COUNTRECS fixed.
+- Fixed informix SELECT FIRST x DISTINCT, and not SELECT DISTINCT FIRST x - reported by F Riosa
+- Now default adodb error handlers ignores error if @ used.
+- If you set $conn->autoRollback=true, we auto-rollback persistent connections for odbc, mysql, oci8, mssql. Default for autoRollback is false. No need to do so for postgres. As interbase requires a transaction id (what a flawed api), we don't do it for interbase.
+- Changed PageExecute() to use non-greedy preg_match when searching for "FROM" keyword.
+
+## 2.20 - 9 July 2002
+
+- Added CacheGetOne($secs2cache,$sql), CacheGetRow($secs2cache,$sql), CacheGetAll($secs2cache,$sql).
+- Added $conn->OffsetDate($dayFraction,$date=false) to generate sql that calcs date offsets. Useful for scheduling appointments.
+- Added connection properties: leftOuter, rightOuter that hold left and right outer join operators.
+- Added connection property: ansiOuter to indicate whether ansi outer joins supported.
+- New driver _mssqlpo_, the portable mssql driver, which converts string concat operator from || to +.
+- Fixed ms access bug - SelectLimit() did not support ties - fixed.
+- Karsten Kraus (Karsten.Kraus#web.de), contributed error-handling code to ADONewConnection. Unfortunately due to backward compat problems, had to rollback most of the changes.
+- Added new parameter to GetAssoc() to allow returning an array of key-value pairs, ignoring any additional columns in the recordset. Off by default.
+- Corrected mssql $conn->sysDate to return only date using convert().
+- CacheExecute() improved debugging output.
+- Changed rs2html() so newlines are converted to BR tags. Also optimized rs2html() based on feedback by "Jerry Workman" jerry#mtncad.com.
+- Added support for Replace() with Interbase, using DELETE and INSERT.
+- Some minor optimizations (mostly removing & references when passing arrays).
+- Changed GenID() to allows id's larger than the size of an integer.
+- Added force_session property to oci8 for better updateblob() support.
+- Fixed PageExecute() which did not work properly with sql containing GROUP BY.
+
+## 2.12 - 12 June 2002
+
+- Added toexport.inc.php to export recordsets in CSV and tab-delimited format.
+- CachePageExecute() does not work - fixed - thx John Huong.
+- Interbase aliases not set properly in FetchField() - fixed. Thx Stefan Goethals.
+- Added cache property to adodb pager class. The number of secs to cache recordsets.
+- SQL rewriting bug in pageexecute() due to skipping of newlines due to missing /s modifier. Fixed.
+- Max size of cached recordset due to a bug was 256000 bytes. Fixed.
+- Speedup of 1st invocation of CacheExecute() by tuning code.
+- We compare $rewritesql with $sql in pageexecute code in case of rewrite failure.
+
+## 2.11 - 7 June 2002
+
+- Fixed PageExecute() rewrite sql problem - COUNT(*) and ORDER BY don't go together with mssql, access and postgres. Thx to Alexander Zhukov alex#unipack.ru
+- DB2 support for CHARACTER type added - thx John Huong huongch#bigfoot.com
+- For ado, $argProvider not properly checked. Fixed - kalimero#ngi.it
+- Added $conn->Replace() function for update with automatic insert if the record does not exist. Supported by all databases except interbase.
+
+## 2.10 - 4 June 2002
+
+- Added uniqueSort property to indicate mssql ORDER BY cols must be unique.
+- Optimized session handler by crc32 the data. We only write if session data has changed.
+- adodb_sess_read in adodb-session.php now returns ''correctly - thanks to Jorma Tuomainen, webmaster#wizactive.com
+- Mssql driver did not throw EXECUTE errors correctly because ErrorMsg() and ErrorNo() called in wrong order. Pointed out by Alexios Fakos. Fixed.
+- Changed ado to use client cursors. This fixes BeginTran() problems with ado.
+- Added handling of timestamp type in ado.
+- Added to ado_mssql support for insert_id() and affected_rows().
+- Added support for mssql.datetimeconvert=0, available since php 4.2.0.
+- Made UnixDate() less strict, so that the time is ignored if present.
+- Changed quote() so that it checks for magic_quotes_gpc.
+- Changed maxblobsize for odbc to default to 64000.
+
+## 2.00 - 13 May 2002
+
+- Added drivers _informix72_ for pre-7.3 versions, and _oci805_ for oracle 8.0.5, and postgres64 for postgresql 6.4 and earlier. The postgres and postgres7 drivers are now identical.
+- Interbase now partially supports ADODB_FETCH_BOTH, by defaulting to ASSOC mode.
+- Proper support for blobs in mssql. Also revised blob support code is base class. Now UpdateBlobFile() calls UpdateBlob() for consistency.
+- Added support for changed odbc_fetch_into api in php 4.2.0 with $conn->_has_stupid_odbc_fetch_api_change.
+- Fixed spelling of tablock locking hint in GenID( ) for mssql.
+- Added RowLock( ) to several databases, including oci8, informix, sybase, etc. Fixed where error in mssql RowLock().
+- Added sysDate and sysTimeStamp properties to most database drivers. These are the sql functions/constants for that database that return the current date and current timestamp, and are useful for portable inserts and updates.
+- Support for RecordCount() caused date handling in sybase and mssql to break. Fixed, thanks to Toni Tunkkari, by creating derived classes for ADORecordSet_array for both databases. Generalized using arrayClass property. Also to support RecordCount(), changed metatype handling for ado drivers. Now the type returned in FetchField is no longer a number, but the 1-char data type returned by MetaType. At the same time, fixed a lot of date handling. Now mssql support dmy and mdy date formats. Also speedups in sybase and mssql with preg_match and ^ in date/timestamp handling. Added support in sybase and mssql for 24 hour clock in timestamps (no AM/PM).
+- Extensive revisions to informix driver - thanks to Samuel CARRIERE samuel_carriere#hotmail.com
+- Added $ok parameter to CommitTrans($ok) for easy rollbacks.
+- Fixed odbc MetaColumns and MetaTables to save and restore $ADODB_FETCH_MODE.
+- Some odbc drivers did not call the base connection class constructor. Fixed.
+- Fixed regex for GetUpdateSQL() and GetInsertSQL() to support more legal character combinations.
+
+## 1.99 - 21 April 2002
+
+- Added emulated RecordCount() to all database drivers if $ADODB_COUNTRECS = true (which it is by default). Inspired by Cristiano Duarte (cunha17#uol.com.br).
+- Unified stored procedure support for mssql and oci8\. Parameter() and PrepareSP() functions implemented.
+- Added support for SELECT FIRST in informix, modified hasTop property to support this.
+- Changed csv driver to handle updates/deletes/inserts properly (when Execute() returns true). Bind params also work now, and raiseErrorFn with csv driver. Added csv driver to QA process.
+- Better error checking in oci8 UpdateBlob() and UpdateBlobFile().
+- Added TIME type to MySQL - patch by Manfred h9125297#zechine.wu-wien.ac.at
+- Prepare/Execute implemented for Interbase/Firebird
+- Changed some regular expressions to be anchored by /^ $/ for speed.
+- Added UnixTimeStamp() and UnixDate() to ADOConnection(). Now these functions are in both ADOConnection and ADORecordSet classes.
+- Empty recordsets were not cached - fixed.
+- Thanks to Gaetano Giunta (g.giunta#libero.it) for the oci8 code review. We didn't agree on everything, but i hoped we agreed to disagree!
+
+## 1.90 - 6 April 2002
+
+- Now all database drivers support fetch modes ADODB_FETCH_NUM and ADODB_FETCH_ASSOC, though still not fully tested. Eg. Frontbase, Sybase, Informix.
+- NextRecordSet() support for mssql. Contributed by "Sven Axelsson" sven.axelsson#bokochwebb.se
+- Added blob support for SQL Anywhere. Contributed by Wade Johnson wade#wadejohnson.de
+- Fixed some security loopholes in server.php. Server.php also supports fetch mode.
+- Generalized GenID() to support odbc and mssql drivers. Mssql no longer generates GUID's.
+- Experimental RowLock($table,$where) for mssql.
+- Properly implemented Prepare() in oci8 and ODBC.
+- Added Bind() support to oci8 to support Prepare().
+- Improved error handler. Catches CacheExecute() and GenID() errors now.
+- Now if you are running php from the command line, debugging messages do not output html formating. Not 100% complete, but getting there.
+
+## 1.81 - 22 March 2002
+
+- Restored default $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT for backward compatibility.
+- SelectLimit for oci8 improved - Our FIRST_ROWS optimization now does not overwrite existing hint.
+- New Sybase SQL Anywhere driver. Contributed by Wade Johnson wade#wadejohnson.de
+
+## 1.80 - 15 March 2002
+
+- Redesigned directory structure of ADOdb files. Added new driver directory where all database drivers reside.
+- Changed caching algorithm to create subdirectories. Now we scale better.
+- Informix driver now supports insert_id(). Contribution by "Andrea Pinnisi" pinnisi#sysnet.it
+- Added experimental ISO date and FetchField support for informix.
+- Fixed a quoting bug in Execute() with bind parameters, causing problems with blobs.
+- Mssql driver speedup by 10-15%.
+- Now in CacheExecute($secs2cache,$sql,...), $secs2cache is optional. If missing, it will take the value defined in $connection->cacheSecs (default is 3600 seconds). Note that CacheSelectLimit(), the secs2cache is still compulsory - sigh.
+- Sybase SQL Anywhere driver (using ODBC) contributed by Wade Johnson wade#wadejohnson.de
+
+## 1.72 - 8 March 2002
+
+- Added @ when returning Fields() to prevent spurious error - "Michael William Miller" mille562#pilot.msu.edu
+- MetaDatabases() for postgres contributed by Phil pamelant#nerim.net
+- Mitchell T. Young (mitch#youngfamily.org) contributed informix driver.
+- Fixed rs2html() problem. I cannot reproduce, so probably a problem with pre PHP 4.1.0 versions, when supporting new ADODB_FETCH_MODEs.
+- Mattia Rossi (mattia#technologist.com) contributed BlobDecode() and UpdateBlobFile() for postgresql using the postgres specific pg_lo_import()/pg_lo_open() - i don't use them but hopefully others will find this useful. See [this posting](http://phplens.com/lens/lensforum/msgs.php?id=1262) for an example of usage.
+- Added UpdateBlobFile() for uploading files to a database.
+- Made UpdateBlob() compatible with oci8po driver.
+- Added noNullStrings support to oci8 driver. Oracle changes all ' ' strings to nulls, so you need to set strings to ' ' to prevent the nullifying of strings. $conn->noNullStrings = true; will do this for you automatically. This is useful when you define a char column as NOT NULL.
+- Fixed UnixTimeStamp() bug - wasn't setting minutes and seconds properly. Patch from Agusti Fita i Borrell agusti#anglatecnic.com.
+- Toni Tunkkari added patch for sybase dates. Problem with spaces in day part of date fixed.
+
+## 1.71 - 18 Jan 2002
+
+- Sequence start id support. Now $conn->Gen_ID('seqname', 50) to start sequence from 50.
+- CSV driver fix for selectlimit, from Andreas - akaiser#vocote.de.
+- Gam3r spotted that a global variable was undefined in the session handler.
+- Mssql date regex had error. Fixed - reported by Minh Hoang vb_user#yahoo.com.
+- DBTimeStamp() and DBDate() now accept iso dates and unix timestamps. This means that the PostgreSQL handling of dates in GetInsertSQL() and GetUpdateSQL() can be removed. Also if these functions are passed '' or null or false, we return a SQL null.
+- GetInsertSQL() and GetUpdateSQL() now accept a new parameter, $magicq to indicate whether quotes should be inserted based on magic quote settings - suggested by dj#4ict.com.
+- Reformated docs slightly based on suggestions by Chris Small.
+
+## 1.65 - 28 Dec 2001
+
+- Fixed borland_ibase class naming bug.
+- Now instead of using $rs->fields[0] internally, we use reset($rs->fields) so that we are compatible with ADODB_FETCH_ASSOC mode. Reported by Nico S.
+- Changed recordset constructor and _initrs() for oci8 so that it returns the field definitions even if no rows in the recordset. Reported by Rick Hickerson (rhickers#mv.mv.com).
+- Improved support for postgresql in GetInsertSQL and GetUpdateSQL by "mike" mike#partner2partner.com and "Ryan Bailey" rebel#windriders.com
+
+## 1.64 - 20 Dec 2001
+
+- Danny Milosavljevic <danny.milo#gmx.net> added some patches for MySQL error handling and displaying default values.
+- Fixed some ADODB_FETCH_BOTH inconsistencies in odbc and interbase.
+- Added more tests to test suite to cover ADODB_FETCH_* and ADODB_ERROR_HANDLER.
+- Added firebird (ibase) driver
+- Added borland_ibase driver for interbase 6.5
+
+## 1.63 - 13 Dec 2001
+
+- Absolute to the adodb-lib.inc.php file not set properly. Fixed.
+
+## 1.62 - 11 Dec 2001
+
+- Major speedup of ADOdb for low-end web sites by reducing the php code loading and compiling cycle. We conditionally compile not so common functions. Moved csv code to adodb-csvlib.inc.php to reduce adodb.inc.php parsing. This file is loaded only when the csv/proxy driver is used, or CacheExecute() is run. Also moved PageExecute(), GetSelectSQL() and GetUpdateSQL() core code to adodb-lib.inc.php. This reduced the 70K main adodb.inc.php file to 55K, and since at least 20K of the file is comments, we have reduced 50K of code in adodb.inc.php to 35K. There should be 35% reduction in memory and thus 35% speedup in compiling the php code for the main adodb.inc.php file.
+- Highly tuned SelectLimit() for oci8 for massive speed improvements on large files. Selecting 20 rows starting from the 20,000th row of a table is now 7 times faster. Thx to Tomas V V Cox.
+- Allow . and # in table definitions in GetInsertSQL and GetUpdateSQL. See ADODB_TABLE_REGEX constant. Thx to Ari Kuorikoski.
+- Added ADODB_PREFETCH_ROWS constant, defaulting to 10\. This determines the number of records to prefetch in a SELECT statement. Only used by oci8.
+- Added high portability Oracle class called oci8po. This uses ? for bind variables, and lower cases column names.
+- Now all database drivers support $ADODB_FETCH_MODE, including interbase, ado, and odbc: ADODB_FETCH_NUM and ADODB_FETCH_ASSOC. ADODB_FETCH_BOTH is not fully implemented for all database drivers.
+
+## 1.61 - Nov 2001
+- Added PO_RecordCount() and PO_Insert_ID(). PO stands for portable. Pablo Roca [pabloroca#mvps.org]
+- GenID now returns 0 if not available. Safer is that you should check $conn->hasGenID for availability.
+- M'soft ADO we now correctly close recordset in _close() peterd#telephonetics.co.uk
+- MSSQL now supports GenID(). It generates a 16-byte GUID from mssql newid() function.
+- Changed ereg_replace to preg_replace in SelectLimit. This is a fix for mssql. Ereg doesn't support t or n! Reported by marino Carlos xaplo#postnuke-espanol.org
+- Added $recordset->connection. This is the ADOConnection object for the recordset. Works with cached and normal recordsets. Surprisingly, this had no affect on performance!
+
+## 1.54 - 15 Nov 2001
+
+- Fixed some more bugs in PageExecute(). I am getting sick of bug in this and will have to reconsider my QA here. The main issue is that I don't use PageExecute() and to check whether it is working requires a visual inspection of the html generated currently. It is possible to write a test script but it would be quite complicated :(
+- More speedups of SelectLimit() for DB2, Oci8, access, vfp, mssql.
+
+## 1.53 - 7 Nov 2001
+
+- Added support for ADODB_FETCH_ASSOC for ado and odbc drivers.
+- Tuned GetRowAssoc(false) in postgresql and mysql.
+- Stephen Van Dyke contributed ADOdb icon, accepted with some minor mods.
+- Enabled Affected_Rows() for postgresql
+- Speedup for Concat() using implode() - Benjamin Curtis ben_curtis#yahoo.com
+- Fixed some more bugs in PageExecute() to prevent infinite loops
+
+## 1.52 - 5 Nov 2001
+
+- Spelling error in CacheExecute() caused it to fail. $ql should be $sql in line 625!
+- Added fixes for parsing [ and ] in GetUpdateSQL().
+
+## 1.51 - 5 Nov 2001
+
+- Oci8 SelectLimit() speedup by using OCIFetch().
+- Oci8 was mistakenly reporting errors when $db->debug = true.
+- If a connection failed with ODBC, it was not correctly reported - fixed.
+- _connectionID was inited to -1, changed to false.
+- Added $rs->FetchRow(), to simplify API, ala PEAR DB
+- Added PEAR DB compat mode, which is still faster than PEAR! See adodb-pear.inc.php.
+- Removed postgres pconnect debugging statement.
+
+## 1.50 - 31 Oct 2001
+
+- ADOdbConnection renamed to ADOConnection, and ADOdbFieldObject to ADOFieldObject.
+- PageExecute() now checks for empty $rs correctly, and the errors in the docs on this subject have been fixed.
+- odbc_error() does not return 6 digit error correctly at times. Implemented workaround.
+- Added ADORecordSet_empty class. This will speedup INSERTS/DELETES/UPDATES because the return object created is much smaller.
+- Added Prepare() to odbc, and oci8 (but doesn't work properly for oci8 still).
+- Made pgsql a synonym for postgre7, and changed SELECT LIMIT to use OFFSET for compat with postgres 7.2.
+- Revised adodb-cryptsession.php thanks to Ari.
+- Set resources to false on _close, to force freeing of resources.
+- Added adodb-errorhandler.inc.php, adodb-errorpear.inc.php and raiseErrorFn on Freek's urging.
+- GetRowAssoc($toUpper=true): $toUpper added as default.
+- Errors when connecting to a database were not captured formerly. Now we do it correctly.
+
+## 1.40 - 19 September 2001
+
+- PageExecute() to implement page scrolling added. Code and idea by Iván Oliva.
+- Some minor postgresql fixes.
+- Added sequence support using GenID() for postgresql, oci8, mysql, interbase.
+- Added UpdateBlob support for interbase (untested).
+- Added encrypted sessions (see adodb-cryptsession.php). By Ari Kuorikoski <kuoriari#finebyte.com>
+
+## 1.31 - 21 August 2001
+
+- Many bug fixes thanks to "GaM3R (Cameron)" <gamr#outworld.cx>. Some session changes due to Gam3r.
+- Fixed qstr() to quote also.
+- rs2html() now pretty printed.
+- Jonathan Younger <jyounger#unilab.com> contributed the great idea GetUpdateSQL() and GetInsertSQL() which generates SQL to update and insert into a table from a recordset. Modify the recordset fields array, then can this function to generate the SQL (the SQL is not executed).
+- Nicola Fankhauser <nicola.fankhauser#couniq.com> found some bugs in date handling for mssql.
+- Added minimal Oracle support for LOBs. Still under development.
+- Added $ADODB_FETCH_MODE so you can control whether recordsets return arrays which are numeric, associative or both. This is a global variable you set. Currently only MySQL, Oci8, Postgres drivers support this.
+- PostgreSQL properly closes recordsets now. Reported by several people.
+- Added UpdateBlob() for Oracle. A hack to make it easier to save blobs.
+- Oracle timestamps did not display properly. Fixed.
+
+## 1.20 - 6 June 2001
+
+- Now Oracle can connect using tnsnames.ora or server and service name
+- Extensive Oci8 speed optimizations. Oci8 code revised to support variable binding, and /*+ FIRST_ROWS */ hint.
+- Worked around some 4.0.6 bugs in odbc_fetch_into().
+- Paolo S. Asioli paolo.asioli#libero.it suggested GetRowAssoc().
+- Escape quotes for oracle wrongly set to '. Now '' is used.
+- Variable binding now works in ODBC also.
+- Jumped to version 1.20 because I don't like 13 :-)
+
+## 1.12 - 6 June 2001
+
+- Changed $ADODB_DIR to ADODB_DIR constant to plug a security loophole.
+- Changed _close() to close persistent connections also. Prevents connection leaks.
+- Major revision of oracle and oci8 drivers. Added OCI_RETURN_NULLS and OCI_RETURN_LOBS to OCIFetchInto(). BLOB, CLOB and VARCHAR2 recognition in MetaType() improved. MetaColumns() returns columns in correct sort order.
+- Interbase timestamp input format was wrong. Fixed.
+
+## 1.11 - 20 May 2001
+
+- Improved file locking for Windows.
+- Probabilistic flushing of cache to avoid avalanche updates when cache timeouts.
+- Cached recordset timestamp not saved in some scenarios. Fixed.
+
+## 1.10 - 19 May 2001
+
+- Added caching. CacheExecute() and CacheSelectLimit().
+- Added csv driver.
+- Fixed SelectLimit(), SELECT TOP not working under certain circumstances.
+- Added better Frontbase support of MetaTypes() by Frank M. Kromann.
+
+## 1.01 - 24 April 2001
+
+- Fixed SelectLimit bug. not quoted properly.
+- SelectLimit: SELECT TOP -1 * FROM TABLE not support by Microsoft. Fixed.
+- GetMenu improved by glen.davies#cce.ac.nz to support multiple hilited items
+- FetchNextObject() did not work with only 1 record returned. Fixed bug reported by $tim#orotech.net
+- Fixed mysql field max_length problem. Fix suggested by Jim Nicholson (jnich#att.com)
+
+## 1.00 - 16 April 2001
+
+- Given some brilliant suggestions on how to simplify ADOdb by akul. You no longer need to setup $ADODB_DIR yourself, and ADOLoadCode() is automatically called by ADONewConnection(), simplifying the startup code.
+- FetchNextObject() added. Suggested by Jakub Marecek. This makes FetchObject() obsolete, as this is more flexible and powerful.
+- Misc fixes to SelectLimit() to support Access (top must follow distinct) and Fields() in the array recordset. From Reinhard Balling.
+
+## 0.96 - 27 Mar 2001
+
+- ADOConnection Close() did not return a value correctly. Thanks to akul#otamedia.com.
+- When the horrible magic_quotes is enabled, back-slash () is changed to double-backslash (\). This doesn't make sense for Microsoft/Sybase databases. We fix this in qstr().
+- Fixed Sybase date problem in UnixDate() thanks to Toni Tunkkari. Also fixed MSSQL problem in UnixDate() - thanks to milhouse31#hotmail.com.
+- MoveNext() moved to leaf classes for speed in MySQL/PostgreSQL. 10-15% speedup.
+- Added null handling in bindInputArray in Execute() -- Ron Baldwin suggestion.
+- Fixed some option tags. Thanks to john#jrmstudios.com.
+
+## 0.95 - 13 Mar 2001
+
+- Added postgres7 database driver which supports LIMIT and other version 7 stuff in the future.
+- Added SelectLimit to ADOConnection to simulate PostgreSQL's "select * from table limit 10 offset 3". Added helper function GetArrayLimit() to ADORecordSet.
+- Fixed mysql metacolumns bug. Thanks to Freek Dijkstra (phpeverywhere#macfreek.com).
+- Also many PostgreSQL changes by Freek. He almost rewrote the whole PostgreSQL driver!
+- Added fix to input parameters in Execute for non-strings by Ron Baldwin.
+- Added new metatype, X for TeXt. Formerly, metatype B for Blob also included text fields. Now 'B' is for binary/image data. 'X' for textual data.
+- Fixed $this->GetArray() in GetRows().
+- Oracle and OCI8: 1st parameter is always blank -- now warns if it is filled.
+- Now _hasLimit_ and _hasTop_ added to indicate whether SELECT * FROM TABLE LIMIT 10 or SELECT TOP 10 * FROM TABLE are supported.
+
+## 0.94 - 04 Feb 2001
+
+- Added ADORecordSet::GetRows() for compatibility with Microsoft ADO. Synonym for GetArray().
+- Added new metatype 'R' to represent autoincrement numbers.
+- Added ADORecordSet.FetchObject() to return a row as an object.
+- Finally got a Linux box to test PostgreSql. Many fixes.
+- Fixed copyright misspellings in 0.93.
+- Fixed mssql MetaColumns type bug.
+- Worked around odbc bug in PHP4 for sessions.
+- Fixed many documentation bugs (affected_rows, metadatabases, qstr).
+- Fixed MySQL timestamp format (removed comma).
+- Interbase driver did not call ibase_pconnect(). Fixed.
+
+## 0.93 - 18 Jan 2002
+
+- Fixed GetMenu bug.
+- Simplified Interbase commit and rollback.
+- Default behaviour on closing a connection is now to rollback all active transactions.
+- Added field object handling for array recordset for future XML compatibility.
+- Added arr2html() to convert array to html table.
+
+## 0.92 - 2 Jan 2002
+
+- Interbase Commit and Rollback should be working again.
+- Changed initialisation of ADORecordSet. This is internal and should not affect users. We are doing this to support cached recordsets in the future.
+- Implemented ADORecordSet_array class. This allows you to simulate a database recordset with an array.
+- Added UnixDate() and UnixTimeStamp() to ADORecordSet.
+
+## 0.91 - 21 Dec 2000
+
+- Fixed ODBC so ErrorMsg() is working.
+- Worked around ADO unrecognised null (0x1) value problem in COM.
+- Added Sybase support for FetchField() type
+- Removed debugging code and unneeded html from various files
+- Changed to javadoc style comments to adodb.inc.php.
+- Added maxsql as synonym for mysqlt
+- Now ODBC downloads first 8K of blob by default
+
+## 0.90 - 15 Nov 2000
+
+- Lots of testing of Microsoft ADO. Should be more stable now.
+- Added $ADODB_COUNTREC. Set to false for high speed selects.
+- Added Sybase support. Contributed by Toni Tunkkari (toni.tunkkari#finebyte.com). Bug in Sybase API: GetFields is unable to determine date types.
+- Changed behaviour of RecordSet.GetMenu() to support size parameter (listbox) properly.
+- Added emptyDate and emptyTimeStamp to RecordSet class that defines how to represent empty dates.
+- Added MetaColumns($table) that returns an array of ADOFieldObject's listing the columns of a table.
+- Added transaction support for PostgresSQL -- thanks to "Eric G. Werk" egw#netguide.dk.
+- Added adodb-session.php for session support.
+
+## 0.80 - 30 Nov 2000
+
+- Added support for charSet for interbase. Implemented MetaTables for most databases. PostgreSQL more extensively tested.
+
+## 0.71 - 22 Nov 2000
+
+- Switched from using require_once to include/include_once for backward compatability with PHP 4.02 and earlier.
+
+## 0.70 - 15 Nov 2000
+
+- Calls by reference have been removed (call_time_pass_reference=Off) to ensure compatibility with future versions of PHP, except in Oracle 7 driver due to a bug in php_oracle.dll.
+- PostgreSQL database driver contributed by Alberto Cerezal (acerezalp#dbnet.es).
+- Oci8 driver for Oracle 8 contributed by George Fourlanos (fou#infomap.gr).
+- Added _mysqlt_ database driver to support MySQL 3.23 which has transaction support.
+- Oracle default date format (DD-MON-YY) did not match ADOdb default date format (which is YYYY-MM-DD). Use ALTER SESSION to force the default date.
+- Error message checking is now included in test suite.
+- MoveNext() did not check EOF properly -- fixed.
+
+## 0.60 - 8 Nov 2000
+
+- Fixed some constructor bugs in ODBC and ADO. Added ErrorNo function to ADOConnection class.
+
+## 0.51 - 18 Oct 2000
+
+- Fixed some interbase bugs.
+
+## 0.50 - 16 Oct 2000
+
+- Interbase commit/rollback changed to be compatible with PHP 4.03\.
+- CommitTrans( ) will now return true if transactions not supported.
+- Conversely RollbackTrans( ) will return false if transactions not supported.
+
+## 0.46 - 12 Oct 2000
+
+- Many Oracle compatibility issues fixed.
+
+## 0.40 - 26 Sept 2000
+
+- Many bug fixes
+- Now Code for BeginTrans, CommitTrans and RollbackTrans is working. So is the Affected_Rows and Insert_ID. Added above functions to test.php.
+- ADO type handling was busted in 0.30\. Fixed.
+- Generalised Move( ) so it works will all databases, including ODBC.
+
+## 0.30 - 18 Sept 2000
+
+- Renamed ADOLoadDB to ADOLoadCode. This is clearer.
+- Added BeginTrans, CommitTrans and RollbackTrans functions.
+- Added Affected_Rows() and Insert_ID(), _affectedrows() and _insertID(), ListTables(), ListDatabases(), ListColumns().
+- Need to add New_ID() and hasInsertID and hasAffectedRows, autoCommit
+
+## 0.20 - 12 Sept 2000
+
+- Added support for Microsoft's ADO.
+- Added new field to ADORecordSet -- canSeek
+- Added new parameter to _fetch($ignore_fields = false). Setting to true will not update fields array for faster performance.
+- Added new field to ADORecordSet/ADOConnection -- dataProvider to indicate whether a class is derived from odbc or ado.
+- Changed class ODBCFieldObject to ADOFieldObject -- not documented currently.
+- Added benchmark.php and testdatabases.inc.php to the test suite.
+- Added to ADORecordSet FastForward( ) for future high speed scrolling. Not documented.
+- Realised that ADO's Move( ) uses relative positioning. ADOdb uses absolute.
+
+## 0.10 - 9 Sept 2000
+
+- First release
diff --git a/vendor/adodb/adodb-php/docs/changelog_v3.x.md b/vendor/adodb/adodb-php/docs/changelog_v3.x.md
new file mode 100644
index 0000000..ee2bded
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/changelog_v3.x.md
@@ -0,0 +1,242 @@
+# ADOdb old Changelog - v3.x
+
+See the [Current Changelog](changelog.md).
+Older changelogs:
+[v2.x](changelog_v2.x.md).
+
+
+## 3.94 - 11 Oct 2003
+
+- Create trigger in datadict-oci8.inc.php did not work, because all cr/lf's must be removed.
+- ErrorMsg()/ErrorNo() did not work for many databases when logging enabled. Fixed.
+- Removed global variable $ADODB_LOGSQL as it does not work properly with multiple connections.
+- Added SQLDate support for sybase. Thx to Chris Phillipson
+- Postgresql checking of pgsql resultset resource was incorrect. Fix by Bharat Mediratta bharat#menalto.com. Same patch applied to _insertid and _affectedrows for adodb-postgres64.inc.php.
+- Added support for NConnect for postgresql.
+- Added Sybase data dict support. Thx to Chris Phillipson
+- Extensive improvements in $perf->UI(), eg. Explain now opens in new window, we show scripts which call sql, etc.
+- Perf Monitor UI works with magic quotes enabled.
+- rsPrefix was declared twice. Removed.
+- Oci8 stored procedure support, eg. "begin func(); end;" was incorrect in _query. Fixed.
+- Tiraboschi Massimiliano contributed italian language file.
+- Fernando Ortiz, fortiz#lacorona.com.mx, contributed informix performance monitor.
+- Added _varchar (varchar arrays) support for postgresql. Reported by PREVOT Stéphane.
+
+
+## 3.92 - 22 Sept 2003
+
+- Added GetAssoc and CacheGetAssoc to connection object.
+- Removed TextMax and CharMax functions from adodb.inc.php.
+- HasFailedTrans() returned false when trans failed. Fixed.
+- Moved perf driver classes into adodb/perf/*.php.
+- Misc improvements to performance monitoring, including UI().
+- RETVAL in mssql Parameter(), we do not append @ now.
+- Added Param($name) to connection class, returns '?' or ":$name", for defining bind parameters portably.
+- LogSQL traps affected_rows() and saves its value properly now. Also fixed oci8 _stmt and _affectedrows() bugs.
+- Session code timestamp check for oci8 works now. Formerly default NLS_DATE_FORMAT stripped off time portion. Thx to Tony Blair (tonanbarbarian#hotmail.com). Also added new $conn->datetime field to oci8, controls whether MetaType() returns 'D' ($this->datetime==false) or 'T' ($this->datetime == true) for DATE type.
+- Fixed bugs in adodb-cryptsession.inc.php and adodb-session-clob.inc.php.
+- Fixed misc bugs in adodb_key_exists, GetInsertSQL() and GetUpdateSQL().
+- Tuned include_once handling to reduce file-system checking overhead.
+
+## 3.91 - 9 Sept 2003
+
+- Only released to InterAkt
+- Added LogSQL() for sql logging and $ADODB_NEWCONNECTION to override factory for driver instantiation.
+- Added IfNull($field,$ifNull) function, thx to johnwilk#juno.com
+- Added portable substr support.
+- Now rs2html() has new parameter, $echo. Set to false to return $html instead of echoing it.
+
+## 3.90 - 5 Sept 2003
+
+- First beta of performance monitoring released.
+- MySQL supports MetaTable() masking.
+- Fixed key_exists() bug in adodb-lib.inc.php
+- Added sp_executesql Prepare() support to mssql.
+- Added bind support to db2.
+- Added swedish language file - Christian Tiberg" christian#commsoft.nu
+- Bug in drop index for mssql data dict fixed. Thx to Gert-Rainer Bitterlich.
+- Left join setting for oci8 was wrong. Thx to johnwilk#juno.com
+
+## 3.80 - 27 Aug 2003
+
+- Patch for PHP 4.3.3 cached recordset csv2rs() fread loop incompatibility.
+- Added matching mask for MetaTables. Only for oci8, mssql and postgres currently.
+- Rewrite of "oracle" driver connection code, merging with "oci8", by Gaetano.
+- Added better debugging for Smart Transactions.
+- Postgres DBTimeStamp() was wrongly using TO_DATE. Changed to TO_TIMESTAMP.
+- ADODB_FETCH_CASE check pushed to ADONewConnection to allow people to define it after including adodb.inc.php.
+- Added portugese (brazilian) to languages. Thx to "Levi Fukumori".
+- Removed arg3 parameter from Execute/SelectLimit/Cache* functions.
+- Execute() now accepts 2-d array as $inputarray. Also changed docs of fnExecute() to note change in sql query counting with 2-d arrays.
+- Added MONEY to MetaType in PostgreSQL.
+- Added more debugging output to CacheFlush().
+
+## 3.72 - 9 Aug 2003
+
+- Added qmagic($str), which is a qstr($str) that auto-checks for magic quotes and does the right thing...
+- Fixed CacheFlush() bug - Thx to martin#gmx.de
+- Walt Boring contributed MetaForeignKeys for postgres7.
+- _fetch() called _BlobDecode() wrongly in interbase. Fixed.
+- adodb_time bug fixed with dates after 2038 fixed by Jason Pell. http://phplens.com/lens/lensforum/msgs.php?id=6980
+
+## 3.71 - 4 Aug 2003
+
+- The oci8 driver, MetaPrimaryKeys() did not check the owner correctly when $owner == false.
+- Russian language file contributed by "Cyrill Malevanov" cyrill#malevanov.spb.ru.
+- Spanish language file contributed by "Horacio Degiorgi" horaciod#codigophp.com.
+- Error handling in oci8 bugfix - if there was an error in Execute(), then when calling ErrorNo() and/or ErrorMsg(), the 1st call would return the error, but the 2nd call would return no error.
+- Error handling in odbc bugfix. ODBC would always return the last error, even if it happened 5 queries ago. Now we reset the errormsg to '' and errorno to 0 everytime before CacheExecute() and Execute().
+
+## 3.70 - 29 July 2003
+
+- Added new SQLite driver. Tested on PHP 4.3 and PHP 5.
+- Added limited "sapdb" driver support - mainly date support.
+- The oci8 driver did not identify NUMBER with no defined precision correctly.
+- Added ADODB_FORCE_NULLS, if set, then PHP nulls are converted to SQL nulls in GetInsertSQL/GetUpdateSQL.
+- DBDate() and DBTimeStamp() format for postgresql had problems. Fixed.
+- Added tableoptions to ChangeTableSQL(). Thx to Mike Benoit.
+- Added charset support to postgresql. Thx to Julian Tarkhanov.
+- Changed OS check for MS-Windows to prevent confusion with darWIN (MacOS)
+- Timestamp format for db2 was wrong. Changed to yyyy-mm-dd-hh.mm.ss.nnnnnn.
+- adodb-cryptsession.php includes wrong. Fixed.
+- Added MetaForeignKeys(). Supported by mssql, odbc_mssql and oci8.
+- Fixed some oci8 MetaColumns/MetaPrimaryKeys bugs. Thx to Walt Boring.
+- adodb_getcount() did not init qryRecs to 0\. Missing "WHERE" clause checking in GetUpdateSQL fixed. Thx to Sebastiaan van Stijn.
+- Added support for only 'VIEWS' and "TABLES" in MetaTables. From Walt Boring.
+- Upgraded to adodb-xmlschema.inc.php 0.0.2.
+- NConnect for mysql now returns value. Thx to Dennis Verspuij.
+- ADODB_FETCH_BOTH support added to interbase/firebird.
+- Czech language file contributed by Kamil Jakubovic jake#host.sk.
+- PostgreSQL BlobDecode did not use _connectionID properly. Thx to Juraj Chlebec.
+- Added some new initialization stuff for Informix. Thx to "Andrea Pinnisi" pinnisi#sysnet.it
+- ADODB_ASSOC_CASE constant wrong in sybase _fetch(). Fixed.
+
+## 3.60 - 16 June 2003
+
+- We now SET CONCAT_NULL_YIELDS_NULL OFF for odbc_mssql driver to be compat with mssql driver.
+- The property $emptyDate missing from connection class. Also changed 1903 to constant (TIMESTAMP_FIRST_YEAR=100). Thx to Sebastiaan van Stijn.
+- ADOdb speedup optimization - we now return all arrays by reference.
+- Now DBDate() and DBTimeStamp() now accepts the string 'null' as a parameter. Suggested by vincent.
+- Added GetArray() to connection class.
+- Added not_null check in informix metacolumns().
+- Connection parameters for postgresql did not work correctly when port was defined.
+- DB2 is now a tested driver, making adodb 100% compatible. Extensive changes to odbc driver for DB2, including implementing serverinfo() and SQLDate(), switching to SQL_CUR_USE_ODBC as the cursor mode, and lastAffectedRows and SelectLimit() fixes.
+- The odbc driver's FetchField() field names did not obey ADODB_ASSOC_CASE. Fixed.
+- Some bugs in adodb_backtrace() fixed.
+- Added "INT IDENTITY" type to adorecordset::MetaType() to support odbc_mssql properly.
+- MetaColumns() for oci8, mssql, odbc revised to support scale. Also minor revisions to odbc MetaColumns() for vfp and db2 compat.
+- Added unsigned support to mysql datadict class. Thx to iamsure.
+- Infinite loop in mssql MoveNext() fixed when ADODB_FETCH_ASSOC used. Thx to Josh R, Night_Wulfe#hotmail.com.
+- ChangeTableSQL contributed by Florian Buzin.
+- The odbc_mssql driver now sets CONCAT_NULL_YIELDS_NULL OFF for compat with mssql driver.
+
+## 3.50 - 19 May 2003
+
+- Fixed mssql compat with FreeTDS. FreeTDS does not implement mssql_fetch_assoc().
+- Merged back connection and recordset code into adodb.inc.php.
+- ADOdb sessions using oracle clobs contributed by achim.gosse#ddd.de. See adodb-session-clob.php.
+- Added /s modifier to preg_match everywhere, which ensures that regex does not stop at /n. Thx Pao-Hsi Huang.
+- Fixed error in metacolumns() for mssql.
+- Added time format support for SQLDate.
+- Image => B added to metatype.
+- MetaType now checks empty($this->blobSize) instead of empty($this).
+- Datadict has beta support for informix, sybase (mapped to mssql), db2 and generic (which is a fudge).
+- BlobEncode for postgresql uses pg_escape_bytea, if available. Needed for compat with 7.3.
+- Added $ADODB_LANG, to support multiple languages in MetaErrorMsg().
+- Datadict can now parse table definition as declarative text.
+- For DataDict, oci8 autoincrement trigger missing semi-colon. Fixed.
+- For DataDict, when REPLACE flag enabled, drop sequence in datadict for autoincrement field in postgres and oci8.s
+- Postgresql defaults to template1 database if no database defined in connect/pconnect.
+- We now clear _resultid in postgresql if query fails.
+
+## 3.40 - 19 May 2003
+
+- Added insert_id for odbc_mssql.
+- Modified postgresql UpdateBlobFile() because it did not work in safe mode.
+- Now connection object is passed to raiseErrorFn as last parameter. Needed by StartTrans().
+- Added StartTrans() and CompleteTrans(). It is recommended that you do not modify transOff, but use the above functions.
+- oci8po now obeys ADODB_ASSOC_CASE settings.
+- Added virtualized error codes, using PEAR DB equivalents. Requires you to manually include adodb-error.inc.php yourself, with MetaError() and MetaErrorMsg($errno).
+- GetRowAssoc for mysql and pgsql were flawed. Fix by Ross Smith.
+- Added to datadict types I1, I2, I4 and I8\. Changed datadict type 'T' to map to timestamp instead of datetime for postgresql.
+- Error handling in ExecuteSQLArray(), adodb-datadict.inc.php did not work.
+- We now auto-quote postgresql connection parameters when building connection string.
+- Added session expiry notification.
+- We now test with odbc mysql - made some changes to odbc recordset constructor.
+- MetaColumns now special cases access and other databases for odbc.
+
+## 3.31 - 17 March 2003
+
+- Added row checking for _fetch in postgres.
+- Added Interval type to MetaType for postgres.
+- Remapped postgres driver to call postgres7 driver internally.
+- Adorecordset_array::getarray() did not return array when nRows >= 0.
+- Postgresql: at times, no error message returned by pg_result_error() but error message returned in pg_last_error(). Recoded again.
+- Interbase blob's now use chunking for updateblob.
+- Move() did not set EOF correctly. Reported by Jorma T.
+- We properly support mysql timestamp fields when we are creating mysql tables using the data-dict interface.
+- Table regex includes backticks character now.
+
+## 3.30 - 3 March 2003
+
+- Added $ADODB_EXTENSION and $ADODB_COMPAT_FETCH constant.
+- Made blank1stItem configurable using syntax "value:text" in GetMenu/GetMenu2. Thx to Gabriel Birke.
+- Previously ADOdb differed from the Microsoft standard because it did not define what to set $this->fields when EOF was reached. Now at EOF, ADOdb sets $this->fields to false for all databases, which is consist with Microsoft's implementation. Postgresql and mysql have always worked this way (in 3.11 and earlier). If you are experiencing compatibility problems (and you are not using postgresql nor mysql) on upgrading to 3.30, try setting the global variables $ADODB_COUNTRECS = true (which is the default) and $ADODB_FETCH_COMPAT = true (this is a new global variable).
+- We now check both pg_result_error and pg_last_error as sometimes pg_result_error does not display anything. Iman Mayes
+- We no longer check for magic quotes gpc in Quote().
+- Misc fixes for table creation in adodb-datadict.inc.php. Thx to iamsure.
+- Time calculations use adodb_time library for all negative timestamps due to problems in Red Hat 7.3 or later. Formerly, only did this for Windows.
+- In mssqlpo, we now check if $sql in _query is a string before we change || to +. This is to support prepared stmts.
+- Move() and MoveLast() internals changed to support to support EOF and $this->fields change.
+- Added ADODB_FETCH_BOTH support to mssql. Thx to Angel Fradejas afradejas#mediafusion.es
+- We now check if link resource exists before we run mysql_escape_string in qstr().
+- Before we flock in csv code, we check that it is not a http url.
+
+## 3.20 - 17 Feb 2003
+
+- Added new Data Dictionary classes for creating tables and indexes. Warning - this is very much alpha quality code. The API can still change. See adodb/tests/test-datadict.php for more info.
+- We now ignore $ADODB_COUNTRECS for mysql, because PHP truncates incomplete recordsets when mysql_unbuffered_query() is called a second time.
+- Now postgresql works correctly when $ADODB_COUNTRECS = false.
+- Changed _adodb_getcount to properly support SELECT DISTINCT.
+- Discovered that $ADODB_COUNTRECS=true has some problems with prepared queries - suspect PHP bug.
+- Now GetOne and GetRow run in $ADODB_COUNTRECS=false mode for better performance.
+- Added support for mysql_real_escape_string() and pg_escape_string() in qstr().
+- Added an intermediate variable for mysql _fetch() and MoveNext() to store fields, to prevent overwriting field array with boolean when mysql_fetch_array() returns false.
+- Made arrays for getinsertsql and getupdatesql case-insensitive. Suggested by Tim Uckun" tim#diligence.com
+
+## 3.11 - 11 Feb 2003
+
+- Added check for ADODB_NEVER_PERSIST constant in PConnect(). If defined, then PConnect() will actually call non-persistent Connect().
+- Modified interbase to properly work with Prepare().
+- Added $this->ibase_timefmt to allow you to change the date and time format.
+- Added support for $input_array parameter in CacheFlush().
+- Added experimental support for dbx, which was then removed when i found that it was slower than using native calls.
+- Added MetaPrimaryKeys for mssql and ibase/firebird.
+- Added new $trim parameter to GetCol and CacheGetCol
+- Uses updated adodb-time.inc.php 0.06.
+
+## 3.10 - 27 Jan 2003
+
+- Added adodb_date(), adodb_getdate(), adodb_mktime() and adodb-time.inc.php.
+- For interbase, added code to handle unlimited number of bind parameters. From Daniel Hasan daniel#hasan.cl.
+- Added BlobDecode and UpdateBlob for informix. Thx to Fernando Ortiz.
+- Added constant ADODB_WINDOWS. If defined, means that running on Windows.
+- Added constant ADODB_PHPVER which stores php version as a hex num. Removed $ADODB_PHPVER variable.
+- Felho Bacsi reported a minor white-space regular expression problem in GetInsertSQL.
+- Modified ADO to use variant to store _affectedRows
+- Changed ibase to use base class Replace(). Modified base class Replace() to support ibase.
+- Changed odbc to auto-detect when 0 records returned is wrong due to bad odbc drivers.
+- Changed mssql to use datetimeconvert ini setting only when 4.30 or later (does not work in 4.23).
+- ExecuteCursor($stmt, $cursorname, $params) now accepts a new $params array of additional bind parameters -- William Lovaton walovaton#yahoo.com.mx.
+- Added support for sybase_unbuffered_query if ADODB_COUNTRECS == false. Thx to chuck may.
+- Fixed FetchNextObj() bug. Thx to Jorma Tuomainen.
+- We now use SCOPE_IDENTITY() instead of @@IDENTITY for mssql - thx to marchesini#eside.it
+- Changed postgresql movenext logic to prevent illegal row number from being passed to pg_fetch_array().
+- Postgresql initrs bug found by "Bogdan RIPA" bripa#interakt.ro $f1 accidentally named $f
+
+## 3.00 - 6 Jan 2003
+
+- Fixed adodb-pear.inc.php syntax error.
+- Improved _adodb_getcount() to use SELECT COUNT(*) FROM ($sql) for languages that accept it.
+- Fixed _adodb_getcount() caching error.
+- Added sql to retrive table and column info for odbc_mssql.
diff --git a/vendor/adodb/adodb-php/docs/changelog_v4+5.md b/vendor/adodb/adodb-php/docs/changelog_v4+5.md
new file mode 100644
index 0000000..c6a9c10
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/changelog_v4+5.md
@@ -0,0 +1,109 @@
+## 4.990/5.05 - 11 Jul 2008
+
+- Added support for multiple recordsets in mysqli Geisel Sierote <geisel#4up.com.br>. See http://phplens.com/lens/lensforum/msgs.php?id=15917
+- Malcolm Cook added new Reload() function to Active Record. See http://phplens.com/lens/lensforum/msgs.php?id=17474
+- Thanks Zoltan Monori [monzol#fotoprizma.hu] for bug fixes in iterator, SelectLimit, GetRandRow, etc.
+- Under heavy loads, the performance monitor for oci8 disables Ixora views.
+- Fixed sybase driver SQLDate to use str_replace(). Also for adodb5, changed sybase driver UnixDate and UnixTimeStamp calls to static.
+- Changed oci8 lob handler to use &amp; reference $this-&gt;_refLOBs[$numlob]['VAR'] = &amp;$var.
+- We now strtolower the get_class() function in PEAR::isError() for php5 compat.
+- CacheExecute did not retrieve cache recordsets properly for 5.04 (worked in 4.98). Fixed.
+- New ADODB_Cache_File class for file caching defined in adodb.inc.php.
+- Farsi language file contribution by Peyman Hooshmandi Raad (phooshmand#gmail.com)
+- New API for creating your custom caching class which is stored in $ADODB_CACHE:
+ ```
+include "/path/to/adodb.inc.php";
+$ADODB_CACHE_CLASS = 'MyCacheClass';
+
+class MyCacheClass extends ADODB_Cache_File
+{
+ function writecache($filename, $contents,$debug=false){...}
+ function &readcache($filename, &$err, $secs2cache, $rsClass){ ...}
+ :
+}
+
+$DB = NewADOConnection($driver);
+$DB->Connect(...); ## MyCacheClass created here and stored in $ADODB_CACHE global variable.
+
+$data = $rs->CacheGetOne($sql); ## MyCacheClass is used here for caching...
+ ```
+- Memcache supports multiple pooled hosts now. Only if none of the pooled servers can be contacted will a connect error be generated. Usage example below:
+ ```
+$db = NewADOConnection($driver);
+$db->memCache = true; /// should we use memCache instead of caching in files
+$db->memCacheHost = array($ip1, $ip2, $ip3); /// $db->memCacheHost = $ip1; still works
+$db->memCachePort = 11211; /// this is default memCache port
+$db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+
+$db->Connect(...);
+$db->CacheExecute($sql);
+ ```
+
+## 4.98/5.04 - 13 Feb 2008
+
+- Fixed adodb_mktime problem which causes a performance bottleneck in $hrs.
+- Added mysqli support to adodb_getcount().
+- Removed MYSQLI_TYPE_CHAR from MetaType().
+
+## 4.97/5.03 - 22 Jan 2008
+
+- Active Record: $ADODB_ASSOC_CASE=1 did not work properly. Fixed.
+- Modified Fields() in recordset class to support display null fields in FetchNextObject().
+- In ADOdb5, active record implementation, we now support column names with spaces in them - we autoconvert the spaces to _ using __set(). Thx Daniel Cook. http://phplens.com/lens/lensforum/msgs.php?id=17200
+- Removed $arg3 from mysqli SelectLimit. See http://phplens.com/lens/lensforum/msgs.php?id=16243. Thx Zsolt Szeberenyi.
+- Changed oci8 FetchField, which returns the max_length of BLOB/CLOB/NCLOB as 4000 (incorrectly) to -1.
+- CacheExecute would sometimes return an error on Windows if it was unable to lock the cache file. This is harmless and has been changed to a warning that can be ignored. Also adodb_write_file() code revised.
+- ADOdb perf code changed to only log sql if execution time &gt;= 0.05 seconds. New $ADODB_PERF_MIN variable holds min sql timing. Any SQL with timing value below this and is not causing an error is not logged.
+- Also adodb_backtrace() now traces 1 level deeper as sometimes actual culprit function is not displayed.
+- Fixed a group by problem with adodb_getcount() for db's which are not postgres/oci8 based.
+- Changed mssql driver Parameter() from SQLCHAR to SQLVARCHAR: case 'string': $type = SQLVARCHAR; break.
+- Problem with mssql driver in php5 (for adodb 5.03) because some functions are not static. Fixed.
+
+## 4.96/5.02 - 24 Sept 2007
+
+ADOdb perf for oci8 now has non-table-locking code when clearing the sql. Slower but better transparency. Added in 4.96a and 5.02a.
+Fix adodb count optimisation. Preg_match did not work properly. Also rewrote the ORDER BY stripping code in _adodb_getcount(), adodb-lib.inc.php.
+SelectLimit for oci8 not optimal for large recordsets when offset=0. Changed $nrows check.
+Active record optimizations. Added support for assoc arrays in Set().
+Now GetOne returns null if EOF (no records found), and false if error occurs. Use ErrorMsg()/ErrorNo() to get the error.
+Also CacheGetRow and CacheGetCol will return false if error occurs, or empty array() if EOF, just like GetRow and GetCol.
+Datadict now allows changing of types which are not resizable, eg. VARCHAR to TEXT in ChangeTableSQL. -- Mateo Tibaquirá
+Added BIT data type support to adodb-ado.inc.php and adodb-ado5.inc.php.
+Ldap driver did not return actual ldap error messages. Fixed.
+Implemented GetRandRow($sql, $inputarr). Optimized for Oci8.
+Changed adodb5 active record to use static SetDatabaseAdapter() and removed php4 constructor. Bas van Beek bas.vanbeek#gmail.com.
+Also in adodb5, changed adodb-session2 to use static function declarations in class. Thx Daniel Berlin.
+Added "Clear SQL Log" to bottom of Performance screen.
+Sessions2 code echo'ed directly to the screen in debug mode. Now uses ADOConnection::outp().
+In mysql/mysqli, qstr(null) will return the string "null" instead of empty quoted string "''".
+postgresql optimizeTable in perf-postgres.inc.php added by Daniel Berlin (mail#daniel-berlin.de)
+Added 5.2.1 compat code for oci8.
+Changed @@identity to SCOPE_IDENTITY() for multiple mssql drivers. Thx Stefano Nari.
+Code sanitization introduced in 4.95 caused problems in European locales (as float 3.2 was typecast to 3,2). Now we only sanitize if is_numeric fails.
+Added support for customizing ADORecordset_empty using $this-&gt;rsPrefix.'empty'. By Josh Truwin.
+Added proper support for ALterColumnSQL for Postgresql in datadict code. Thx. Josh Truwin.
+Added better support for MetaType() in mysqli when using an array recordset.
+Changed parser for pgsql error messages in adodb-error.inc.php to case-insensitive regex.
+
+## 4.95/5.01 - 17 May 2007
+
+CacheFlush debug outp() passed in invalid parameters. Fixed.
+Added Thai language file for adodb. Thx Trirat Petchsingh rosskouk#gmail.com
+and Marcos Pont
+Added zerofill checking support to MetaColumns for mysql and mysqli.
+CacheFlush no longer deletes all files/directories. Only *.cache files
+deleted.
+DB2 timestamp format changed to var $fmtTimeStamp =
+&quot;'Y-m-d-H:i:s'&quot;;
+Added some code sanitization to AutoExecute in adodb-lib.inc.php.
+Due to typo, all connections in adodb-oracle.inc.php would become
+persistent, even non-persistent ones. Fixed.
+Oci8 DBTimeStamp uses 24 hour time for input now, so you can perform string
+comparisons between 2 DBTimeStamp values.
+Some PHP4.4 compat issues fixed in adodb-session2.inc.php
+For ADOdb 5.01, fixed some adodb-datadict.inc.php MetaType compat issues
+with PHP5.
+The $argHostname was wiped out in adodb-ado5.inc.php. Fixed.
+Adodb5 version, added iterator support for adodb_recordset_empty.
+Adodb5 version,more error checking code now will use exceptions if
+available.
diff --git a/vendor/adodb/adodb-php/docs/changelog_v4.x.md b/vendor/adodb/adodb-php/docs/changelog_v4.x.md
new file mode 100644
index 0000000..e346921
--- /dev/null
+++ b/vendor/adodb/adodb-php/docs/changelog_v4.x.md
@@ -0,0 +1,722 @@
+# ADOdb old Changelog - v4.x
+
+See the [Current Changelog](changelog.md).
+Older changelogs:
+[v3.x](changelog_v3.x.md),
+[v2.x](changelog_v2.x.md).
+
+
+## 4.990 - 11 Jul 2008
+
+- Added support for multiple recordsets in mysqli Geisel Sierote <geisel#4up.com.br>. See http://phplens.com/lens/lensforum/msgs.php?id=15917
+- Malcolm Cook added new Reload() function to Active Record. See http://phplens.com/lens/lensforum/msgs.php?id=17474
+- Thanks Zoltan Monori [monzol#fotoprizma.hu] for bug fixes in iterator, SelectLimit, GetRandRow, etc.
+- Under heavy loads, the performance monitor for oci8 disables Ixora views.
+- Fixed sybase driver SQLDate to use str_replace(). Also for adodb5, changed sybase driver UnixDate and UnixTimeStamp calls to static.
+- Changed oci8 lob handler to use &amp; reference $this-&gt;_refLOBs[$numlob]['VAR'] = &amp;$var.
+- We now strtolower the get_class() function in PEAR::isError() for php5 compat.
+- CacheExecute did not retrieve cache recordsets properly for 5.04 (worked in 4.98). Fixed.
+- New ADODB_Cache_File class for file caching defined in adodb.inc.php.
+- Farsi language file contribution by Peyman Hooshmandi Raad (phooshmand#gmail.com)
+- New API for creating your custom caching class which is stored in $ADODB_CACHE:
+ ```
+include "/path/to/adodb.inc.php";
+$ADODB_CACHE_CLASS = 'MyCacheClass';
+
+class MyCacheClass extends ADODB_Cache_File
+{
+ function writecache($filename, $contents,$debug=false){...}
+ function &readcache($filename, &$err, $secs2cache, $rsClass){ ...}
+ :
+}
+
+$DB = NewADOConnection($driver);
+$DB->Connect(...); ## MyCacheClass created here and stored in $ADODB_CACHE global variable.
+
+$data = $rs->CacheGetOne($sql); ## MyCacheClass is used here for caching...
+```
+- Memcache supports multiple pooled hosts now. Only if none of the pooled servers can be contacted will a connect error be generated. Usage example below:
+ ```
+$db = NewADOConnection($driver);
+$db->memCache = true; /// should we use memCache instead of caching in files
+$db->memCacheHost = array($ip1, $ip2, $ip3); /// $db->memCacheHost = $ip1; still works
+$db->memCachePort = 11211; /// this is default memCache port
+$db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+
+$db->Connect(...);
+$db->CacheExecute($sql);
+```
+
+## 4.98 - 13 Feb 2008
+
+- Fixed adodb_mktime problem which causes a performance bottleneck in $hrs.
+- Added mysqli support to adodb_getcount().
+- Removed MYSQLI_TYPE_CHAR from MetaType().
+
+## 4.97 - 22 Jan 2008
+
+- Active Record: $ADODB_ASSOC_CASE=1 did not work properly. Fixed.
+- Modified Fields() in recordset class to support display null fields in FetchNextObject().
+- In ADOdb5, active record implementation, we now support column names with spaces in them - we autoconvert the spaces to `_` using `__set()`. Thx Daniel Cook. http://phplens.com/lens/lensforum/msgs.php?id=17200
+- Removed $arg3 from mysqli SelectLimit. See http://phplens.com/lens/lensforum/msgs.php?id=16243. Thx Zsolt Szeberenyi.
+- Changed oci8 FetchField, which returns the max_length of BLOB/CLOB/NCLOB as 4000 (incorrectly) to -1.
+- CacheExecute would sometimes return an error on Windows if it was unable to lock the cache file. This is harmless and has been changed to a warning that can be ignored. Also adodb_write_file() code revised.
+- ADOdb perf code changed to only log sql if execution time &gt;= 0.05 seconds. New $ADODB_PERF_MIN variable holds min sql timing. Any SQL with timing value below this and is not causing an error is not logged.
+- Also adodb_backtrace() now traces 1 level deeper as sometimes actual culprit function is not displayed.
+- Fixed a group by problem with adodb_getcount() for db's which are not postgres/oci8 based.
+- Changed mssql driver Parameter() from SQLCHAR to SQLVARCHAR: case 'string': $type = SQLVARCHAR; break.
+- Problem with mssql driver in php5 (for adodb 5.03) because some functions are not static. Fixed.
+
+## 4.96 - 24 Sept 2007
+
+- ADOdb perf for oci8 now has non-table-locking code when clearing the sql. Slower but better transparency. Added in 4.96a and 5.02a.
+- Fix adodb count optimisation. Preg_match did not work properly. Also rewrote the ORDER BY stripping code in _adodb_getcount(), adodb-lib.inc.php.
+- SelectLimit for oci8 not optimal for large recordsets when offset=0. Changed $nrows check.
+- Active record optimizations. Added support for assoc arrays in Set().
+- Now GetOne returns null if EOF (no records found), and false if error occurs. Use ErrorMsg()/ErrorNo() to get the error.
+- Also CacheGetRow and CacheGetCol will return false if error occurs, or empty array() if EOF, just like GetRow and GetCol.
+- Datadict now allows changing of types which are not resizable, eg. VARCHAR to TEXT in ChangeTableSQL. -- Mateo Tibaquirá
+- Added BIT data type support to adodb-ado.inc.php and adodb-ado5.inc.php.
+- Ldap driver did not return actual ldap error messages. Fixed.
+- Implemented GetRandRow($sql, $inputarr). Optimized for Oci8.
+- Changed adodb5 active record to use static SetDatabaseAdapter() and removed php4 constructor. Bas van Beek bas.vanbeek#gmail.com.
+- Also in adodb5, changed adodb-session2 to use static function declarations in class. Thx Daniel Berlin.
+- Added "Clear SQL Log" to bottom of Performance screen.
+- Sessions2 code echo'ed directly to the screen in debug mode. Now uses ADOConnection::outp().
+- In mysql/mysqli, qstr(null) will return the string "null" instead of empty quoted string "''".
+- postgresql optimizeTable in perf-postgres.inc.php added by Daniel Berlin (mail#daniel-berlin.de)
+- Added 5.2.1 compat code for oci8.
+- Changed @@identity to SCOPE_IDENTITY() for multiple mssql drivers. Thx Stefano Nari.
+- Code sanitization introduced in 4.95 caused problems in European locales (as float 3.2 was typecast to 3,2). Now we only sanitize if is_numeric fails.
+- Added support for customizing ADORecordset_empty using $this-&gt;rsPrefix.'empty'. By Josh Truwin.
+- Added proper support for ALterColumnSQL for Postgresql in datadict code. Thx. Josh Truwin.
+- Added better support for MetaType() in mysqli when using an array recordset.
+- Changed parser for pgsql error messages in adodb-error.inc.php to case-insensitive regex.
+
+## 4.95 - 17 May 2007
+
+- CacheFlush debug outp() passed in invalid parameters. Fixed.
+- Added Thai language file for adodb. Thx Trirat Petchsingh rosskouk#gmail.com and Marcos Pont
+- Added zerofill checking support to MetaColumns for mysql and mysqli.
+- CacheFlush no longer deletes all files directories. Only `*.cache` files deleted.
+- DB2 timestamp format changed to var $fmtTimeStamp = 'Y-m-d-H:i:s';
+- Added some code sanitization to AutoExecute in adodb-lib.inc.php.
+- Due to typo, all connections in adodb-oracle.inc.php would become persistent, even non-persistent ones. Fixed.
+- Oci8 DBTimeStamp uses 24 hour time for input now, so you can perform string comparisons between 2 DBTimeStamp values.
+- Some PHP4.4 compat issues fixed in adodb-session2.inc.php
+- For ADOdb 5.01, fixed some adodb-datadict.inc.php MetaType compat issues with PHP5.
+- The $argHostname was wiped out in adodb-ado5.inc.php. Fixed.
+- Adodb5 version, added iterator support for adodb_recordset_empty.
+- Adodb5 version,more error checking code now will use exceptions if available.
+
+## 4.94 - 23 Jan 2007
+
+- Active Record: $ADODB_ASSOC_CASE=2 did not work properly. Fixed. Thx gmane#auxbuss.com.
+- mysqli had bugs in BeginTrans() and EndTrans(). Fixed.
+- Improved error handling when no database is connected for oci8. Thx Andy Hassall.
+- Names longer than 30 chars in oci8 datadict will be changed to random name. Thx Eugenio. http://phplens.com/lens/lensforum/msgs.php?id=16182
+- Added var $upperCase = 'ucase' to access and ado_access drivers. Thx Renato De Giovanni renato#cria.org.br
+- Postgres64 driver, if preparing plan failed in _query, did not handle error properly. Fixed. See http://phplens.com/lens/lensforum/msgs.php?id=16131.
+- Fixed GetActiveRecordsClass() reference bug. See http://phplens.com/lens/lensforum/msgs.php?id=16120
+- Added handling of nulls in adodb-ado_mssql.inc.php for qstr(). Thx to Felix Rabinovich.
+- Adodb-dict contributions by Gaetano
+ - Support for INDEX in data-dict. Example: idx_ev1. The ability to define indexes using the INDEX keyword was added in ADOdb 4.94. The following example features mutiple indexes, including a compound index idx_ev1.
+
+ ```
+ event_id I(11) NOTNULL AUTOINCREMENT PRIMARY,
+ event_type I(4) NOTNULL
+ event_start_date T DEFAULT NULL **INDEX id_esd**,
+ event_end_date T DEFAULT '0000-00-00 00:00:00' **INDEX id_eted**,
+ event_parent I(11) UNSIGNED NOTNULL DEFAULT 0 **INDEX id_evp**,
+ event_owner I(11) DEFAULT 0 **INDEX idx_ev1**,
+ event_project I(11) DEFAULT 0 **INDEX idx_ev1**,
+ event_times_recuring I(11) UNSIGNED NOTNULL DEFAULT 0,
+ event_icon C(20) DEFAULT 'obj/event',
+ event_description X
+ ```
+
+ - Prevents the generated SQL from including double drop-sequence statements for REPLACE case of tables with autoincrement columns (on those dbs that emulate it via sequences)
+ - makes any date defined as DEFAULT value for D and T columns work cross-database, not just the "sysdate" value (as long as it is specified using adodb standard format). See above example.
+- Fixed pdo's GetInsertID() support. Thx Ricky Su.
+- oci8 Prepare() now sets error messages if an error occurs.
+- Added 'PT_BR' to SetDateLocale() -- brazilian portugese.
+- charset in oci8 was not set correctly on `*Connect()`
+- ADOConnection::Transpose() now appends as first column the field names.
+- Added $ADODB_QUOTE_FIELDNAMES. If set to true, will autoquote field names in AutoExecute(),GetInsertSQL(), GetUpdateSQL().
+- Transpose now adds the field names as the first column after transposition.
+- Added === check in ADODB_SetDatabaseAdapter for $db, adodb-active-record.inc.php. Thx Christian Affolter.
+- Added ErrorNo() to adodb-active-record.inc.php. Thx ante#novisplet.com.
+
+## 4.93 - 10 Oct 2006
+
+- Added support for multiple database connections in performance monitoring code (adodb-perf.inc.php). Now all sql in multiple database connections can be saved into one database ($ADODB_LOG_CONN).
+- Added MetaIndexes() to odbc_mssql.
+- Added connection property $db->null2null = 'null'. In autoexecute/getinsertsql/getupdatesql, this value will be converted to a null. Set this to a funny invalid value if you do not want null conversion. See http://phplens.com/lens/lensforum/msgs.php?id=15902.
+- Path disclosure problem in mysqli fixed. Thx Andy.
+- Fixed typo in session_schema2.xml.
+- Changed INT in oci8 to return correct precision in $fld->max_length, MetaColumns(). Thx Eloy Lafuente Plaza.
+- Patched postgres64 _connect to handle serverinfo(). see http://phplens.com/lens/lensforum/msgs.php?id=15887.
+- Added pdo fix for null columns. See http://phplens.com/lens/lensforum/msgs.php?id=15889
+- For stored procedures, missing connection id now passed into mssql_query(). Thx Ecsy (ecsy#freemail.hu).
+
+## 4.92a - 30 Aug 2006
+
+- Syntax error in postgres7 driver. Thx Eloy Lafuente Plaza.
+- Minor bug fixes - adodb informix 10 types added to adodb.inc.php. Thx Fernando Ortiz.
+
+## 4.92 - 29 Aug 2006
+
+- Better odbtp date support.
+- Added IgnoreErrors() to bypass default error handling.
+- The _adodb_getcount() function in adodb-lib.inc.php, some ORDER BY bug fixes.
+- For ibase and firebird, set $sysTimeStamp = "CURRENT_TIMESTAMP".
+- Fixed postgres connection bug: http://phplens.com/lens/lensforum/msgs.php?id=11057.
+- Changed CacheSelectLimit() to flush cache when $secs2cache==-1 due to complaints from other users.
+- Added support for using memcached with CacheExecute/CacheSelectLimit. Requires memcache module PECL extension. Usage:
+ ```
+$db = NewADOConnection($driver);
+$db->memCache = true; /// should we use memCache instead of caching in files
+$db->memCacheHost = "126.0.1.1"; /// memCache host
+$db->memCachePort = 11211; /// this is default memCache port
+$db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+$db->Connect(...);
+$db->CacheExecute($sql);
+```
+
+- Implemented Transpose() for recordsets. Recordset must be retrieved using ADODB_FETCH_NUM. First column becomes the column name.
+ ```
+$db = NewADOConnection('mysql');
+$db->Connect(...);
+$db->SetFetchMode(ADODB_FETCH_NUM);
+$rs = $db->Execute('select productname,productid,unitprice from products limit 10');
+$rs2 = $db->Transpose($rs);
+rs2html($rs2);
+```
+
+## 4.91 - 2 Aug 2006
+
+- Major session code rewrite... See session docs.
+- PDO bindinputarray() was not set properly for MySQL (changed from true to false).
+- Changed CacheSelectLimit() to re-cache when $secs2cache==0. This is one way to flush the cache when SelectLimit is called.
+- Added to quotes to mysql and mysqli: "SHOW COLUMNS FROM \`%s\`";
+- Removed accidental optgroup handling in GetMenu(). Fixed ibase _BlobDecode for php5 compat, and also mem alloc issues for small blobs, thx salvatori#interia.pl
+- Mysql driver OffsetDate() speedup, useful for adodb-sessions.
+- Fix for GetAssoc() PHP5 compat. See http://phplens.com/lens/lensforum/msgs.php?id=15425
+- Active Record - If inserting a record and the value of a primary key field is null, then we do not insert that field in as we assume it is an auto-increment field. Needed by mssql.
+- Changed postgres7 MetaForeignKeys() see http://phplens.com/lens/lensforum/msgs.php?id=15531
+- DB2 will now return db2_conn_errormsg() when it is a connection error.
+
+## 4.90 - 8 June 2006
+
+- Changed adodb_countrec() in adodb-lib.inc.php to allow LIMIT to be used as a speedup to reduce no of records counted.
+- Added support for transaction modes for postgres and oci8 with SetTransactionMode(). These transaction modes affect all subsequent transactions of that connection.
+- Thanks to Halmai Csongor for suggestion.
+- Removed `$off = $fieldOffset - 1` line in db2 driver, FetchField(). Tx Larry Menard.
+- Added support for PHP5 objects as Execute() bind parameters using `__toString` (eg. Simple-XML). Thx Carl-Christian Salvesen.
+- Rounding in tohtml.inc.php did not work properly. Fixed.
+- MetaIndexes in postgres fails when fields are deleted then added in again because the attnum has gaps in it. See https://sourceforge.net/p/adodb/bugs/45/. Fixed.
+- MetaForeignkeys in mysql and mysqli did not work when fetchMode==ADODB_FETCH_ASSOC used. Fixed.
+- Reference error in AutoExecute() fixed.
+- Added macaddr postgres type to MetaType. Maps to 'C'.
+- Added to `_connect()` in adodb-ado5.inc.php support for $database and $dataProvider parameters. Thx Larry Menard.
+- Added support for sequences in adodb-ado_mssql.inc.php. Thx Larry Menard.
+- Added ADODB_SESSION_READONLY.
+- Added session expiryref support to crc32 mode, and in LOB code.
+- Clear `_errorMsg` in postgres7 driver, so that ErrorMsg() displays properly when no error occurs.
+- Added BindDate and BindTimeStamp
+
+## 4.81 - 3 May 2006
+
+- Fixed variable ref errors in adodb-ado5.inc.php in _query().
+- Mysqli setcharset fix using method_exists().
+- The adodb-perf.inc.php CreateLogTable() code now works for user-defined table names.
+- Error in ibase_blob_open() fixed. See http://phplens.com/lens/lensforum/msgs.php?id=14997
+
+## 4.80 - 8 Mar 2006
+
+- Added activerecord support.
+- Added mysql `$conn->compat323 = true` if you want MySQL 3.23 compat enabled. Fixes GetOne() Select-Limit problems.
+- Added adodb-xmlschema03.inc.php to support XML Schema version 3 and updated adodb-datadict.htm docs.
+- Better memory management in Execute. Thx Mike Fedyk.
+
+## 4.72 - 21 Feb 2006
+
+- Added 'new' DSN parameter for NConnect().
+- Pager now sanitizes $PHP_SELF to protect against XSS. Thx to James Bercegay and others.
+- ADOConnection::MetaType changed to setup $rs->connection correctly.
+- New native DB2 driver contributed by Larry Menard, Dan Scott, Andy Staudacher, Bharat Mediratta.
+- The mssql CreateSequence() did not BEGIN TRANSACTION correctly. Fixed. Thx Sean Lee.
+- The _adodb_countrecs() function in adodb-lib.inc.php has been revised to handle more ORDER BY variations.
+
+## 4.71 - 24 Jan 2006
+
+- Fixes postgresql security issue related to binary strings. Thx to Andy Staudacher.
+- Several DSN bugs found:
+ 1. Fix bugs in DSN connections introduced in 4.70 when underscores are found in the DSN.
+ 2. DSN with _ did not work properly in PHP5 (fine in PHP4). Fixed.
+ 3. Added support for PDO DSN connections in NewADOConnection(), and database parameter in PDO::Connect().
+- The oci8 datetime flag not correctly implemented in ADORecordSet_array. Fixed.
+- Added BlobDelete() to postgres, as a counterpoint to UpdateBlobFile().
+- Fixed GetInsertSQL() to support oci8po.
+- Fixed qstr() issue with postgresql with \0 in strings.
+- Fixed some datadict driver loading issues in _adodb_getdriver().
+- Added register shutdown function session_write_close in adodb-session.inc.php for PHP 5 compat. See http://phplens.com/lens/lensforum/msgs.php?id=14200.
+
+## 4.70 - 6 Jan 2006
+
+- Many fixes from Danila Ulyanov to ibase, oci8, postgres, mssql, odbc_oracle, odbtp, etc drivers.
+- Changed usage of binary hint in adodb-session.inc.php for mysql. See http://phplens.com/lens/lensforum/msgs.php?id=14160
+- Fixed invalid variable reference problem in undomq(), adodb-perf.inc.php.
+- Fixed http://phplens.com/lens/lensforum/msgs.php?id=14254 in adodb-perf.inc.php, `_DBParameter()` settings of fetchmode was wrong.
+- Fixed security issues in server.php and tmssql.php discussed by Andreas Sandblad in a Secunia security advisory. Added `$ACCEPTIP = 127.0.0.1` and changed suggested root password to something more secure.
+- Changed pager to close recordset after RenderLayout().
+
+## 4.68 - 25 Nov 2005
+
+- PHP 5 compat for mysqli. MetaForeignKeys repeated twice and MYSQLI_BINARY_FLAG missing.
+- PHP 5.1 support for postgresql bind parameters using ? did not work if >= 10 parameters. Fixed. Thx to Stanislav Shramko.
+- Lots of PDO improvements.
+- Spelling error fixed in mysql MetaForeignKeys, $associative parameter.
+
+## 4.67 - 16 Nov 2005
+
+- Postgresql not_null flag not set to false correctly. Thx Cristian MARIN.
+- We now check in Replace() if key is in fieldArray. Thx Sébastien Vanvelthem.
+- `_file_get_contents()` function was missing in xmlschema. fixed.
+- Added week in year support to SQLDate(), using 'W' flag. Thx Spider.
+- In sqlite metacolumns was repeated twice, causing PHP 5 problems. Fixed.
+- Made debug output XHTML compliant.
+
+## 4.66 - 28 Sept 2005
+
+- ExecuteCursor() in oci8 did not clean up properly on failure. Fixed.
+- Updated xmlschema.dtd, by "Alec Smecher" asmecher#smecher.bc.ca
+- Hardened SelectLimit, typecasting nrows and offset to integer.
+- Fixed misc bugs in AutoExecute() and GetInsertSQL().
+- Added $conn->database as the property holding the database name. The older $conn->databaseName is retained for backward compat.
+- Changed _adodb_backtrace() compat check to use function_exists().
+- Bug in postgresql MetaIndexes fixed. Thx Kevin Jamieson.
+- Improved OffsetDate for MySQL, reducing rounding error.
+- Metacolumns added to sqlite. Thx Mark Newnham.
+- PHP 4.4 compat fixes for GetAssoc().
+- Added postgresql bind support for php 5.1. Thx Cristiano da Cunha Duarte
+- OffsetDate() fixes for postgresql, typecasting strings to date or timestamp.
+- DBTimeStamp formats for mssql, odbc_mssql and postgresql made to conform with other db's.
+- Changed PDO constants from PDO_ to PDO:: to support latest spec.
+
+## 4.65 - 22 July 2005
+
+- Reverted 'X' in mssql datadict to 'TEXT' to be compat with mssql driver. However now you can set $datadict->typeX = 'varchar(4000)' or 'TEXT' or 'CLOB' for mssql and oci8 drivers.
+- Added charset support when using DSN for Oracle.
+- _adodb_getmenu did not use fieldcount() to get number of fields. Fixed.
+- MetaForeignKeys() for mysql/mysqli contributed by Juan Carlos Gonzalez.
+- MetaDatabases() now correctly returns an array for mysqli driver. Thx Cristian MARIN.
+- CompleteTrans(false) did not return false. Fixed. Thx to JMF.
+- AutoExecute() did not work with Oracle. Fixed. Thx José Moreira.
+- MetaType() added to connection object.
+- More PHP 4.4 reference return fixes. Thx Ryan C Bonham and others.
+
+## 4.64 - 20 June 2005
+
+- In datadict, if the default field value is set to '', then it is not applied when the field is created. Fixed by Eugenio.
+- MetaPrimaryKeys for postgres did not work because of true/false change in 4.63. Fixed.
+- Tested ocifetchstatement in oci8. Rejected at the end.
+- Added port to dsn handling. Supported in postgres, mysql, mysqli,ldap.
+- Added 'w' and 'l' to mysqli SQLDate().
+- Fixed error handling in ldap _connect() to be more consistent. Also added ErrorMsg() handling to ldap.
+- Added support for union in _adodb_getcount, adodb-lib.inc.php for postgres and oci8.
+- rs2html() did not work with null dates properly.
+- PHP 4.4 reference return fixes.
+
+## 4.63 - 18 May 2005
+
+- Added $nrows<0 check to mysqli's SelectLimit().
+- Added OptimizeTable() and OptimizeTables() in adodb-perf.inc.php. By Markus Staab.
+- PostgreSQL inconsistencies fixed. true and false set to TRUE and FALSE, and boolean type in datadict-postgres.inc.php set to 'L' => 'BOOLEAN'. Thx Kevin Jamieson.
+- New adodb_session_create_table() function in adodb-session.inc.php. By Markus Staab.
+- Added null check to UserTimeStamp().
+- Fixed typo in mysqlt driver in adorecordset. Thx to Andy Staudacher.
+- GenID() had a bug in the raiseErrorFn handling. Fixed. Thx Marcos Pont.
+- Datadict name quoting now handles ( ) in index fields correctly - they aren't part of the index field.
+- Performance monitoring:
+ 1. oci8 Ixora checks moved down;
+ 2. expensive sql changed so that only those sql with count(*)>1 are shown;
+ 3. changed sql1 field to a length+crc32 checksum - this breaks backward compat.
+- We remap firebird15 to firebird in data dictionary.
+
+## 4.62 - 2 Apr 2005
+
+- Added 'w' (dow as 0-6 or 1-7) and 'l' (dow as string) for SQLDate for oci8, postgres and mysql.
+- Rolled back MetaType() changes for mysqli done in prev version.
+- Datadict change by chris, cblin#tennaxia.com data mappings from:
+
+ ```
+oci8: X->varchar(4000) XL->CLOB
+mssql: X->XL->TEXT
+mysql: X->XL->LONGTEXT
+fbird: X->XL->varchar(4000)
+```
+ to:
+ ```
+oci8: X->varchar(4000) XL->CLOB
+mssql: X->VARCHAR(4000) XL->TEXT
+mysql: X->TEXT XL->LONGTEXT
+fbird: X->VARCHAR(4000) XL->VARCHAR(32000)
+```
+- Added $connection->disableBlobs to postgresql to improve performance when no bytea is used (2-5% improvement).
+- Removed all HTTP_* vars.
+- Added $rs->tableName to be set before calling AutoExecute().
+- Alex Rootoff rootoff#pisem.net contributed ukrainian language file.
+- Added new mysql_option() support using $conn->optionFlags array.
+- Added support for ldap_set_option() using the $LDAP_CONNECT_OPTIONS global variable. Contributed by Josh Eldridge.
+- Added LDAP_* constant definitions to ldap.
+- Added support for boolean bind variables. We use $conn->false and $conn->true to hold values to set false/true to.
+- We now do not close the session connection in adodb-session.inc.php as other objects could be using this connection.
+- We now strip off `\0` at end of Ixora SQL strings in $perf->tohtml() for oci8.
+
+## 4.61 - 23 Feb 2005
+
+- MySQLi added support for mysqli_connect_errno() and mysqli_connect_error().
+- Massive improvements to alpha PDO driver.
+- Quote string bind parameters logged by performance monitor for easy type checking. Thx Jason Judge.
+- Added support for $role when connecting with Interbase/firebird.
+- Added support for enum recognition in MetaColumns() mysql and mysqli. Thx Amedeo Petrella.
+- The sybase_ase driver contributed by Interakt Online. Thx Cristian Marin cristic#interaktonline.com.
+- Removed not_null, has_default, and default_value from ADOFieldObject.
+- Sessions code, fixed quoting of keys when handling LOBs in session write() function.
+- Sessions code, added adodb_session_regenerate_id(), to reduce risk of session hijacking by changing session cookie dynamically. Thx Joe Li.
+- Perf monitor, polling for CPU did not work for PHP 4.3.10 and 5.0.0-5.0.3 due to PHP bugs, so we special case these versions.
+- Postgresql, UpdateBlob() added code to handle type==CLOB.
+
+## 4.60 - 24 Jan 2005
+
+- Implemented PEAR DB's autoExecute(). Simplified design because I don't like using constants when strings work fine.
+- _rs2serialize will now update $rs->sql and $rs->oldProvider.
+- Added autoExecute().
+- Added support for postgres8 driver. Currently just remapped to postgres7 driver.
+- Changed oci8 _query(), so that OCIBindByName() sets the length to -1 if element size is > 4000. This provides better support for LONGs.
+- Added SetDateLocale() support for netherlands (Nl).
+- Spelling error in pivot code ($iff should be $iif).
+- mysql insert_id() did not work with mysql 3.x. Fixed.
+- `\r\n` not converted to spaces correctly in exporting data. Fixed.
+- _nconnect() in mysqli did not return value correctly. Fixed.
+- Arne Eckmann contributed danish language file.
+- Added clone() support to FetchObject() for PHP5.
+- Removed SQL_CUR_USE_ODBC from odbc_mssql.
+
+## 4.55 - 5 Jan 2005
+
+- Found bug in Execute() with bind params for db's that do not support binding natively.
+- DropSequence() now correctly uses default parameter.
+- Now Execute() ignores locale for floats, so 1.23 is NEVER converted to 1,23.
+- SetFetchMode() not properly saved in adodb-perf, suspicious sql and expensive sql. Fixed.
+- Added INET to postgresql metatypes. Thx motzel.
+- Allow oracle hints to work when counting with _adodb_getcount in adodb-lib.inc.php. Thx Chris Wrye.
+- Changed mysql insert_id() to use SELECT LAST_INSERT_ID().
+- If alter col in datadict does not modify col type/size of actual col, then it is removed from alter col code. By Mark Newham. Not perfect as MetaType() !== ActualType().
+- Added handling of view fields in metacolumns() for postgresql. Thx Renato De Giovanni.
+- Added to informix MetaPrimaryKeys and MetaColumns fixes for null bit. Thx to Cecilio Albero.
+- Removed obsolete connection_timeout() from perf code.
+- Added support for arrayClass in adodb-csv.inc.php.
+- RSFilter now accepts methods of the form $array($obj, 'methodname'). Thx to blake#near-time.com.
+- Changed CacheFlush to `$cmd = 'rm -rf '.$ADODB_CACHE_DIR.'/[0-9a-f][0-9a-f]/';`
+- For better cursor concurrency, added code to free ref cursors in oci8 when $rs->Close() is called. Note that CLose() is called internally by the Get* functions too.
+- Added IIF support for access when pivoting. Thx Volodia Krupach.
+- Added mssql datadict support for timestamp. Thx Alexios.
+- Informix pager fix. By Mario Ramirez.
+- ADODB_TABLE_REGEX now includes ':'. By Mario Ramirez.
+- Mark Newnham contributed MetaIndexes for oci8 and db2.
+
+## 4.54 - 5 Nov 2004
+
+- Now you can set $db->charSet = ?? before doing a Connect() in oci8.
+- Added adodbFetchMode to sqlite.
+- Perf code, added a string typecast to substr in adodb_log_sql().
+- Postgres: Changed BlobDecode() to use po_loread, added new $maxblobsize parameter, and now it returns the blob instead of sending it to stdout - make sure to mention that as a compat warning. Also added $db->IsOID($oid) function; uses a heuristic, not guaranteed to work 100%.
+- Contributed arabic language file by "El-Shamaa, Khaled" k.el-shamaa#cgiar.org
+- PHP5 exceptions did not handle @ protocol properly. Fixed.
+- Added ifnull handling for postgresql (using coalesce).
+- Added metatables() support for Postgresql 8.0 (no longer uses pg_% dictionary tables).
+- Improved Sybase ErrorMsg() function. By Gaetano Giunta.
+- Improved oci8 SelectLimit() to use Prepare(). By Cristiano Duarte.
+- Type-cast $row parameter in ifx_fetch_row() to int. Thx stefan bodgan.
+- Ralf becker contributed improvements in postgresql, sapdb, mysql data dictionary handling:
+ - MySql and Postgres MetaType was reporting every int column which was part of a primary key and unique as serial
+ - Postgres was not reporting the scale of decimal types
+ - MaxDB was padding the defaults of none-string types with spaces
+ - MySql now correctly converts enum columns to varchar
+- Ralf also changed Postgresql datadict:
+ - you cant add NOT NULL columns in postgres in one go, they need to be added as NULL and then altered to NOT NULL
+ - AlterColumnSQL could not change a varchar column with numbers into an integer column, postgres need an explicit conversation
+ - a re-created sequence was not set to the correct value, if the name was the old name (no implicit sequence), now always the new name of the implicit sequence is used
+- Sergio Strampelli added extra $intoken check to Lens_ParseArgs() in datadict code.
+
+## 4.53 - 28 Sept 2004
+
+- FetchMode cached in recordset is sometimes mapped to native db fetchMode. Normally this does not matter, but when using cached recordsets, we need to switch back to using adodb fetchmode. So we cache this in $rs->adodbFetchMode if it differs from the db's fetchMode.
+- For informix we now set canSeek = false driver because stefan bodgan tells me that seeking doesn't work.
+- SetDateLocale() never worked till now ;-) Thx david#tomato.it
+- Set $_bindInputArray = true in sapdb driver. Required for clob support.
+- Fixed some PEAR::DB emulation issues with isError() and isWarning. Thx to Gert-Rainer Bitterlich.
+- Empty() used in getupdatesql without strlen() check. Fixed.
+- Added unsigned detection to mysql and mysqli drivers. Thx to dan cech.
+- Added hungarian language file. Thx to Halászvári Gábor.
+- Improved fieldname-type formatting of datadict SQL generated (adding $widespacing parameter to _GenField).
+- Datadict oci8 DROP CONSTRAINTS misspelt. Fixed. Thx Mark Newnham.
+- Changed odbtp to dynamically change databaseType based on connection, eg. from 'odbtp' to 'odbtp_mssql' when connecting to mssql database.
+- In datadict, MySQL I4 was wrongly mapped to MEDIUMINT, which is actually I3. Fixed.
+- Fixed mysqli MetaType() recognition. Mysqli returns numeric types unlike mysql extension. Thx Francesco Riosa.
+- VFP odbc driver curmode set wrongly, causing problems with memo fields. Fixed.
+- Odbc driver did not recognize odbc version 2 driver date types properly. Fixed. Thx Bostjan.
+- ChangeTableSQL() fixes to datadict-db2.inc.php by Mark Newnham.
+- Perf monitoring with odbc improved. Now we try in perf code to manually set the sysTimeStamp using date() if sysTimeStamp is empty.
+- All ADO errors are thrown as exceptions in PHP5. So we added exception handling to ado in PHP5 by creating new adodb-ado5.inc.php driver.
+- Added IsConnected(). Returns true if connection object connected. By Luca.Gioppo.
+- "Ralf Becker" RalfBecker#digitalROCK.de contributed new sapdb data-dictionary driver and a large patch that implements field and table renaming for oracle, mssql, postgresql, mysql and sapdb. See the new RenameTableSQL() and RenameColumnSQL() functions.
+- We now check ExecuteCursor to see if PrepareSP was initially called.
+- Changed oci8 datadict to use MODIFY for $dd->alterCol. Thx Mark Newnham.
+
+## 4.52 - 10 Aug 2004
+
+- Bug found in Replace() when performance logging enabled, introduced in ADOdb 4.50. Fixed.
+- Replace() checks update stmt. If update stmt fails, we now return immediately. Thx to alex.
+- Added support for $ADODB_FORCE_TYPE in GetUpdateSQL/GetInsertSQL. Thx to niko.
+- Added ADODB_ASSOC_CASE support to postgres/postgres7 driver.
+- Support for DECLARE stmt in oci8. Thx Lochbrunner.
+
+## 4.51 - 29 July 2004
+
+- Added adodb-xmlschema 1.0.2. Thx dan and richard.
+- Added new adorecordset_ext_* classes. If ADOdb extension installed for mysql, mysqlt and oci8 (but not oci8po), we use the superfast ADOdb extension code for movenext.
+- Added schema support to mssql and odbc_mssql MetaPrimaryKeys().
+- Patched MSSQL driver to support PHP NULL and Boolean values while binding the input array parameters in the _query() function. By Stephen Farmer.
+- Added support for clob's for mssql, UpdateBlob(). Thx to gfran#directa.com.br
+- Added normalize support for postgresql (true=lowercase table name, or false=case-sensitive table names) to MetaColumns($table, $normalize=true).
+- PHP5 variant dates in ADO not working. Fixed in adodb-ado.inc.php.
+- Constant ADODB_FORCE_NULLS was not working properly for many releases (for GetUpdateSQL). Fixed. Also GetUpdateSQL strips off ORDER BY now - thx Elieser Leão.
+- Perf Monitor for oci8 now dynamically highlights optimizer_* params if too high/low.
+- Added dsn support to NewADOConnection/ADONewConnection.
+- Fixed out of page bounds bug in _adodb_pageexecute_all_rows() Thx to "Sergio Strampelli" sergio#rir.it
+- Speedup of movenext for mysql and oci8 drivers.
+- Moved debugging code _adodb_debug_execute() to adodb-lib.inc.php.
+- Fixed postgresql bytea detection bug. See http://phplens.com/lens/lensforum/msgs.php?id=9849.
+- Fixed ibase datetimestamp typo in PHP5. Thx stefan.
+- Removed whitespace at end of odbtp drivers.
+- Added db2 metaprimarykeys fix.
+- Optimizations to MoveNext() for mysql and oci8. Misc speedups to Get* functions.
+
+## 4.50 - 6 July 2004
+
+- Bumped it to 4.50 to avoid confusion with PHP 4.3.x series.
+- Added db2 metatables and metacolumns extensions.
+- Added alpha PDO driver. Very buggy, only works with odbc.
+- Tested mysqli. Set poorAffectedRows = true. Cleaned up movenext() and _fetch().
+- PageExecute does not work properly with php5 (return val not a variable). Reported Dmytro Sychevsky sych#php.com.ua. Fixed.
+- MetaTables() for mysql, $showschema parameter was not backward compatible with older versions of adodb. Fixed.
+- Changed mysql GetOne() to work with mysql 3.23 when using with non-select stmts (e.g. SHOW TABLES).
+- Changed TRIG_ prefix to a variable in datadict-oci8.inc.php. Thx to Luca.Gioppo#csi.it.
+- New to adodb-time code. We allow you to define your own daylights savings function, adodb_daylight_sv for pre-1970 dates. If the function is defined (somewhere in an include), then you can correct for daylights savings. See http://phplens.com/phpeverywhere/node/view/16#daylightsavings for more info.
+- New sqlitepo driver. This is because assoc mode does not work like other drivers in sqlite. Namely, when selecting (joining) multiple tables, in assoc mode the table names are included in the assoc keys in the "sqlite" driver. In "sqlitepo" driver, the table names are stripped from the returned column names. When this results in a conflict, the first field get preference. Contributed by Herman Kuiper herman#ozuzo.net
+- Added $forcenull parameter to GetInsertSQL/GetUpdateSQL. Idea by Marco Aurelio Silva.
+- More XHTML changes for GetMenu. By Jeremy Evans.
+- Fixes some ibase date issues. Thx to stefan bogdan.
+- Improvements to mysqli driver to support $ADODB_COUNTRECS.
+- Fixed adodb-csvlib.inc.php problem when reading stream from socket. We need to poll stream continiously.
+
+## 4.23 - 16 June 2004
+
+- New interbase/firebird fixes thx to Lester Caine. Driver fixes a problem with getting field names in the result array, and corrects a couple of data conversions. Also we default to dialect3 for firebird. Also ibase sysDate property was wrong. Changed to cast as timestamp.
+- The datadict driver is set up to give quoted tables and fields as this was the only way round reserved words being used as field names in TikiWiki. TikiPro is tidying that up, and I hope to be able to produce a build of THAT which uses what I consider proper UPPERCASE field and table names. The conversion of TikiWiki to ADOdb helped in that, but until the database is completely tidied up in TikiPro ...
+- Modified _gencachename() to include fetchmode in name hash. This means you should clear your cache directory after installing this release as the cache name algorithm has changed.
+- Now Cache* functions work in safe mode, because we do not create sub-directories in the $ADODB_CACHE_DIR in safe mode. In non-safe mode we still create sub-directories. Done by modifying _gencachename().
+- Added $gmt parameter (true/false) to UserDate and UserTimeStamp in connection class, to force conversion of input (in local time) to be converted to UTC/GMT.
+- Mssql datadict did not support INT types properly (no size param allowed). Added _GetSize() to datadict-mssql.inc.php.
+- For borland_ibase, BeginTrans(), changed:
+
+ ```
+$this->_transactionID = $this->_connectionID;
+```
+
+ to
+
+ ```
+$this->_transactionID = ibase_trans($this->ibasetrans, $this->_connectionID);
+```
+
+- Fixed typo in mysqi_field_seek(). Thx to Sh4dow (sh4dow#php.pl).
+- LogSQL did not work with Firebird/Interbase. Fixed.
+- Postgres: made errorno() handling more consistent. Thx to Michael Jahn, Michael.Jahn#mailbox.tu-dresden.de.
+- Added informix patch to better support metatables, metacolumns by "Cecilio Albero" c-albero#eos-i.com
+- Cyril Malevanov contributed patch to oci8 to support passing of LOB parameters:
+
+ ```
+$text = 'test test test';
+$sql = "declare rs clob; begin :rs := lobinout(:sa0); end;";
+$stmt = $conn -> PrepareSP($sql);
+$conn -> InParameter($stmt,$text,'sa0', -1, OCI_B_CLOB);
+$rs = '';
+$conn -> OutParameter($stmt,$rs,'rs', -1, OCI_B_CLOB);
+$conn -> Execute($stmt);
+echo "return = ".$rs."<br>";</pre>
+```
+
+ As he says, the LOBs limitations are:
+ - use OCINewDescriptor before binding
+ - if Param is IN, uses save() before each execute. This is done automatically for you.
+ - if Param is OUT, uses load() after each execute. This is done automatically for you.
+ - when we bind $var as LOB, we create new descriptor and return it as a Bind Result, so if we want to use OUT parameters, we have to store somewhere &$var to load() data from LOB to it.
+ - IN OUT params are not working now (should not be a big problem to fix it)
+ - now mass binding not working too (I've wrote about it before)</pre>
+- Simplified Connect() and PConnect() error handling.
+- When extension not loaded, Connect() and PConnect() will return null. On connect error, the fns will return false.
+- CacheGetArray() added to code.
+- Added Init() to adorecordset_empty().
+- Changed postgres64 driver, MetaColumns() to not strip off quotes in default value if :: detected (type-casting of default).
+- Added test: if (!defined('ADODB_DIR')) die(). Useful to prevent hackers from detecting file paths.
+- Changed metaTablesSQL to ignore Postgres 7.4 information schemas (sql_*).
+- New polish language file by Grzegorz Pacan
+- Added support for UNION in _adodb_getcount().
+- Added security check for ADODB_DIR to limit path disclosure issues. Requested by postnuke team.
+- Added better error message support to oracle driver. Thx to Gaetano Giunta.
+- Added showSchema support to mysql.
+- Bind in oci8 did not handle $name=false properly. Fixed.
+- If extension not loaded, Connect(), PConnect(), NConnect() will return null.
+
+## 4.22 - 15 Apr 2004
+
+- Moved docs to own adodb/docs folder.
+- Fixed session bug when quoting compressed/encrypted data in Replace().
+- Netezza Driver and LDAP drivers contributed by Josh Eldridge.
+- GetMenu now uses rtrim() on values instead of trim().
+- Changed MetaColumnNames to return an associative array, keys being the field names in uppercase.
+- Suggested fix to adodb-ado.inc.php affected_rows to support PHP5 variants. Thx to Alexios Fakos.
+- Contributed bulgarian language file by Valentin Sheiretsky valio#valio.eu.org.
+- Contributed romanian language file by stefan bogdan.
+- GetInsertSQL now checks for table name (string) in $rs, and will create a recordset for that table automatically. Contributed by Walt Boring. Also added OCI_B_BLOB in bind on Walt's request - hope it doesn't break anything :-)
+- Some minor postgres speedups in `_initrs()`.
+- ChangeTableSQL checks now if MetaColumns returns empty. Thx Jason Judge.
+- Added ADOConnection::Time(), returns current database time in unix timestamp format, or false.
+
+## 4.21 - 20 Mar 2004
+
+- We no longer in SelectLimit for VFP driver add SELECT TOP X unless an ORDER BY exists.
+- Pim Koeman contributed dutch language file adodb-nl.inc.php.
+- Rick Hickerson added CLOB support to db2 datadict.
+- Added odbtp driver. Thx to "stefan bogdan" sbogdan#rsb.ro.
+- Changed PrepareSP() 2nd parameter, $cursor, to default to true (formerly false). Fixes oci8 backward compat problems with OUT params.
+- Fixed month calculation error in adodb-time.inc.php. 2102-June-01 appeared as 2102-May-32.
+- Updated PHP5 RC1 iterator support. API changed, hasMore() renamed to valid().
+- Changed internal format of serialized cache recordsets. As we store a version number, this should be backward compatible.
+- Error handling when driver file not found was flawed in ADOLoadCode(). Fixed.
+
+## 4.20 - 27 Feb 2004
+
+- Updated to AXMLS 1.01.
+- MetaForeignKeys for postgres7 modified by Edward Jaramilla, works on pg 7.4.
+- Now numbers accepts function calls or sequences for GetInsertSQL/GetUpdateSQL numeric fields.
+- Changed quotes of 'delete from $perf_table' to "". Thx Kehui (webmaster#kehui.net)
+- Added ServerInfo() for ifx, and putenv trim fix. Thx Fernando Ortiz.
+- Added addq(), which is analogous to addslashes().
+- Tested with php5b4. Fix some php5 compat problems with exceptions and sybase.
+- Carl-Christian Salvesen added patch to mssql _query to support binds greater than 4000 chars.
+- Mike suggested patch to PHP5 exception handler. $errno must be numeric.
+- Added double quotes (") to ADODB_TABLE_REGEX.
+- For oci8, Prepare(...,$cursor), $cursor's meaning was accidentally inverted in 4.11. This causes problems with ExecuteCursor() too, which calls Prepare() internally. Thx to William Lovaton.
+- Now dateHasTime property in connection object renamed to datetime for consistency. This could break bc.
+- Csongor Halmai reports that db2 SelectLimit with input array is not working. Fixed..
+
+## 4.11 - 27 Jan 2004
+
+- Csongor Halmai reports db2 binding not working. Reverted back to emulated binding.
+- Dan Cech modifies datadict code. Adds support for DropIndex. Minor cleanups.
+- Table misspelt in perf-oci8.inc.php. Changed v$conn_cache_advice to v$db_cache_advice. Reported by Steve W.
+- UserTimeStamp and DBTimeStamp did not handle YYYYMMDDHHMMSS format properly. Reported by Mike Muir. Fixed.
+- Changed oci8 Prepare(). Does not auto-allocate OCINewCursor automatically, unless 2nd param is set to true. This will break backward compat, if Prepare/Execute is used instead of ExecuteCursor. Reported by Chris Jones.
+- Added InParameter() and OutParameter(). Wrapper functions to Parameter(), but nicer because they are self-documenting.
+- Added 'R' handling in ActualType() to datadict-mysql.inc.php
+- Added ADOConnection::SerializableRS($rs). Returns a recordset that can be serialized in a session.
+- Added "Run SQL" to performance UI().
+- Misc spelling corrections in adodb-mysqli.inc.php, adodb-oci8.inc.php and datadict-oci8.inc.php, from Heinz Hombergs.
+- MetaIndexes() for ibase contributed by Heinz Hombergs.
+
+## 4.10 - 12 Jan 2004
+
+- Dan Cech contributed extensive changes to data dictionary to support name quoting (with `\``), and drop table/index.
+- Informix added cursorType property. Default remains IFX_SCROLL, but you can change to 0 (non-scrollable cursor) for performance.
+- Added ADODB_View_PrimaryKeys() for returning view primary keys to MetaPrimaryKeys().
+- Simplified chinese file, adodb-cn.inc.php from cysoft.
+- Added check for ctype_alnum in adodb-datadict.inc.php. Thx to Jason Judge.
+- Added connection parameter to ibase Prepare(). Fix by Daniel Hassan.
+- Added nameQuote for quoting identifiers and names to connection obj. Requested by Jason Judge. Also the data dictionary parser now detects `field name` and generates column names with spaces correctly.
+- BOOL type not recognised correctly as L. Fixed.
+- Fixed paths in ADODB_DIR for session files, and back-ported it to 4.05 (15 Dec 2003)
+- Added Schema to postgresql MetaTables. Thx to col#gear.hu
+- Empty postgresql recordsets that had blob fields did not set EOF properly. Fixed.
+- CacheSelectLimit internal parameters to SelectLimit were wrong. Thx to Nio.
+- Modified adodb_pr() and adodb_backtrace() to support command-line usage (eg. no html).
+- Fixed some fr and it lang errors. Thx to Gaetano G.
+- Added contrib directory, with adodb rs to xmlrpc convertor by Gaetano G.
+- Fixed array recordset bugs when `_skiprow1` is true. Thx to Gaetano G.
+- Fixed pivot table code when count is false.
+
+## 4.05 - 13 Dec 2003
+
+- Added MetaIndexes to data-dict code - thx to Dan Cech.
+- Rewritten session code by Ross Smith. Moved code to adodb/session directory.
+- Added function exists check on connecting to most drivers, so we don't crash with the unknown function error.
+- Smart Transactions failed with GenID() when it no seq table has been created because the sql statement fails. Fix by Mark Newnham.
+- Added $db->length, which holds name of function that returns strlen.
+- Fixed error handling for bad driver in ADONewConnection - passed too few params to error-handler.
+- Datadict did not handle types like 16.0 properly in _GetSize. Fixed.
+- Oci8 driver SelectLimit() bug &= instead of =& used. Thx to Swen Thümmler.
+- Jesse Mullan suggested not flushing outp when output buffering enabled. Due to Apache 2.0 bug. Added.
+- MetaTables/MetaColumns return ref bug with PHP5 fixed in adodb-datadict.inc.php.
+- New mysqli driver contributed by Arjen de Rijke. Based on adodb 3.40 driver. Then jlim added BeginTrans, CommitTrans, RollbackTrans, IfNull, SQLDate. Also fixed return ref bug.
+- $ADODB_FLUSH added, if true then force flush in debugging outp. Default is false. In earlier versions, outp defaulted to flush, which is not compat with apache 2.0.
+- Mysql driver's GenID() function did not work when when sql logging is on. Fixed.
+- $ADODB_SESSION_TBL not declared as global var. Not available if adodb-session.inc.php included in function. Fixed.
+- The input array not passed to Execute() in _adodb_getcount(). Fixed.
+
+## 4.04 - 13 Nov 2003
+
+- Switched back to foreach - faster than list-each.
+- Fixed bug in ado driver - wiping out $this->fields with date fields.
+- Performance Monitor, View SQL, Explain Plan did not work if strlen($SQL)>max($_GET length). Fixed.
+- Performance monitor, oci8 driver added memory sort ratio.
+- Added random property, returns SQL to generate a floating point number between 0 and 1;
+
+## 4.03 - 6 Nov 2003
+
+- The path to adodb-php4.inc.php and adodb-iterators.inc.php was not setup properly.
+- Patched SQLDate in interbase to support hours/mins/secs. Thx to ari kuorikoski.
+- Force autorollback for pgsql persistent connections - apparently pgsql did not autorollback properly before 4.3.4. See http://bugs.php.net/bug.php?id=25404
+
+## 4.02 - 5 Nov 2003
+
+- Some errors in adodb_error_pg() fixed. Thx to Styve.
+- Spurious Insert_ID() error was generated by LogSQL(). Fixed.
+- Insert_ID was interfering with Affected_Rows() and Replace() when LogSQL() enabled. Fixed.
+- More foreach loops optimized with list/each.
+- Null dates not handled properly in ADO driver (it becomes 31 Dec 1969!).
+- Heinz Hombergs contributed patches for mysql MetaColumns - adding scale, made interbase MetaColumns work with firebird/interbase, and added lang/adodb-de.inc.php.
+- Added INFORMIXSERVER environment variable.
+- Added $ADODB_ANSI_PADDING_OFF for interbase/firebird.
+- PHP 5 beta 2 compat check. Foreach (Iterator) support. Exceptions support.
+
+## 4.01 - 23 Oct 2003
+
+- Fixed bug in rs2html(), tohtml.inc.php, that generated blank table cells.
+- Fixed insert_id() incorrectly generated when logsql() enabled.
+- Modified PostgreSQL _fixblobs to use list/each instead of foreach.
+- Informix ErrorNo() implemented correctly.
+- Modified several places to use list/each, including GetRowAssoc().
+- Added UserTimeStamp() to connection class.
+- Added $ADODB_ANSI_PADDING_OFF for oci8po.
+
+## 4.00 - 20 Oct 2003
+
+- Upgraded adodb-xmlschema to 1 Oct 2003 snapshot.
+- Fix to rs2html warning message. Thx to Filo.
+- Fix for odbc_mssql/mssql SQLDate(), hours was wrong.
+- Added MetaColumns and MetaPrimaryKeys for sybase. Thx to Chris Phillipson.
+- Added autoquoting to datadict for MySQL and PostgreSQL. Suggestion by Karsten Dambekalns
diff --git a/vendor/adodb/adodb-php/drivers/adodb-access.inc.php b/vendor/adodb/adodb-php/drivers/adodb-access.inc.php
new file mode 100644
index 0000000..813b71b
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-access.inc.php
@@ -0,0 +1,88 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft Access data driver. Requires ODBC. Works only on MS Windows.
+*/
+if (!defined('_ADODB_ODBC_LAYER')) {
+ if (!defined('ADODB_DIR')) die();
+
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+ if (!defined('_ADODB_ACCESS')) {
+ define('_ADODB_ACCESS',1);
+
+class ADODB_access extends ADODB_odbc {
+ var $databaseType = 'access';
+ var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
+ var $fmtDate = "#Y-m-d#";
+ var $fmtTimeStamp = "#Y-m-d h:i:sA#"; // note not comma
+ var $_bindInputArray = false; // strangely enough, setting to true does not work reliably
+ var $sysDate = "FORMAT(NOW,'yyyy-mm-dd')";
+ var $sysTimeStamp = 'NOW';
+ var $hasTransactions = false;
+ var $upperCase = 'ucase';
+
+ function __construct()
+ {
+ global $ADODB_EXTENSION;
+
+ $ADODB_EXTENSION = false;
+ parent::__construct();
+ }
+
+ function Time()
+ {
+ return time();
+ }
+
+ function BeginTrans() { return false;}
+
+ function IfNull( $field, $ifNull )
+ {
+ return " IIF(IsNull($field), $ifNull, $field) "; // if Access
+ }
+/*
+ function MetaTables()
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = odbc_tables($this->_connectionID);
+ $rs = new ADORecordSet_odbc($qid);
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) return false;
+
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+
+ $arr = $rs->GetArray();
+ //print_pre($arr);
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($arr[$i][2] && $arr[$i][3] != 'SYSTEM TABLE')
+ $arr2[] = $arr[$i][2];
+ }
+ return $arr2;
+ }*/
+}
+
+
+class ADORecordSet_access extends ADORecordSet_odbc {
+
+ var $databaseType = "access";
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}// class
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ado.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ado.inc.php
new file mode 100644
index 0000000..866f4b1
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ado.inc.php
@@ -0,0 +1,660 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft ADO data driver. Requires ADO. Works only on MS Windows.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+define("_ADODB_ADO_LAYER", 1 );
+/*--------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------*/
+
+
+class ADODB_ado extends ADOConnection {
+ var $databaseType = "ado";
+ var $_bindInputArray = false;
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $dataProvider = "ado";
+ var $hasAffectedRows = true;
+ var $adoParameterType = 201; // 201 = long varchar, 203=long wide varchar, 205 = long varbinary
+ var $_affectedRows = false;
+ var $_thisTransactions;
+ var $_cursor_type = 3; // 3=adOpenStatic,0=adOpenForwardOnly,1=adOpenKeyset,2=adOpenDynamic
+ var $_cursor_location = 3; // 2=adUseServer, 3 = adUseClient;
+ var $_lock_type = -1;
+ var $_execute_option = -1;
+ var $poorAffectedRows = true;
+ var $charPage;
+
+ function __construct()
+ {
+ $this->_affectedRows = new VARIANT;
+ }
+
+ function ServerInfo()
+ {
+ if (!empty($this->_connectionID)) $desc = $this->_connectionID->provider;
+ return array('description' => $desc, 'version' => '');
+ }
+
+ function _affectedrows()
+ {
+ if (PHP_VERSION >= 5) return $this->_affectedRows;
+
+ return $this->_affectedRows->value;
+ }
+
+ // you can also pass a connection string like this:
+ //
+ // $DB->Connect('USER ID=sa;PASSWORD=pwd;SERVER=mangrove;DATABASE=ai',false,false,'SQLOLEDB');
+ function _connect($argHostname, $argUsername, $argPassword, $argProvider= 'MSDASQL')
+ {
+ $u = 'UID';
+ $p = 'PWD';
+
+ if (!empty($this->charPage))
+ $dbc = new COM('ADODB.Connection',null,$this->charPage);
+ else
+ $dbc = new COM('ADODB.Connection');
+
+ if (! $dbc) return false;
+
+ /* special support if provider is mssql or access */
+ if ($argProvider=='mssql') {
+ $u = 'User Id'; //User parameter name for OLEDB
+ $p = 'Password';
+ $argProvider = "SQLOLEDB"; // SQL Server Provider
+
+ // not yet
+ //if ($argDatabasename) $argHostname .= ";Initial Catalog=$argDatabasename";
+
+ //use trusted conection for SQL if username not specified
+ if (!$argUsername) $argHostname .= ";Trusted_Connection=Yes";
+ } else if ($argProvider=='access')
+ $argProvider = "Microsoft.Jet.OLEDB.4.0"; // Microsoft Jet Provider
+
+ if ($argProvider) $dbc->Provider = $argProvider;
+
+ if ($argUsername) $argHostname .= ";$u=$argUsername";
+ if ($argPassword)$argHostname .= ";$p=$argPassword";
+
+ if ($this->debug) ADOConnection::outp( "Host=".$argHostname."<BR>\n version=$dbc->version");
+ // @ added below for php 4.0.1 and earlier
+ @$dbc->Open((string) $argHostname);
+
+ $this->_connectionID = $dbc;
+
+ $dbc->CursorLocation = $this->_cursor_location;
+ return $dbc->State > 0;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argProvider='MSDASQL')
+ {
+ return $this->_connect($argHostname,$argUsername,$argPassword,$argProvider);
+ }
+
+/*
+ adSchemaCatalogs = 1,
+ adSchemaCharacterSets = 2,
+ adSchemaCollations = 3,
+ adSchemaColumns = 4,
+ adSchemaCheckConstraints = 5,
+ adSchemaConstraintColumnUsage = 6,
+ adSchemaConstraintTableUsage = 7,
+ adSchemaKeyColumnUsage = 8,
+ adSchemaReferentialContraints = 9,
+ adSchemaTableConstraints = 10,
+ adSchemaColumnsDomainUsage = 11,
+ adSchemaIndexes = 12,
+ adSchemaColumnPrivileges = 13,
+ adSchemaTablePrivileges = 14,
+ adSchemaUsagePrivileges = 15,
+ adSchemaProcedures = 16,
+ adSchemaSchemata = 17,
+ adSchemaSQLLanguages = 18,
+ adSchemaStatistics = 19,
+ adSchemaTables = 20,
+ adSchemaTranslations = 21,
+ adSchemaProviderTypes = 22,
+ adSchemaViews = 23,
+ adSchemaViewColumnUsage = 24,
+ adSchemaViewTableUsage = 25,
+ adSchemaProcedureParameters = 26,
+ adSchemaForeignKeys = 27,
+ adSchemaPrimaryKeys = 28,
+ adSchemaProcedureColumns = 29,
+ adSchemaDBInfoKeywords = 30,
+ adSchemaDBInfoLiterals = 31,
+ adSchemaCubes = 32,
+ adSchemaDimensions = 33,
+ adSchemaHierarchies = 34,
+ adSchemaLevels = 35,
+ adSchemaMeasures = 36,
+ adSchemaProperties = 37,
+ adSchemaMembers = 38
+
+*/
+
+ function MetaTables($ttype = false, $showSchema = false, $mask = false)
+ {
+ $arr= array();
+ $dbc = $this->_connectionID;
+
+ $adors=@$dbc->OpenSchema(20);//tables
+ if ($adors){
+ $f = $adors->Fields(2);//table/view name
+ $t = $adors->Fields(3);//table type
+ while (!$adors->EOF){
+ $tt=substr($t->value,0,6);
+ if ($tt!='SYSTEM' && $tt !='ACCESS')
+ $arr[]=$f->value;
+ //print $f->value . ' ' . $t->value.'<br>';
+ $adors->MoveNext();
+ }
+ $adors->Close();
+ }
+
+ return $arr;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $table = strtoupper($table);
+ $arr = array();
+ $dbc = $this->_connectionID;
+
+ $adors=@$dbc->OpenSchema(4);//tables
+
+ if ($adors){
+ $t = $adors->Fields(2);//table/view name
+ while (!$adors->EOF){
+
+
+ if (strtoupper($t->Value) == $table) {
+
+ $fld = new ADOFieldObject();
+ $c = $adors->Fields(3);
+ $fld->name = $c->Value;
+ $fld->type = 'CHAR'; // cannot discover type in ADO!
+ $fld->max_length = -1;
+ $arr[strtoupper($fld->name)]=$fld;
+ }
+
+ $adors->MoveNext();
+ }
+ $adors->Close();
+ }
+ $false = false;
+ return empty($arr) ? $false : $arr;
+ }
+
+
+
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+
+ $dbc = $this->_connectionID;
+ $false = false;
+
+ // return rs
+ if ($inputarr) {
+
+ if (!empty($this->charPage))
+ $oCmd = new COM('ADODB.Command',null,$this->charPage);
+ else
+ $oCmd = new COM('ADODB.Command');
+ $oCmd->ActiveConnection = $dbc;
+ $oCmd->CommandText = $sql;
+ $oCmd->CommandType = 1;
+
+ // Map by http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdmthcreateparam.asp
+ // Check issue http://bugs.php.net/bug.php?id=40664 !!!
+ foreach ($inputarr as $val) {
+ $type = gettype($val);
+ $len=strlen($val);
+ if ($type == 'boolean')
+ $this->adoParameterType = 11;
+ else if ($type == 'integer')
+ $this->adoParameterType = 3;
+ else if ($type == 'double')
+ $this->adoParameterType = 5;
+ elseif ($type == 'string')
+ $this->adoParameterType = 202;
+ else if (($val === null) || (!defined($val)))
+ $len=1;
+ else
+ $this->adoParameterType = 130;
+
+ // name, type, direction 1 = input, len,
+ $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,$len,$val);
+
+ $oCmd->Parameters->Append($p);
+ }
+ $p = false;
+ $rs = $oCmd->Execute();
+ $e = $dbc->Errors;
+ if ($dbc->Errors->Count > 0) return $false;
+ return $rs;
+ }
+
+ $rs = @$dbc->Execute($sql,$this->_affectedRows, $this->_execute_option);
+
+ if ($dbc->Errors->Count > 0) return $false;
+ if (! $rs) return $false;
+
+ if ($rs->State == 0) {
+ $true = true;
+ return $true; // 0 = adStateClosed means no records returned
+ }
+ return $rs;
+ }
+
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+
+ if (isset($this->_thisTransactions))
+ if (!$this->_thisTransactions) return false;
+ else {
+ $o = $this->_connectionID->Properties("Transaction DDL");
+ $this->_thisTransactions = $o ? true : false;
+ if (!$o) return false;
+ }
+ @$this->_connectionID->BeginTrans();
+ $this->transCnt += 1;
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transOff) return true;
+
+ @$this->_connectionID->CommitTrans();
+ if ($this->transCnt) @$this->transCnt -= 1;
+ return true;
+ }
+ function RollbackTrans() {
+ if ($this->transOff) return true;
+ @$this->_connectionID->RollbackTrans();
+ if ($this->transCnt) @$this->transCnt -= 1;
+ return true;
+ }
+
+ /* Returns: the last error message from previous database operation */
+
+ function ErrorMsg()
+ {
+ if (!$this->_connectionID) return "No connection established";
+ $errc = $this->_connectionID->Errors;
+ if (!$errc) return "No Errors object found";
+ if ($errc->Count == 0) return '';
+ $err = $errc->Item($errc->Count-1);
+ return $err->Description;
+ }
+
+ function ErrorNo()
+ {
+ $errc = $this->_connectionID->Errors;
+ if ($errc->Count == 0) return 0;
+ $err = $errc->Item($errc->Count-1);
+ return $err->NativeError;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if ($this->_connectionID) $this->_connectionID->Close();
+ $this->_connectionID = false;
+ return true;
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_ado extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "ado";
+ var $dataProvider = "ado";
+ var $_tarr = false; // caches the types
+ var $_flds; // and field objects
+ var $canSeek = true;
+ var $hideErrors = true;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+ return parent::__construct($id,$mode);
+ }
+
+
+ // returns the field object
+ function FetchField($fieldOffset = -1) {
+ $off=$fieldOffset+1; // offsets begin at 1
+
+ $o= new ADOFieldObject();
+ $rs = $this->_queryID;
+ $f = $rs->Fields($fieldOffset);
+ $o->name = $f->Name;
+ $t = $f->Type;
+ $o->type = $this->MetaType($t);
+ $o->max_length = $f->DefinedSize;
+ $o->ado_type = $t;
+
+ //print "off=$off name=$o->name type=$o->type len=$o->max_length<br>";
+ return $o;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _initrs()
+ {
+ $rs = $this->_queryID;
+ $this->_numOfRows = $rs->RecordCount;
+
+ $f = $rs->Fields;
+ $this->_numOfFields = $f->Count;
+ }
+
+
+ // should only be used to move forward as we normally use forward-only cursors
+ function _seek($row)
+ {
+ $rs = $this->_queryID;
+ // absoluteposition doesn't work -- my maths is wrong ?
+ // $rs->AbsolutePosition->$row-2;
+ // return true;
+ if ($this->_currentRow > $row) return false;
+ @$rs->Move((integer)$row - $this->_currentRow-1); //adBookmarkFirst
+ return true;
+ }
+
+/*
+ OLEDB types
+
+ enum DBTYPEENUM
+ { DBTYPE_EMPTY = 0,
+ DBTYPE_NULL = 1,
+ DBTYPE_I2 = 2,
+ DBTYPE_I4 = 3,
+ DBTYPE_R4 = 4,
+ DBTYPE_R8 = 5,
+ DBTYPE_CY = 6,
+ DBTYPE_DATE = 7,
+ DBTYPE_BSTR = 8,
+ DBTYPE_IDISPATCH = 9,
+ DBTYPE_ERROR = 10,
+ DBTYPE_BOOL = 11,
+ DBTYPE_VARIANT = 12,
+ DBTYPE_IUNKNOWN = 13,
+ DBTYPE_DECIMAL = 14,
+ DBTYPE_UI1 = 17,
+ DBTYPE_ARRAY = 0x2000,
+ DBTYPE_BYREF = 0x4000,
+ DBTYPE_I1 = 16,
+ DBTYPE_UI2 = 18,
+ DBTYPE_UI4 = 19,
+ DBTYPE_I8 = 20,
+ DBTYPE_UI8 = 21,
+ DBTYPE_GUID = 72,
+ DBTYPE_VECTOR = 0x1000,
+ DBTYPE_RESERVED = 0x8000,
+ DBTYPE_BYTES = 128,
+ DBTYPE_STR = 129,
+ DBTYPE_WSTR = 130,
+ DBTYPE_NUMERIC = 131,
+ DBTYPE_UDT = 132,
+ DBTYPE_DBDATE = 133,
+ DBTYPE_DBTIME = 134,
+ DBTYPE_DBTIMESTAMP = 135
+
+ ADO Types
+
+ adEmpty = 0,
+ adTinyInt = 16,
+ adSmallInt = 2,
+ adInteger = 3,
+ adBigInt = 20,
+ adUnsignedTinyInt = 17,
+ adUnsignedSmallInt = 18,
+ adUnsignedInt = 19,
+ adUnsignedBigInt = 21,
+ adSingle = 4,
+ adDouble = 5,
+ adCurrency = 6,
+ adDecimal = 14,
+ adNumeric = 131,
+ adBoolean = 11,
+ adError = 10,
+ adUserDefined = 132,
+ adVariant = 12,
+ adIDispatch = 9,
+ adIUnknown = 13,
+ adGUID = 72,
+ adDate = 7,
+ adDBDate = 133,
+ adDBTime = 134,
+ adDBTimeStamp = 135,
+ adBSTR = 8,
+ adChar = 129,
+ adVarChar = 200,
+ adLongVarChar = 201,
+ adWChar = 130,
+ adVarWChar = 202,
+ adLongVarWChar = 203,
+ adBinary = 128,
+ adVarBinary = 204,
+ adLongVarBinary = 205,
+ adChapter = 136,
+ adFileTime = 64,
+ adDBFileTime = 137,
+ adPropVariant = 138,
+ adVarNumeric = 139
+*/
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ if (!is_numeric($t)) return $t;
+
+ switch ($t) {
+ case 0:
+ case 12: // variant
+ case 8: // bstr
+ case 129: //char
+ case 130: //wc
+ case 200: // varc
+ case 202:// varWC
+ case 128: // bin
+ case 204: // varBin
+ case 72: // guid
+ if ($len <= $this->blobSize) return 'C';
+
+ case 201:
+ case 203:
+ return 'X';
+ case 128:
+ case 204:
+ case 205:
+ return 'B';
+ case 7:
+ case 133: return 'D';
+
+ case 134:
+ case 135: return 'T';
+
+ case 11: return 'L';
+
+ case 16:// adTinyInt = 16,
+ case 2://adSmallInt = 2,
+ case 3://adInteger = 3,
+ case 4://adBigInt = 20,
+ case 17://adUnsignedTinyInt = 17,
+ case 18://adUnsignedSmallInt = 18,
+ case 19://adUnsignedInt = 19,
+ case 20://adUnsignedBigInt = 21,
+ return 'I';
+ default: return 'N';
+ }
+ }
+
+ // time stamp not supported yet
+ function _fetch()
+ {
+ $rs = $this->_queryID;
+ if (!$rs or $rs->EOF) {
+ $this->fields = false;
+ return false;
+ }
+ $this->fields = array();
+
+ if (!$this->_tarr) {
+ $tarr = array();
+ $flds = array();
+ for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {
+ $f = $rs->Fields($i);
+ $flds[] = $f;
+ $tarr[] = $f->Type;
+ }
+ // bind types and flds only once
+ $this->_tarr = $tarr;
+ $this->_flds = $flds;
+ }
+ $t = reset($this->_tarr);
+ $f = reset($this->_flds);
+
+ if ($this->hideErrors) $olde = error_reporting(E_ERROR|E_CORE_ERROR);// sometimes $f->value be null
+ for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {
+ //echo "<p>",$t,' ';var_dump($f->value); echo '</p>';
+ switch($t) {
+ case 135: // timestamp
+ if (!strlen((string)$f->value)) $this->fields[] = false;
+ else {
+ if (!is_numeric($f->value)) # $val = variant_date_to_timestamp($f->value);
+ // VT_DATE stores dates as (float) fractional days since 1899/12/30 00:00:00
+ $val=(float) variant_cast($f->value,VT_R8)*3600*24-2209161600;
+ else
+ $val = $f->value;
+ $this->fields[] = adodb_date('Y-m-d H:i:s',$val);
+ }
+ break;
+ case 133:// A date value (yyyymmdd)
+ if ($val = $f->value) {
+ $this->fields[] = substr($val,0,4).'-'.substr($val,4,2).'-'.substr($val,6,2);
+ } else
+ $this->fields[] = false;
+ break;
+ case 7: // adDate
+ if (!strlen((string)$f->value)) $this->fields[] = false;
+ else {
+ if (!is_numeric($f->value)) $val = variant_date_to_timestamp($f->value);
+ else $val = $f->value;
+
+ if (($val % 86400) == 0) $this->fields[] = adodb_date('Y-m-d',$val);
+ else $this->fields[] = adodb_date('Y-m-d H:i:s',$val);
+ }
+ break;
+ case 1: // null
+ $this->fields[] = false;
+ break;
+ case 6: // currency is not supported properly;
+ ADOConnection::outp( '<b>'.$f->Name.': currency type not supported by PHP</b>');
+ $this->fields[] = (float) $f->value;
+ break;
+ case 11: //BIT;
+ $val = "";
+ if(is_bool($f->value)) {
+ if($f->value==true) $val = 1;
+ else $val = 0;
+ }
+ if(is_null($f->value)) $val = null;
+
+ $this->fields[] = $val;
+ break;
+ default:
+ $this->fields[] = $f->value;
+ break;
+ }
+ //print " $f->value $t, ";
+ $f = next($this->_flds);
+ $t = next($this->_tarr);
+ } // for
+ if ($this->hideErrors) error_reporting($olde);
+ @$rs->MoveNext(); // @ needed for some versions of PHP!
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+ return true;
+ }
+
+ function NextRecordSet()
+ {
+ $rs = $this->_queryID;
+ $this->_queryID = $rs->NextRecordSet();
+ //$this->_queryID = $this->_QueryId->NextRecordSet();
+ if ($this->_queryID == null) return false;
+
+ $this->_currentRow = -1;
+ $this->_currentPage = -1;
+ $this->bind = false;
+ $this->fields = false;
+ $this->_flds = false;
+ $this->_tarr = false;
+
+ $this->_inited = false;
+ $this->Init();
+ return true;
+ }
+
+ function _close() {
+ $this->_flds = false;
+ @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk)
+ $this->_queryID = false;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ado5.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ado5.inc.php
new file mode 100644
index 0000000..a7066ad
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ado5.inc.php
@@ -0,0 +1,708 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft ADO data driver. Requires ADO. Works only on MS Windows. PHP5 compat version.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+define("_ADODB_ADO_LAYER", 1 );
+/*--------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------*/
+
+
+class ADODB_ado extends ADOConnection {
+ var $databaseType = "ado";
+ var $_bindInputArray = false;
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $dataProvider = "ado";
+ var $hasAffectedRows = true;
+ var $adoParameterType = 201; // 201 = long varchar, 203=long wide varchar, 205 = long varbinary
+ var $_affectedRows = false;
+ var $_thisTransactions;
+ var $_cursor_type = 3; // 3=adOpenStatic,0=adOpenForwardOnly,1=adOpenKeyset,2=adOpenDynamic
+ var $_cursor_location = 3; // 2=adUseServer, 3 = adUseClient;
+ var $_lock_type = -1;
+ var $_execute_option = -1;
+ var $poorAffectedRows = true;
+ var $charPage;
+
+ function __construct()
+ {
+ $this->_affectedRows = new VARIANT;
+ }
+
+ function ServerInfo()
+ {
+ if (!empty($this->_connectionID)) $desc = $this->_connectionID->provider;
+ return array('description' => $desc, 'version' => '');
+ }
+
+ function _affectedrows()
+ {
+ if (PHP_VERSION >= 5) return $this->_affectedRows;
+
+ return $this->_affectedRows->value;
+ }
+
+ // you can also pass a connection string like this:
+ //
+ // $DB->Connect('USER ID=sa;PASSWORD=pwd;SERVER=mangrove;DATABASE=ai',false,false,'SQLOLEDB');
+ function _connect($argHostname, $argUsername, $argPassword,$argDBorProvider, $argProvider= '')
+ {
+ // two modes
+ // - if $argProvider is empty, we assume that $argDBorProvider holds provider -- this is for backward compat
+ // - if $argProvider is not empty, then $argDBorProvider holds db
+
+
+ if ($argProvider) {
+ $argDatabasename = $argDBorProvider;
+ } else {
+ $argDatabasename = '';
+ if ($argDBorProvider) $argProvider = $argDBorProvider;
+ else if (stripos($argHostname,'PROVIDER') === false) /* full conn string is not in $argHostname */
+ $argProvider = 'MSDASQL';
+ }
+
+
+ try {
+ $u = 'UID';
+ $p = 'PWD';
+
+ if (!empty($this->charPage))
+ $dbc = new COM('ADODB.Connection',null,$this->charPage);
+ else
+ $dbc = new COM('ADODB.Connection');
+
+ if (! $dbc) return false;
+
+ /* special support if provider is mssql or access */
+ if ($argProvider=='mssql') {
+ $u = 'User Id'; //User parameter name for OLEDB
+ $p = 'Password';
+ $argProvider = "SQLOLEDB"; // SQL Server Provider
+
+ // not yet
+ //if ($argDatabasename) $argHostname .= ";Initial Catalog=$argDatabasename";
+
+ //use trusted conection for SQL if username not specified
+ if (!$argUsername) $argHostname .= ";Trusted_Connection=Yes";
+ } else if ($argProvider=='access')
+ $argProvider = "Microsoft.Jet.OLEDB.4.0"; // Microsoft Jet Provider
+
+ if ($argProvider) $dbc->Provider = $argProvider;
+
+ if ($argProvider) $argHostname = "PROVIDER=$argProvider;DRIVER={SQL Server};SERVER=$argHostname";
+
+
+ if ($argDatabasename) $argHostname .= ";DATABASE=$argDatabasename";
+ if ($argUsername) $argHostname .= ";$u=$argUsername";
+ if ($argPassword)$argHostname .= ";$p=$argPassword";
+
+ if ($this->debug) ADOConnection::outp( "Host=".$argHostname."<BR>\n version=$dbc->version");
+ // @ added below for php 4.0.1 and earlier
+ @$dbc->Open((string) $argHostname);
+
+ $this->_connectionID = $dbc;
+
+ $dbc->CursorLocation = $this->_cursor_location;
+ return $dbc->State > 0;
+ } catch (exception $e) {
+ if ($this->debug) echo "<pre>",$argHostname,"\n",$e,"</pre>\n";
+ }
+
+ return false;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argProvider='MSDASQL')
+ {
+ return $this->_connect($argHostname,$argUsername,$argPassword,$argProvider);
+ }
+
+/*
+ adSchemaCatalogs = 1,
+ adSchemaCharacterSets = 2,
+ adSchemaCollations = 3,
+ adSchemaColumns = 4,
+ adSchemaCheckConstraints = 5,
+ adSchemaConstraintColumnUsage = 6,
+ adSchemaConstraintTableUsage = 7,
+ adSchemaKeyColumnUsage = 8,
+ adSchemaReferentialContraints = 9,
+ adSchemaTableConstraints = 10,
+ adSchemaColumnsDomainUsage = 11,
+ adSchemaIndexes = 12,
+ adSchemaColumnPrivileges = 13,
+ adSchemaTablePrivileges = 14,
+ adSchemaUsagePrivileges = 15,
+ adSchemaProcedures = 16,
+ adSchemaSchemata = 17,
+ adSchemaSQLLanguages = 18,
+ adSchemaStatistics = 19,
+ adSchemaTables = 20,
+ adSchemaTranslations = 21,
+ adSchemaProviderTypes = 22,
+ adSchemaViews = 23,
+ adSchemaViewColumnUsage = 24,
+ adSchemaViewTableUsage = 25,
+ adSchemaProcedureParameters = 26,
+ adSchemaForeignKeys = 27,
+ adSchemaPrimaryKeys = 28,
+ adSchemaProcedureColumns = 29,
+ adSchemaDBInfoKeywords = 30,
+ adSchemaDBInfoLiterals = 31,
+ adSchemaCubes = 32,
+ adSchemaDimensions = 33,
+ adSchemaHierarchies = 34,
+ adSchemaLevels = 35,
+ adSchemaMeasures = 36,
+ adSchemaProperties = 37,
+ adSchemaMembers = 38
+
+*/
+
+ function MetaTables($ttype = false, $showSchema = false, $mask = false)
+ {
+ $arr= array();
+ $dbc = $this->_connectionID;
+
+ $adors=@$dbc->OpenSchema(20);//tables
+ if ($adors){
+ $f = $adors->Fields(2);//table/view name
+ $t = $adors->Fields(3);//table type
+ while (!$adors->EOF){
+ $tt=substr($t->value,0,6);
+ if ($tt!='SYSTEM' && $tt !='ACCESS')
+ $arr[]=$f->value;
+ //print $f->value . ' ' . $t->value.'<br>';
+ $adors->MoveNext();
+ }
+ $adors->Close();
+ }
+
+ return $arr;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $table = strtoupper($table);
+ $arr= array();
+ $dbc = $this->_connectionID;
+
+ $adors=@$dbc->OpenSchema(4);//tables
+
+ if ($adors){
+ $t = $adors->Fields(2);//table/view name
+ while (!$adors->EOF){
+
+
+ if (strtoupper($t->Value) == $table) {
+
+ $fld = new ADOFieldObject();
+ $c = $adors->Fields(3);
+ $fld->name = $c->Value;
+ $fld->type = 'CHAR'; // cannot discover type in ADO!
+ $fld->max_length = -1;
+ $arr[strtoupper($fld->name)]=$fld;
+ }
+
+ $adors->MoveNext();
+ }
+ $adors->Close();
+ }
+
+ return $arr;
+ }
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ try { // In PHP5, all COM errors are exceptions, so to maintain old behaviour...
+
+ $dbc = $this->_connectionID;
+
+ // return rs
+
+ $false = false;
+
+ if ($inputarr) {
+
+ if (!empty($this->charPage))
+ $oCmd = new COM('ADODB.Command',null,$this->charPage);
+ else
+ $oCmd = new COM('ADODB.Command');
+ $oCmd->ActiveConnection = $dbc;
+ $oCmd->CommandText = $sql;
+ $oCmd->CommandType = 1;
+
+ foreach ($inputarr as $val) {
+ $type = gettype($val);
+ $len=strlen($val);
+ if ($type == 'boolean')
+ $this->adoParameterType = 11;
+ else if ($type == 'integer')
+ $this->adoParameterType = 3;
+ else if ($type == 'double')
+ $this->adoParameterType = 5;
+ elseif ($type == 'string')
+ $this->adoParameterType = 202;
+ else if (($val === null) || (!defined($val)))
+ $len=1;
+ else
+ $this->adoParameterType = 130;
+
+ // name, type, direction 1 = input, len,
+ $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,$len,$val);
+
+ $oCmd->Parameters->Append($p);
+ }
+
+ $p = false;
+ $rs = $oCmd->Execute();
+ $e = $dbc->Errors;
+ if ($dbc->Errors->Count > 0) return $false;
+ return $rs;
+ }
+
+ $rs = @$dbc->Execute($sql,$this->_affectedRows, $this->_execute_option);
+
+ if ($dbc->Errors->Count > 0) return $false;
+ if (! $rs) return $false;
+
+ if ($rs->State == 0) {
+ $true = true;
+ return $true; // 0 = adStateClosed means no records returned
+ }
+ return $rs;
+
+ } catch (exception $e) {
+
+ }
+ return $false;
+ }
+
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+
+ if (isset($this->_thisTransactions))
+ if (!$this->_thisTransactions) return false;
+ else {
+ $o = $this->_connectionID->Properties("Transaction DDL");
+ $this->_thisTransactions = $o ? true : false;
+ if (!$o) return false;
+ }
+ @$this->_connectionID->BeginTrans();
+ $this->transCnt += 1;
+ return true;
+ }
+ function CommitTrans($ok=true)
+ {
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transOff) return true;
+
+ @$this->_connectionID->CommitTrans();
+ if ($this->transCnt) @$this->transCnt -= 1;
+ return true;
+ }
+ function RollbackTrans() {
+ if ($this->transOff) return true;
+ @$this->_connectionID->RollbackTrans();
+ if ($this->transCnt) @$this->transCnt -= 1;
+ return true;
+ }
+
+ /* Returns: the last error message from previous database operation */
+
+ function ErrorMsg()
+ {
+ if (!$this->_connectionID) return "No connection established";
+ $errmsg = '';
+
+ try {
+ $errc = $this->_connectionID->Errors;
+ if (!$errc) return "No Errors object found";
+ if ($errc->Count == 0) return '';
+ $err = $errc->Item($errc->Count-1);
+ $errmsg = $err->Description;
+ }catch(exception $e) {
+ }
+ return $errmsg;
+ }
+
+ function ErrorNo()
+ {
+ $errc = $this->_connectionID->Errors;
+ if ($errc->Count == 0) return 0;
+ $err = $errc->Item($errc->Count-1);
+ return $err->NativeError;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if ($this->_connectionID) $this->_connectionID->Close();
+ $this->_connectionID = false;
+ return true;
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_ado extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "ado";
+ var $dataProvider = "ado";
+ var $_tarr = false; // caches the types
+ var $_flds; // and field objects
+ var $canSeek = true;
+ var $hideErrors = true;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+ return parent::__construct($id,$mode);
+ }
+
+
+ // returns the field object
+ function FetchField($fieldOffset = -1) {
+ $off=$fieldOffset+1; // offsets begin at 1
+
+ $o= new ADOFieldObject();
+ $rs = $this->_queryID;
+ if (!$rs) return false;
+
+ $f = $rs->Fields($fieldOffset);
+ $o->name = $f->Name;
+ $t = $f->Type;
+ $o->type = $this->MetaType($t);
+ $o->max_length = $f->DefinedSize;
+ $o->ado_type = $t;
+
+
+ //print "off=$off name=$o->name type=$o->type len=$o->max_length<br>";
+ return $o;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _initrs()
+ {
+ $rs = $this->_queryID;
+
+ try {
+ $this->_numOfRows = $rs->RecordCount;
+ } catch (Exception $e) {
+ $this->_numOfRows = -1;
+ }
+ $f = $rs->Fields;
+ $this->_numOfFields = $f->Count;
+ }
+
+
+ // should only be used to move forward as we normally use forward-only cursors
+ function _seek($row)
+ {
+ $rs = $this->_queryID;
+ // absoluteposition doesn't work -- my maths is wrong ?
+ // $rs->AbsolutePosition->$row-2;
+ // return true;
+ if ($this->_currentRow > $row) return false;
+ @$rs->Move((integer)$row - $this->_currentRow-1); //adBookmarkFirst
+ return true;
+ }
+
+/*
+ OLEDB types
+
+ enum DBTYPEENUM
+ { DBTYPE_EMPTY = 0,
+ DBTYPE_NULL = 1,
+ DBTYPE_I2 = 2,
+ DBTYPE_I4 = 3,
+ DBTYPE_R4 = 4,
+ DBTYPE_R8 = 5,
+ DBTYPE_CY = 6,
+ DBTYPE_DATE = 7,
+ DBTYPE_BSTR = 8,
+ DBTYPE_IDISPATCH = 9,
+ DBTYPE_ERROR = 10,
+ DBTYPE_BOOL = 11,
+ DBTYPE_VARIANT = 12,
+ DBTYPE_IUNKNOWN = 13,
+ DBTYPE_DECIMAL = 14,
+ DBTYPE_UI1 = 17,
+ DBTYPE_ARRAY = 0x2000,
+ DBTYPE_BYREF = 0x4000,
+ DBTYPE_I1 = 16,
+ DBTYPE_UI2 = 18,
+ DBTYPE_UI4 = 19,
+ DBTYPE_I8 = 20,
+ DBTYPE_UI8 = 21,
+ DBTYPE_GUID = 72,
+ DBTYPE_VECTOR = 0x1000,
+ DBTYPE_RESERVED = 0x8000,
+ DBTYPE_BYTES = 128,
+ DBTYPE_STR = 129,
+ DBTYPE_WSTR = 130,
+ DBTYPE_NUMERIC = 131,
+ DBTYPE_UDT = 132,
+ DBTYPE_DBDATE = 133,
+ DBTYPE_DBTIME = 134,
+ DBTYPE_DBTIMESTAMP = 135
+
+ ADO Types
+
+ adEmpty = 0,
+ adTinyInt = 16,
+ adSmallInt = 2,
+ adInteger = 3,
+ adBigInt = 20,
+ adUnsignedTinyInt = 17,
+ adUnsignedSmallInt = 18,
+ adUnsignedInt = 19,
+ adUnsignedBigInt = 21,
+ adSingle = 4,
+ adDouble = 5,
+ adCurrency = 6,
+ adDecimal = 14,
+ adNumeric = 131,
+ adBoolean = 11,
+ adError = 10,
+ adUserDefined = 132,
+ adVariant = 12,
+ adIDispatch = 9,
+ adIUnknown = 13,
+ adGUID = 72,
+ adDate = 7,
+ adDBDate = 133,
+ adDBTime = 134,
+ adDBTimeStamp = 135,
+ adBSTR = 8,
+ adChar = 129,
+ adVarChar = 200,
+ adLongVarChar = 201,
+ adWChar = 130,
+ adVarWChar = 202,
+ adLongVarWChar = 203,
+ adBinary = 128,
+ adVarBinary = 204,
+ adLongVarBinary = 205,
+ adChapter = 136,
+ adFileTime = 64,
+ adDBFileTime = 137,
+ adPropVariant = 138,
+ adVarNumeric = 139
+*/
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ if (!is_numeric($t)) return $t;
+
+ switch ($t) {
+ case 0:
+ case 12: // variant
+ case 8: // bstr
+ case 129: //char
+ case 130: //wc
+ case 200: // varc
+ case 202:// varWC
+ case 128: // bin
+ case 204: // varBin
+ case 72: // guid
+ if ($len <= $this->blobSize) return 'C';
+
+ case 201:
+ case 203:
+ return 'X';
+ case 128:
+ case 204:
+ case 205:
+ return 'B';
+ case 7:
+ case 133: return 'D';
+
+ case 134:
+ case 135: return 'T';
+
+ case 11: return 'L';
+
+ case 16:// adTinyInt = 16,
+ case 2://adSmallInt = 2,
+ case 3://adInteger = 3,
+ case 4://adBigInt = 20,
+ case 17://adUnsignedTinyInt = 17,
+ case 18://adUnsignedSmallInt = 18,
+ case 19://adUnsignedInt = 19,
+ case 20://adUnsignedBigInt = 21,
+ return 'I';
+ default: return 'N';
+ }
+ }
+
+ // time stamp not supported yet
+ function _fetch()
+ {
+ $rs = $this->_queryID;
+ if (!$rs or $rs->EOF) {
+ $this->fields = false;
+ return false;
+ }
+ $this->fields = array();
+
+ if (!$this->_tarr) {
+ $tarr = array();
+ $flds = array();
+ for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {
+ $f = $rs->Fields($i);
+ $flds[] = $f;
+ $tarr[] = $f->Type;
+ }
+ // bind types and flds only once
+ $this->_tarr = $tarr;
+ $this->_flds = $flds;
+ }
+ $t = reset($this->_tarr);
+ $f = reset($this->_flds);
+
+ if ($this->hideErrors) $olde = error_reporting(E_ERROR|E_CORE_ERROR);// sometimes $f->value be null
+ for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {
+ //echo "<p>",$t,' ';var_dump($f->value); echo '</p>';
+ switch($t) {
+ case 135: // timestamp
+ if (!strlen((string)$f->value)) $this->fields[] = false;
+ else {
+ if (!is_numeric($f->value)) # $val = variant_date_to_timestamp($f->value);
+ // VT_DATE stores dates as (float) fractional days since 1899/12/30 00:00:00
+ $val= (float) variant_cast($f->value,VT_R8)*3600*24-2209161600;
+ else
+ $val = $f->value;
+ $this->fields[] = adodb_date('Y-m-d H:i:s',$val);
+ }
+ break;
+ case 133:// A date value (yyyymmdd)
+ if ($val = $f->value) {
+ $this->fields[] = substr($val,0,4).'-'.substr($val,4,2).'-'.substr($val,6,2);
+ } else
+ $this->fields[] = false;
+ break;
+ case 7: // adDate
+ if (!strlen((string)$f->value)) $this->fields[] = false;
+ else {
+ if (!is_numeric($f->value)) $val = variant_date_to_timestamp($f->value);
+ else $val = $f->value;
+
+ if (($val % 86400) == 0) $this->fields[] = adodb_date('Y-m-d',$val);
+ else $this->fields[] = adodb_date('Y-m-d H:i:s',$val);
+ }
+ break;
+ case 1: // null
+ $this->fields[] = false;
+ break;
+ case 20:
+ case 21: // bigint (64 bit)
+ $this->fields[] = (float) $f->value; // if 64 bit PHP, could use (int)
+ break;
+ case 6: // currency is not supported properly;
+ ADOConnection::outp( '<b>'.$f->Name.': currency type not supported by PHP</b>');
+ $this->fields[] = (float) $f->value;
+ break;
+ case 11: //BIT;
+ $val = "";
+ if(is_bool($f->value)) {
+ if($f->value==true) $val = 1;
+ else $val = 0;
+ }
+ if(is_null($f->value)) $val = null;
+
+ $this->fields[] = $val;
+ break;
+ default:
+ $this->fields[] = $f->value;
+ break;
+ }
+ //print " $f->value $t, ";
+ $f = next($this->_flds);
+ $t = next($this->_tarr);
+ } // for
+ if ($this->hideErrors) error_reporting($olde);
+ @$rs->MoveNext(); // @ needed for some versions of PHP!
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+ return true;
+ }
+
+ function NextRecordSet()
+ {
+ $rs = $this->_queryID;
+ $this->_queryID = $rs->NextRecordSet();
+ //$this->_queryID = $this->_QueryId->NextRecordSet();
+ if ($this->_queryID == null) return false;
+
+ $this->_currentRow = -1;
+ $this->_currentPage = -1;
+ $this->bind = false;
+ $this->fields = false;
+ $this->_flds = false;
+ $this->_tarr = false;
+
+ $this->_inited = false;
+ $this->Init();
+ return true;
+ }
+
+ function _close() {
+ $this->_flds = false;
+ try {
+ @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk)
+ } catch (Exception $e) {
+ }
+ $this->_queryID = false;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ado_access.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ado_access.inc.php
new file mode 100644
index 0000000..0e26499
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ado_access.inc.php
@@ -0,0 +1,50 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+Released under both BSD license and Lesser GPL library license.
+Whenever there is any discrepancy between the two licenses,
+the BSD license will take precedence. See License.txt.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft Access ADO data driver. Requires ADO and ODBC. Works only on MS Windows.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ADO_LAYER')) {
+ if (PHP_VERSION >= 5) include(ADODB_DIR."/drivers/adodb-ado5.inc.php");
+ else include(ADODB_DIR."/drivers/adodb-ado.inc.php");
+}
+
+class ADODB_ado_access extends ADODB_ado {
+ var $databaseType = 'ado_access';
+ var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
+ var $fmtDate = "#Y-m-d#";
+ var $fmtTimeStamp = "#Y-m-d h:i:sA#";// note no comma
+ var $sysDate = "FORMAT(NOW,'yyyy-mm-dd')";
+ var $sysTimeStamp = 'NOW';
+ var $upperCase = 'ucase';
+
+ /*function BeginTrans() { return false;}
+
+ function CommitTrans() { return false;}
+
+ function RollbackTrans() { return false;}*/
+
+}
+
+
+class ADORecordSet_ado_access extends ADORecordSet_ado {
+
+ var $databaseType = "ado_access";
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ado_mssql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ado_mssql.inc.php
new file mode 100644
index 0000000..dfb8035
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ado_mssql.inc.php
@@ -0,0 +1,150 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft SQL Server ADO data driver. Requires ADO and MSSQL client.
+ Works only on MS Windows.
+
+ Warning: Some versions of PHP (esp PHP4) leak memory when ADO/COM is used.
+ Please check http://bugs.php.net/ for more info.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ADO_LAYER')) {
+ if (PHP_VERSION >= 5) include(ADODB_DIR."/drivers/adodb-ado5.inc.php");
+ else include(ADODB_DIR."/drivers/adodb-ado.inc.php");
+}
+
+
+class ADODB_ado_mssql extends ADODB_ado {
+ var $databaseType = 'ado_mssql';
+ var $hasTop = 'top';
+ var $hasInsertID = true;
+ var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ var $sysTimeStamp = 'GetDate()';
+ var $leftOuter = '*=';
+ var $rightOuter = '=*';
+ var $ansiOuter = true; // for mssql7 or later
+ var $substr = "substring";
+ var $length = 'len';
+ var $_dropSeqSQL = "drop table %s";
+
+ //var $_inTransaction = 1; // always open recordsets, so no transaction problems.
+
+ function _insertid()
+ {
+ return $this->GetOne('select SCOPE_IDENTITY()');
+ }
+
+ function _affectedrows()
+ {
+ return $this->GetOne('select @@rowcount');
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET TRANSACTION ".$transaction_mode);
+ }
+
+ function qstr($s,$magic_quotes=false)
+ {
+ $s = ADOConnection::qstr($s, $magic_quotes);
+ return str_replace("\0", "\\\\000", $s);
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $table = strtoupper($table);
+ $arr= array();
+ $dbc = $this->_connectionID;
+
+ $osoptions = array();
+ $osoptions[0] = null;
+ $osoptions[1] = null;
+ $osoptions[2] = $table;
+ $osoptions[3] = null;
+
+ $adors=@$dbc->OpenSchema(4, $osoptions);//tables
+
+ if ($adors){
+ while (!$adors->EOF){
+ $fld = new ADOFieldObject();
+ $c = $adors->Fields(3);
+ $fld->name = $c->Value;
+ $fld->type = 'CHAR'; // cannot discover type in ADO!
+ $fld->max_length = -1;
+ $arr[strtoupper($fld->name)]=$fld;
+
+ $adors->MoveNext();
+ }
+ $adors->Close();
+ }
+ $false = false;
+ return empty($arr) ? $false : $arr;
+ }
+
+ function CreateSequence($seq='adodbseq',$start=1)
+ {
+
+ $this->Execute('BEGIN TRANSACTION adodbseq');
+ $start -= 1;
+ $this->Execute("create table $seq (id float(53))");
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ $this->Execute('ROLLBACK TRANSACTION adodbseq');
+ return false;
+ }
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return true;
+ }
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ //$this->debug=1;
+ $this->Execute('BEGIN TRANSACTION adodbseq');
+ $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
+ if (!$ok) {
+ $this->Execute("create table $seq (id float(53))");
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ $this->Execute('ROLLBACK TRANSACTION adodbseq');
+ return false;
+ }
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return $start;
+ }
+ $num = $this->GetOne("select id from $seq");
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return $num;
+
+ // in old implementation, pre 1.90, we returned GUID...
+ //return $this->GetOne("SELECT CONVERT(varchar(255), NEWID()) AS 'Char'");
+ }
+
+ } // end class
+
+ class ADORecordSet_ado_mssql extends ADORecordSet_ado {
+
+ var $databaseType = 'ado_mssql';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ads.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ads.inc.php
new file mode 100644
index 0000000..8d31b21
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ads.inc.php
@@ -0,0 +1,776 @@
+<?php
+/*
+ (c) 2000-2014 John Lim (jlim#natsoft.com.my). All rights reserved.
+ Portions Copyright (c) 2007-2009, iAnywhere Solutions, Inc.
+ All rights reserved. All unpublished rights reserved.
+
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+Set tabs to 4 for best viewing.
+
+
+NOTE: This driver requires the Advantage PHP client libraries, which
+ can be downloaded for free via:
+ http://devzone.advantagedatabase.com/dz/content.aspx?key=20
+
+DELPHI FOR PHP USERS:
+ The following steps can be taken to utilize this driver from the
+ CodeGear Delphi for PHP product:
+ 1 - See note above, download and install the Advantage PHP client.
+ 2 - Copy the following files to the Delphi for PHP\X.X\php\ext directory:
+ ace32.dll
+ axcws32.dll
+ adsloc32.dll
+ php_advantage.dll (rename the existing php_advantage.dll.5.x.x file)
+ 3 - Add the following line to the Delphi for PHP\X.X\php\php.ini.template file:
+ extension=php_advantage.dll
+ 4 - To use: enter "ads" as the DriverName on a connection component, and set
+ a Host property similar to "DataDirectory=c:\". See the Advantage PHP
+ help file topic for ads_connect for details on connection path options
+ and formatting.
+ 5 - (optional) - Modify the Delphi for PHP\X.X\vcl\packages\database.packages.php
+ file and add ads to the list of strings returned when registering the
+ Database object's DriverName property.
+
+*/
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+ define("_ADODB_ADS_LAYER", 2 );
+
+/*--------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------*/
+
+
+class ADODB_ads extends ADOConnection {
+ var $databaseType = "ads";
+ var $fmt = "'m-d-Y'";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $concat_operator = '';
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $dataProvider = "ads";
+ var $hasAffectedRows = true;
+ var $binmode = ODBC_BINMODE_RETURN;
+ var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
+ // breaking backward-compat
+ //var $longreadlen = 8000; // default number of chars to return for a Blob/Long field
+ var $_bindInputArray = false;
+ var $curmode = SQL_CUR_USE_DRIVER; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L
+ var $_genSeqSQL = "create table %s (id integer)";
+ var $_autocommit = true;
+ var $_haserrorfunctions = true;
+ var $_has_stupid_odbc_fetch_api_change = true;
+ var $_lastAffectedRows = 0;
+ var $uCaseTables = true; // for meta* functions, uppercase table names
+
+
+ function __construct()
+ {
+ $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050;
+ $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200;
+ }
+
+ // returns true or false
+ function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('ads_connect')) return null;
+
+ if ($this->debug && $argDatabasename && $this->databaseType != 'vfp') {
+ ADOConnection::outp("For Advantage Connect(), $argDatabasename is not used. Place dsn in 1st parameter.");
+ }
+ $last_php_error = $this->resetLastError();
+ if ($this->curmode === false) $this->_connectionID = ads_connect($argDSN,$argUsername,$argPassword);
+ else $this->_connectionID = ads_connect($argDSN,$argUsername,$argPassword,$this->curmode);
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ return $this->_connectionID != false;
+ }
+
+ // returns true or false
+ function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('ads_connect')) return null;
+
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = '';
+ if ($this->debug && $argDatabasename) {
+ ADOConnection::outp("For PConnect(), $argDatabasename is not used. Place dsn in 1st parameter.");
+ }
+ // print "dsn=$argDSN u=$argUsername p=$argPassword<br>"; flush();
+ if ($this->curmode === false) $this->_connectionID = ads_connect($argDSN,$argUsername,$argPassword);
+ else $this->_connectionID = ads_pconnect($argDSN,$argUsername,$argPassword,$this->curmode);
+
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ if ($this->_connectionID && $this->autoRollback) @ads_rollback($this->_connectionID);
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ return $this->_connectionID != false;
+ }
+
+ // returns the Server version and Description
+ function ServerInfo()
+ {
+
+ if (!empty($this->host) && ADODB_PHPVER >= 0x4300) {
+ $stmt = $this->Prepare('EXECUTE PROCEDURE sp_mgGetInstallInfo()');
+ $res = $this->Execute($stmt);
+ if(!$res)
+ print $this->ErrorMsg();
+ else{
+ $ret["version"]= $res->fields[3];
+ $ret["description"]="Advantage Database Server";
+ return $ret;
+ }
+ }
+ else {
+ return ADOConnection::ServerInfo();
+ }
+ }
+
+
+ // returns true or false
+ function CreateSequence($seqname = 'adodbseq', $start = 1)
+ {
+ $res = $this->Execute("CREATE TABLE $seqname ( ID autoinc( 1 ) ) IN DATABASE");
+ if(!$res){
+ print $this->ErrorMsg();
+ return false;
+ }
+ else
+ return true;
+
+ }
+
+ // returns true or false
+ function DropSequence($seqname = 'adodbseq')
+ {
+ $res = $this->Execute("DROP TABLE $seqname");
+ if(!$res){
+ print $this->ErrorMsg();
+ return false;
+ }
+ else
+ return true;
+ }
+
+
+ // returns the generated ID or false
+ // checks if the table already exists, else creates the table and inserts a record into the table
+ // and gets the ID number of the last inserted record.
+ function GenID($seqname = 'adodbseq', $start = 1)
+ {
+ $go = $this->Execute("select * from $seqname");
+ if (!$go){
+ $res = $this->Execute("CREATE TABLE $seqname ( ID autoinc( 1 ) ) IN DATABASE");
+ if(!res){
+ print $this->ErrorMsg();
+ return false;
+ }
+ }
+ $res = $this->Execute("INSERT INTO $seqname VALUES( DEFAULT )");
+ if(!$res){
+ print $this->ErrorMsg();
+ return false;
+ }
+ else{
+ $gen = $this->Execute("SELECT LastAutoInc( STATEMENT ) FROM system.iota");
+ $ret = $gen->fields[0];
+ return $ret;
+ }
+
+ }
+
+
+
+
+ function ErrorMsg()
+ {
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+ if (empty($this->_connectionID)) return @ads_errormsg();
+ return @ads_errormsg($this->_connectionID);
+ } else return ADOConnection::ErrorMsg();
+ }
+
+
+ function ErrorNo()
+ {
+
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorCode !== false) {
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode;
+ }
+
+ if (empty($this->_connectionID)) $e = @ads_error();
+ else $e = @ads_error($this->_connectionID);
+
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ // so we check and patch
+ if (strlen($e)<=2) return 0;
+ return $e;
+ } else return ADOConnection::ErrorNo();
+ }
+
+
+
+ function BeginTrans()
+ {
+ if (!$this->hasTransactions) return false;
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->_autocommit = false;
+ return ads_autocommit($this->_connectionID,false);
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = ads_commit($this->_connectionID);
+ ads_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = ads_rollback($this->_connectionID);
+ ads_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+
+ // Returns tables,Views or both on succesfull execution. Returns
+ // tables by default on succesfull execustion.
+ function &MetaTables($ttype = false, $showSchema = false, $mask = false)
+ {
+ $recordSet1 = $this->Execute("select * from system.tables");
+ if(!$recordSet1){
+ print $this->ErrorMsg();
+ return false;
+ }
+ $recordSet2 = $this->Execute("select * from system.views");
+ if(!$recordSet2){
+ print $this->ErrorMsg();
+ return false;
+ }
+ $i=0;
+ while (!$recordSet1->EOF){
+ $arr["$i"] = $recordSet1->fields[0];
+ $recordSet1->MoveNext();
+ $i=$i+1;
+ }
+ if($ttype=='FALSE'){
+ while (!$recordSet2->EOF){
+ $arr["$i"] = $recordSet2->fields[0];
+ $recordSet2->MoveNext();
+ $i=$i+1;
+ }
+ return $arr;
+ }
+ elseif($ttype=='VIEWS'){
+ while (!$recordSet2->EOF){
+ $arrV["$i"] = $recordSet2->fields[0];
+ $recordSet2->MoveNext();
+ $i=$i+1;
+ }
+ return $arrV;
+ }
+ else{
+ return $arr;
+ }
+
+ }
+
+ function &MetaPrimaryKeys($table, $owner = false)
+ {
+ $recordSet = $this->Execute("select table_primary_key from system.tables where name='$table'");
+ if(!$recordSet){
+ print $this->ErrorMsg();
+ return false;
+ }
+ $i=0;
+ while (!$recordSet->EOF){
+ $arr["$i"] = $recordSet->fields[0];
+ $recordSet->MoveNext();
+ $i=$i+1;
+ }
+ return $arr;
+ }
+
+/*
+See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcdatetime_data_type_changes.asp
+/ SQL data type codes /
+#define SQL_UNKNOWN_TYPE 0
+#define SQL_CHAR 1
+#define SQL_NUMERIC 2
+#define SQL_DECIMAL 3
+#define SQL_INTEGER 4
+#define SQL_SMALLINT 5
+#define SQL_FLOAT 6
+#define SQL_REAL 7
+#define SQL_DOUBLE 8
+#if (ODBCVER >= 0x0300)
+#define SQL_DATETIME 9
+#endif
+#define SQL_VARCHAR 12
+
+
+/ One-parameter shortcuts for date/time data types /
+#if (ODBCVER >= 0x0300)
+#define SQL_TYPE_DATE 91
+#define SQL_TYPE_TIME 92
+#define SQL_TYPE_TIMESTAMP 93
+
+#define SQL_UNICODE (-95)
+#define SQL_UNICODE_VARCHAR (-96)
+#define SQL_UNICODE_LONGVARCHAR (-97)
+*/
+ function ODBCTypes($t)
+ {
+ switch ((integer)$t) {
+ case 1:
+ case 12:
+ case 0:
+ case -95:
+ case -96:
+ return 'C';
+ case -97:
+ case -1: //text
+ return 'X';
+ case -4: //image
+ return 'B';
+
+ case 9:
+ case 91:
+ return 'D';
+
+ case 10:
+ case 11:
+ case 92:
+ case 93:
+ return 'T';
+
+ case 4:
+ case 5:
+ case -6:
+ return 'I';
+
+ case -11: // uniqidentifier
+ return 'R';
+ case -7: //bit
+ return 'L';
+
+ default:
+ return 'N';
+ }
+ }
+
+ function &MetaColumns($table, $normalize = true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ /*if (false) { // after testing, confirmed that the following does not work becoz of a bug
+ $qid2 = ads_tables($this->_connectionID);
+ $rs = new ADORecordSet_ads($qid2);
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) return false;
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+ $rs->_fetch();
+
+ while (!$rs->EOF) {
+ if ($table == strtoupper($rs->fields[2])) {
+ $q = $rs->fields[0];
+ $o = $rs->fields[1];
+ break;
+ }
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $qid = ads_columns($this->_connectionID,$q,$o,strtoupper($table),'%');
+ } */
+
+ switch ($this->databaseType) {
+ case 'access':
+ case 'vfp':
+ $qid = ads_columns($this->_connectionID);#,'%','',strtoupper($table),'%');
+ break;
+
+
+ case 'db2':
+ $colname = "%";
+ $qid = ads_columns($this->_connectionID, "", $schema, $table, $colname);
+ break;
+
+ default:
+ $qid = @ads_columns($this->_connectionID,'%','%',strtoupper($table),'%');
+ if (empty($qid)) $qid = ads_columns($this->_connectionID);
+ break;
+ }
+ if (empty($qid)) return $false;
+
+ $rs = new ADORecordSet_ads($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return $false;
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+ $rs->_fetch();
+
+ $retarr = array();
+
+ /*
+ $rs->fields indices
+ 0 TABLE_QUALIFIER
+ 1 TABLE_SCHEM
+ 2 TABLE_NAME
+ 3 COLUMN_NAME
+ 4 DATA_TYPE
+ 5 TYPE_NAME
+ 6 PRECISION
+ 7 LENGTH
+ 8 SCALE
+ 9 RADIX
+ 10 NULLABLE
+ 11 REMARKS
+ */
+ while (!$rs->EOF) {
+ // adodb_pr($rs->fields);
+ if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[3];
+ $fld->type = $this->ODBCTypes($rs->fields[4]);
+
+ // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
+ // access uses precision to store length for char/varchar
+ if ($fld->type == 'C' or $fld->type == 'X') {
+ if ($this->databaseType == 'access')
+ $fld->max_length = $rs->fields[6];
+ else if ($rs->fields[4] <= -95) // UNICODE
+ $fld->max_length = $rs->fields[7]/2;
+ else
+ $fld->max_length = $rs->fields[7];
+ } else
+ $fld->max_length = $rs->fields[7];
+ $fld->not_null = !empty($rs->fields[10]);
+ $fld->scale = $rs->fields[8];
+ $retarr[strtoupper($fld->name)] = $fld;
+ } else if (sizeof($retarr)>0)
+ break;
+ $rs->MoveNext();
+ }
+ $rs->Close(); //-- crashes 4.03pl1 -- why?
+
+ if (empty($retarr)) $retarr = false;
+ return $retarr;
+ }
+
+ // Returns an array of columns names for a given table
+ function &MetaColumnNames($table, $numIndexes = false, $useattnum = false)
+ {
+ $recordSet = $this->Execute("select name from system.columns where parent='$table'");
+ if(!$recordSet){
+ print $this->ErrorMsg();
+ return false;
+ }
+ else{
+ $i=0;
+ while (!$recordSet->EOF){
+ $arr["FIELD$i"] = $recordSet->fields[0];
+ $recordSet->MoveNext();
+ $i=$i+1;
+ }
+ return $arr;
+ }
+ }
+
+
+ function Prepare($sql)
+ {
+ if (! $this->_bindInputArray) return $sql; // no binding
+ $stmt = ads_prepare($this->_connectionID,$sql);
+ if (!$stmt) {
+ // we don't know whether odbc driver is parsing prepared stmts, so just return sql
+ return $sql;
+ }
+ return array($sql,$stmt,false);
+ }
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = '';
+
+ if ($inputarr) {
+ if (is_array($sql)) {
+ $stmtid = $sql[1];
+ } else {
+ $stmtid = ads_prepare($this->_connectionID,$sql);
+
+ if ($stmtid == false) {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ return false;
+ }
+ }
+
+ if (! ads_execute($stmtid,$inputarr)) {
+ //@ads_free_result($stmtid);
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = ads_errormsg();
+ $this->_errorCode = ads_error();
+ }
+ return false;
+ }
+
+ } else if (is_array($sql)) {
+ $stmtid = $sql[1];
+ if (!ads_execute($stmtid)) {
+ //@ads_free_result($stmtid);
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = ads_errormsg();
+ $this->_errorCode = ads_error();
+ }
+ return false;
+ }
+ } else
+ {
+
+ $stmtid = ads_exec($this->_connectionID,$sql);
+
+ }
+
+ $this->_lastAffectedRows = 0;
+
+ if ($stmtid) {
+
+ if (@ads_num_fields($stmtid) == 0) {
+ $this->_lastAffectedRows = ads_num_rows($stmtid);
+ $stmtid = true;
+
+ } else {
+
+ $this->_lastAffectedRows = 0;
+ ads_binmode($stmtid,$this->binmode);
+ ads_longreadlen($stmtid,$this->maxblobsize);
+
+ }
+
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = '';
+ $this->_errorCode = 0;
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ } else {
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = ads_errormsg();
+ $this->_errorCode = ads_error();
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ }
+
+ return $stmtid;
+
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ $last_php_error = $this->resetLastError();
+ $sql = "UPDATE $table SET $column=? WHERE $where";
+ $stmtid = ads_prepare($this->_connectionID,$sql);
+ if ($stmtid == false){
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ return false;
+ }
+ if (! ads_execute($stmtid,array($val),array(SQL_BINARY) )){
+ if ($this->_haserrorfunctions){
+ $this->_errorMsg = ads_errormsg();
+ $this->_errorCode = ads_error();
+ }
+ return false;
+ }
+ return TRUE;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ $ret = @ads_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $ret;
+ }
+
+ function _affectedrows()
+ {
+ return $this->_lastAffectedRows;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_ads extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "ads";
+ var $dataProvider = "ads";
+ var $useFetchArray;
+ var $_has_stupid_odbc_fetch_api_change;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+
+ $this->_queryID = $id;
+
+ // the following is required for mysql odbc driver in 4.3.1 -- why?
+ $this->EOF = false;
+ $this->_currentRow = -1;
+ //parent::__construct($id);
+ }
+
+
+ // returns the field object
+ function &FetchField($fieldOffset = -1)
+ {
+
+ $off=$fieldOffset+1; // offsets begin at 1
+
+ $o= new ADOFieldObject();
+ $o->name = @ads_field_name($this->_queryID,$off);
+ $o->type = @ads_field_type($this->_queryID,$off);
+ $o->max_length = @ads_field_len($this->_queryID,$off);
+ if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name);
+ else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name);
+ return $o;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS) ? @ads_num_rows($this->_queryID) : -1;
+ $this->_numOfFields = @ads_num_fields($this->_queryID);
+ // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
+ if ($this->_numOfRows == 0) $this->_numOfRows = -1;
+ //$this->useFetchArray = $this->connection->useFetchArray;
+ $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200;
+ }
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
+ function &GetArrayLimit($nrows,$offset=-1)
+ {
+ if ($offset <= 0) {
+ $rs =& $this->GetArray($nrows);
+ return $rs;
+ }
+ $savem = $this->fetchMode;
+ $this->fetchMode = ADODB_FETCH_NUM;
+ $this->Move($offset);
+ $this->fetchMode = $savem;
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields =& $this->GetRowAssoc();
+ }
+
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+
+ function MoveNext()
+ {
+ if ($this->_numOfRows != 0 && !$this->EOF) {
+ $this->_currentRow++;
+ if( $this->_fetch() ) {
+ return true;
+ }
+ }
+ $this->fields = false;
+ $this->EOF = true;
+ return false;
+ }
+
+ function _fetch()
+ {
+ $this->fields = false;
+ if ($this->_has_stupid_odbc_fetch_api_change)
+ $rez = @ads_fetch_into($this->_queryID,$this->fields);
+ else {
+ $row = 0;
+ $rez = @ads_fetch_into($this->_queryID,$row,$this->fields);
+ }
+ if ($rez) {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields =& $this->GetRowAssoc();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ function _close()
+ {
+ return @ads_free_result($this->_queryID);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-borland_ibase.inc.php b/vendor/adodb/adodb-php/drivers/adodb-borland_ibase.inc.php
new file mode 100644
index 0000000..70a746a
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-borland_ibase.inc.php
@@ -0,0 +1,89 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Support Borland Interbase 6.5 and later
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-ibase.inc.php");
+
+class ADODB_borland_ibase extends ADODB_ibase {
+ var $databaseType = "borland_ibase";
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->autoCommit = false;
+ $this->_transactionID = ibase_trans($this->ibasetrans, $this->_connectionID);
+ return $this->_transactionID;
+ }
+
+ function ServerInfo()
+ {
+ $arr['dialect'] = $this->dialect;
+ switch($arr['dialect']) {
+ case '':
+ case '1': $s = 'Interbase 6.5, Dialect 1'; break;
+ case '2': $s = 'Interbase 6.5, Dialect 2'; break;
+ default:
+ case '3': $s = 'Interbase 6.5, Dialect 3'; break;
+ }
+ $arr['version'] = '6.5';
+ $arr['description'] = $s;
+ return $arr;
+ }
+
+ // Note that Interbase 6.5 uses ROWS instead - don't you love forking wars!
+ // SELECT col1, col2 FROM table ROWS 5 -- get 5 rows
+ // SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2
+ // Firebird uses
+ // SELECT FIRST 5 SKIP 2 col1, col2 FROM TABLE
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ if ($nrows > 0) {
+ if ($offset <= 0) $str = " ROWS $nrows ";
+ else {
+ $a = $offset+1;
+ $b = $offset+$nrows;
+ $str = " ROWS $a TO $b";
+ }
+ } else {
+ // ok, skip
+ $a = $offset + 1;
+ $str = " ROWS $a TO 999999999"; // 999 million
+ }
+ $sql .= $str;
+
+ return ($secs2cache) ?
+ $this->CacheExecute($secs2cache,$sql,$inputarr)
+ :
+ $this->Execute($sql,$inputarr);
+ }
+
+};
+
+
+class ADORecordSet_borland_ibase extends ADORecordSet_ibase {
+
+ var $databaseType = "borland_ibase";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-csv.inc.php b/vendor/adodb/adodb-php/drivers/adodb-csv.inc.php
new file mode 100644
index 0000000..8e0766a
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-csv.inc.php
@@ -0,0 +1,209 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ Currently unsupported: MetaDatabases, MetaTables and MetaColumns, and also inputarr in Execute.
+ Native types have been converted to MetaTypes.
+ Transactions not supported yet.
+
+ Limitation of url length. For IIS, see MaxClientRequestBuffer registry value.
+
+ http://support.microsoft.com/default.aspx?scid=kb;en-us;260694
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_CSV_LAYER")) {
+ define("_ADODB_CSV_LAYER", 1 );
+
+include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
+
+class ADODB_csv extends ADOConnection {
+ var $databaseType = 'csv';
+ var $databaseProvider = 'csv';
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $_affectedrows=0;
+ var $_insertid=0;
+ var $_url;
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $hasTransactions = false;
+ var $_errorNo = false;
+
+ function __construct()
+ {
+ }
+
+ function _insertid()
+ {
+ return $this->_insertid;
+ }
+
+ function _affectedrows()
+ {
+ return $this->_affectedrows;
+ }
+
+ function MetaDatabases()
+ {
+ return false;
+ }
+
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (strtolower(substr($argHostname,0,7)) !== 'http://') return false;
+ $this->_url = $argHostname;
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (strtolower(substr($argHostname,0,7)) !== 'http://') return false;
+ $this->_url = $argHostname;
+ return true;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ return false;
+ }
+
+
+ // parameters use PostgreSQL convention, not MySQL
+ function SelectLimit($sql, $nrows = -1, $offset = -1, $inputarr = false, $secs2cache = 0)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $url = $this->_url.'?sql='.urlencode($sql)."&nrows=$nrows&fetch=".
+ (($this->fetchMode !== false)?$this->fetchMode : $ADODB_FETCH_MODE).
+ "&offset=$offset";
+ $err = false;
+ $rs = csv2rs($url,$err,false);
+
+ if ($this->debug) print "$url<br><i>$err</i><br>";
+
+ $at = strpos($err,'::::');
+ if ($at === false) {
+ $this->_errorMsg = $err;
+ $this->_errorNo = (integer)$err;
+ } else {
+ $this->_errorMsg = substr($err,$at+4,1024);
+ $this->_errorNo = -9999;
+ }
+ if ($this->_errorNo)
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,'');
+ }
+
+ if (is_object($rs)) {
+
+ $rs->databaseType='csv';
+ $rs->fetchMode = ($this->fetchMode !== false) ? $this->fetchMode : $ADODB_FETCH_MODE;
+ $rs->connection = $this;
+ }
+ return $rs;
+ }
+
+ // returns queryID or false
+ function _Execute($sql,$inputarr=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ if (!$this->_bindInputArray && $inputarr) {
+ $sqlarr = explode('?',$sql);
+ $sql = '';
+ $i = 0;
+ foreach($inputarr as $v) {
+
+ $sql .= $sqlarr[$i];
+ if (gettype($v) == 'string')
+ $sql .= $this->qstr($v);
+ else if ($v === null)
+ $sql .= 'NULL';
+ else
+ $sql .= $v;
+ $i += 1;
+
+ }
+ $sql .= $sqlarr[$i];
+ if ($i+1 != sizeof($sqlarr))
+ print "Input Array does not match ?: ".htmlspecialchars($sql);
+ $inputarr = false;
+ }
+
+ $url = $this->_url.'?sql='.urlencode($sql)."&fetch=".
+ (($this->fetchMode !== false)?$this->fetchMode : $ADODB_FETCH_MODE);
+ $err = false;
+
+
+ $rs = csv2rs($url,$err,false);
+ if ($this->debug) print urldecode($url)."<br><i>$err</i><br>";
+ $at = strpos($err,'::::');
+ if ($at === false) {
+ $this->_errorMsg = $err;
+ $this->_errorNo = (integer)$err;
+ } else {
+ $this->_errorMsg = substr($err,$at+4,1024);
+ $this->_errorNo = -9999;
+ }
+
+ if ($this->_errorNo)
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr);
+ }
+ if (is_object($rs)) {
+ $rs->fetchMode = ($this->fetchMode !== false) ? $this->fetchMode : $ADODB_FETCH_MODE;
+
+ $this->_affectedrows = $rs->affectedrows;
+ $this->_insertid = $rs->insertid;
+ $rs->databaseType='csv';
+ $rs->connection = $this;
+ }
+ return $rs;
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+ return $this->_errorMsg;
+ }
+
+ /* Returns: the last error number from previous database operation */
+ function ErrorNo()
+ {
+ return $this->_errorNo;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ return true;
+ }
+} // class
+
+class ADORecordset_csv extends ADORecordset {
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+ function _close()
+ {
+ return true;
+ }
+}
+
+} // define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-db2.inc.php b/vendor/adodb/adodb-php/drivers/adodb-db2.inc.php
new file mode 100644
index 0000000..bfe99cd
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-db2.inc.php
@@ -0,0 +1,843 @@
+<?php
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+ This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension
+ for PHP (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2 or
+ higher.
+
+ Originally tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2.
+ More recently tested with PHP 5.1.2 and Apache 2.0.55 on Windows XP SP2.
+
+ This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard#rogers.com".
+ I ripped out what I believed to be a lot of redundant or obsolete code, but there are
+ probably still some remnants of the ODBC support in this file; I'm relying on reviewers
+ of this code to point out any other things that can be removed.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+ define("_ADODB_DB2_LAYER", 2 );
+
+/*--------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------*/
+
+
+
+
+
+class ADODB_db2 extends ADOConnection {
+ var $databaseType = "db2";
+ var $fmtDate = "'Y-m-d'";
+ var $concat_operator = '||';
+
+ var $sysTime = 'CURRENT TIME';
+ var $sysDate = 'CURRENT DATE';
+ var $sysTimeStamp = 'CURRENT TIMESTAMP';
+
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $dataProvider = "db2";
+ var $hasAffectedRows = true;
+
+ var $binmode = DB2_BINARY;
+
+ var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
+ // breaking backward-compat
+ var $_bindInputArray = false;
+ var $_genIDSQL = "VALUES NEXTVAL FOR %s";
+ var $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s NO MAXVALUE NO CYCLE";
+ var $_dropSeqSQL = "DROP SEQUENCE %s";
+ var $_autocommit = true;
+ var $_haserrorfunctions = true;
+ var $_lastAffectedRows = 0;
+ var $uCaseTables = true; // for meta* functions, uppercase table names
+ var $hasInsertID = true;
+
+
+ function _insertid()
+ {
+ return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()');
+ }
+
+ function __construct()
+ {
+ $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050;
+ }
+
+ // returns true or false
+ function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('db2_connect')) {
+ ADOConnection::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension which is not installed.");
+ return null;
+ }
+ // This needs to be set before the connect().
+ // Replaces the odbc_binmode() call that was in Execute()
+ ini_set('ibm_db2.binmode', $this->binmode);
+
+ if ($argDatabasename && empty($argDSN)) {
+
+ if (stripos($argDatabasename,'UID=') && stripos($argDatabasename,'PWD=')) $this->_connectionID = db2_connect($argDatabasename,null,null);
+ else $this->_connectionID = db2_connect($argDatabasename,$argUsername,$argPassword);
+ } else {
+ if ($argDatabasename) $schema = $argDatabasename;
+ if (stripos($argDSN,'UID=') && stripos($argDSN,'PWD=')) $this->_connectionID = db2_connect($argDSN,null,null);
+ else $this->_connectionID = db2_connect($argDSN,$argUsername,$argPassword);
+ }
+
+ // For db2_connect(), there is an optional 4th arg. If present, it must be
+ // an array of valid options. So far, we don't use them.
+
+ $this->_errorMsg = @db2_conn_errormsg();
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ if ($this->_connectionID && isset($schema)) $this->Execute("SET SCHEMA=$schema");
+ return $this->_connectionID != false;
+ }
+
+ // returns true or false
+ function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('db2_connect')) return null;
+
+ // This needs to be set before the connect().
+ // Replaces the odbc_binmode() call that was in Execute()
+ ini_set('ibm_db2.binmode', $this->binmode);
+
+ $this->_errorMsg = '';
+
+ if ($argDatabasename && empty($argDSN)) {
+
+ if (stripos($argDatabasename,'UID=') && stripos($argDatabasename,'PWD=')) $this->_connectionID = db2_pconnect($argDatabasename,null,null);
+ else $this->_connectionID = db2_pconnect($argDatabasename,$argUsername,$argPassword);
+ } else {
+ if ($argDatabasename) $schema = $argDatabasename;
+ if (stripos($argDSN,'UID=') && stripos($argDSN,'PWD=')) $this->_connectionID = db2_pconnect($argDSN,null,null);
+ else $this->_connectionID = db2_pconnect($argDSN,$argUsername,$argPassword);
+ }
+
+ $this->_errorMsg = @db2_conn_errormsg();
+ if ($this->_connectionID && $this->autoRollback) @db2_rollback($this->_connectionID);
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ if ($this->_connectionID && isset($schema)) $this->Execute("SET SCHEMA=$schema");
+ return $this->_connectionID != false;
+ }
+
+ // format and return date string in database timestamp format
+ function DBTimeStamp($ts, $isfld = false)
+ {
+ if (empty($ts) && $ts !== 0) return 'null';
+ if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts);
+ return 'TO_DATE('.adodb_date($this->fmtTimeStamp,$ts).",'YYYY-MM-DD HH24:MI:SS')";
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ // use right() and replace() ?
+ if (!$col) $col = $this->sysDate;
+
+ /* use TO_CHAR() if $fmt is TO_CHAR() allowed fmt */
+ if ($fmt== 'Y-m-d H:i:s')
+ return 'TO_CHAR('.$col.", 'YYYY-MM-DD HH24:MI:SS')";
+
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= $this->concat_operator;
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ if ($len==1) return "year($col)";
+ $s .= "char(year($col))";
+ break;
+ case 'M':
+ if ($len==1) return "monthname($col)";
+ $s .= "substr(monthname($col),1,3)";
+ break;
+ case 'm':
+ if ($len==1) return "month($col)";
+ $s .= "right(digits(month($col)),2)";
+ break;
+ case 'D':
+ case 'd':
+ if ($len==1) return "day($col)";
+ $s .= "right(digits(day($col)),2)";
+ break;
+ case 'H':
+ case 'h':
+ if ($len==1) return "hour($col)";
+ if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)";
+ else $s .= "''";
+ break;
+ case 'i':
+ case 'I':
+ if ($len==1) return "minute($col)";
+ if ($col != $this->sysDate)
+ $s .= "right(digits(minute($col)),2)";
+ else $s .= "''";
+ break;
+ case 'S':
+ case 's':
+ if ($len==1) return "second($col)";
+ if ($col != $this->sysDate)
+ $s .= "right(digits(second($col)),2)";
+ else $s .= "''";
+ break;
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ }
+ }
+ return $s;
+ }
+
+
+ function ServerInfo()
+ {
+ $row = $this->GetRow("SELECT service_level, fixpack_num FROM TABLE(sysproc.env_get_inst_info())
+ as INSTANCEINFO");
+
+
+ if ($row) {
+ $info['version'] = $row[0].':'.$row[1];
+ $info['fixpack'] = $row[1];
+ $info['description'] = '';
+ } else {
+ return ADOConnection::ServerInfo();
+ }
+
+ return $info;
+ }
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ if (empty($this->_genSeqSQL)) return false;
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$start));
+ if (!$ok) return false;
+ return true;
+ }
+
+ function DropSequence($seqname = 'adodbseq')
+ {
+ if (empty($this->_dropSeqSQL)) return false;
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ function SelectLimit($sql, $nrows = -1, $offset = -1, $inputArr = false, $secs2cache = 0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ if ($offset <= 0) {
+ // could also use " OPTIMIZE FOR $nrows ROWS "
+ if ($nrows >= 0) $sql .= " FETCH FIRST $nrows ROWS ONLY ";
+ $rs = $this->Execute($sql,$inputArr);
+ } else {
+ if ($offset > 0 && $nrows < 0);
+ else {
+ $nrows += $offset;
+ $sql .= " FETCH FIRST $nrows ROWS ONLY ";
+ }
+ $rs = ADOConnection::SelectLimit($sql,-1,$offset,$inputArr);
+ }
+
+ return $rs;
+ }
+
+ /*
+ This algorithm is not very efficient, but works even if table locking
+ is not available.
+
+ Will return false if unable to generate an ID after $MAXLOOPS attempts.
+ */
+ function GenID($seq='adodbseq',$start=1)
+ {
+ // if you have to modify the parameter below, your database is overloaded,
+ // or you need to implement generation of id's yourself!
+ $num = $this->GetOne("VALUES NEXTVAL FOR $seq");
+ return $num;
+ }
+
+
+ function ErrorMsg()
+ {
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+ if (empty($this->_connectionID)) return @db2_conn_errormsg();
+ return @db2_conn_errormsg($this->_connectionID);
+ } else return ADOConnection::ErrorMsg();
+ }
+
+ function ErrorNo()
+ {
+
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorCode !== false) {
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode;
+ }
+
+ if (empty($this->_connectionID)) $e = @db2_conn_error();
+ else $e = @db2_conn_error($this->_connectionID);
+
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ // so we check and patch
+ if (strlen($e)<=2) return 0;
+ return $e;
+ } else return ADOConnection::ErrorNo();
+ }
+
+
+
+ function BeginTrans()
+ {
+ if (!$this->hasTransactions) return false;
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->_autocommit = false;
+ return db2_autocommit($this->_connectionID,false);
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = db2_commit($this->_connectionID);
+ db2_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = db2_rollback($this->_connectionID);
+ db2_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+ function MetaPrimaryKeys($table, $owner = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = @db2_primarykeys($this->_connectionID,'',$schema,$table);
+
+ if (!$qid) {
+ $ADODB_FETCH_MODE = $savem;
+ return false;
+ }
+ $rs = new ADORecordSet_db2($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return false;
+
+ $arr = $rs->GetArray();
+ $rs->Close();
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($arr[$i][3]) $arr2[] = $arr[$i][3];
+ }
+ return $arr2;
+ }
+
+ function MetaForeignKeys($table, $owner = FALSE, $upper = FALSE, $asociative = FALSE )
+ {
+ global $ADODB_FETCH_MODE;
+
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = @db2_foreign_keys($this->_connectionID,'',$schema,$table);
+ if (!$qid) {
+ $ADODB_FETCH_MODE = $savem;
+ return false;
+ }
+ $rs = new ADORecordSet_db2($qid);
+
+ $ADODB_FETCH_MODE = $savem;
+ /*
+ $rs->fields indices
+ 0 PKTABLE_CAT
+ 1 PKTABLE_SCHEM
+ 2 PKTABLE_NAME
+ 3 PKCOLUMN_NAME
+ 4 FKTABLE_CAT
+ 5 FKTABLE_SCHEM
+ 6 FKTABLE_NAME
+ 7 FKCOLUMN_NAME
+ */
+ if (!$rs) return false;
+
+ $foreign_keys = array();
+ while (!$rs->EOF) {
+ if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
+ if (!is_array($foreign_keys[$rs->fields[5].'.'.$rs->fields[6]]))
+ $foreign_keys[$rs->fields[5].'.'.$rs->fields[6]] = array();
+ $foreign_keys[$rs->fields[5].'.'.$rs->fields[6]][$rs->fields[7]] = $rs->fields[3];
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $foreign_key;
+ }
+
+
+ function MetaTables($ttype = false, $schema = false, $mask = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = db2_tables($this->_connectionID);
+
+ $rs = new ADORecordSet_db2($qid);
+
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) {
+ $false = false;
+ return $false;
+ }
+
+ $arr = $rs->GetArray();
+ $rs->Close();
+ $arr2 = array();
+
+ if ($ttype) {
+ $isview = strncmp($ttype,'V',1) === 0;
+ }
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if (!$arr[$i][2]) continue;
+ $type = $arr[$i][3];
+ $owner = $arr[$i][1];
+ $schemaval = ($schema) ? $arr[$i][1].'.' : '';
+ if ($ttype) {
+ if ($isview) {
+ if (strncmp($type,'V',1) === 0) $arr2[] = $schemaval.$arr[$i][2];
+ } else if (strncmp($owner,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2];
+ } else if (strncmp($owner,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2];
+ }
+ return $arr2;
+ }
+
+/*
+See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp
+/ SQL data type codes /
+#define SQL_UNKNOWN_TYPE 0
+#define SQL_CHAR 1
+#define SQL_NUMERIC 2
+#define SQL_DECIMAL 3
+#define SQL_INTEGER 4
+#define SQL_SMALLINT 5
+#define SQL_FLOAT 6
+#define SQL_REAL 7
+#define SQL_DOUBLE 8
+#if (DB2VER >= 0x0300)
+#define SQL_DATETIME 9
+#endif
+#define SQL_VARCHAR 12
+
+
+/ One-parameter shortcuts for date/time data types /
+#if (DB2VER >= 0x0300)
+#define SQL_TYPE_DATE 91
+#define SQL_TYPE_TIME 92
+#define SQL_TYPE_TIMESTAMP 93
+
+#define SQL_UNICODE (-95)
+#define SQL_UNICODE_VARCHAR (-96)
+#define SQL_UNICODE_LONGVARCHAR (-97)
+*/
+ function DB2Types($t)
+ {
+ switch ((integer)$t) {
+ case 1:
+ case 12:
+ case 0:
+ case -95:
+ case -96:
+ return 'C';
+ case -97:
+ case -1: //text
+ return 'X';
+ case -4: //image
+ return 'B';
+
+ case 9:
+ case 91:
+ return 'D';
+
+ case 10:
+ case 11:
+ case 92:
+ case 93:
+ return 'T';
+
+ case 4:
+ case 5:
+ case -6:
+ return 'I';
+
+ case -11: // uniqidentifier
+ return 'R';
+ case -7: //bit
+ return 'L';
+
+ default:
+ return 'N';
+ }
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ $colname = "%";
+ $qid = db2_columns($this->_connectionID, "", $schema, $table, $colname);
+ if (empty($qid)) return $false;
+
+ $rs = new ADORecordSet_db2($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return $false;
+ $rs->_fetch();
+
+ $retarr = array();
+
+ /*
+ $rs->fields indices
+ 0 TABLE_QUALIFIER
+ 1 TABLE_SCHEM
+ 2 TABLE_NAME
+ 3 COLUMN_NAME
+ 4 DATA_TYPE
+ 5 TYPE_NAME
+ 6 PRECISION
+ 7 LENGTH
+ 8 SCALE
+ 9 RADIX
+ 10 NULLABLE
+ 11 REMARKS
+ */
+ while (!$rs->EOF) {
+ if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[3];
+ $fld->type = $this->DB2Types($rs->fields[4]);
+
+ // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
+ // access uses precision to store length for char/varchar
+ if ($fld->type == 'C' or $fld->type == 'X') {
+ if ($rs->fields[4] <= -95) // UNICODE
+ $fld->max_length = $rs->fields[7]/2;
+ else
+ $fld->max_length = $rs->fields[7];
+ } else
+ $fld->max_length = $rs->fields[7];
+ $fld->not_null = !empty($rs->fields[10]);
+ $fld->scale = $rs->fields[8];
+ $fld->primary_key = false;
+ $retarr[strtoupper($fld->name)] = $fld;
+ } else if (sizeof($retarr)>0)
+ break;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if (empty($retarr)) $retarr = false;
+
+ $qid = db2_primary_keys($this->_connectionID, "", $schema, $table);
+ if (empty($qid)) return $false;
+
+ $rs = new ADORecordSet_db2($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return $retarr;
+ $rs->_fetch();
+
+ /*
+ $rs->fields indices
+ 0 TABLE_CAT
+ 1 TABLE_SCHEM
+ 2 TABLE_NAME
+ 3 COLUMN_NAME
+ 4 KEY_SEQ
+ 5 PK_NAME
+ */
+ while (!$rs->EOF) {
+ if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
+ $retarr[strtoupper($rs->fields[3])]->primary_key = true;
+ } else if (sizeof($retarr)>0)
+ break;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ if (empty($retarr)) $retarr = false;
+ return $retarr;
+ }
+
+
+ function Prepare($sql)
+ {
+ if (! $this->_bindInputArray) return $sql; // no binding
+ $stmt = db2_prepare($this->_connectionID,$sql);
+ if (!$stmt) {
+ // we don't know whether db2 driver is parsing prepared stmts, so just return sql
+ return $sql;
+ }
+ return array($sql,$stmt,false);
+ }
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = '';
+
+ if ($inputarr) {
+ if (is_array($sql)) {
+ $stmtid = $sql[1];
+ } else {
+ $stmtid = db2_prepare($this->_connectionID,$sql);
+
+ if ($stmtid == false) {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ return false;
+ }
+ }
+
+ if (! db2_execute($stmtid,$inputarr)) {
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = db2_stmt_errormsg();
+ $this->_errorCode = db2_stmt_error();
+ }
+ return false;
+ }
+
+ } else if (is_array($sql)) {
+ $stmtid = $sql[1];
+ if (!db2_execute($stmtid)) {
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = db2_stmt_errormsg();
+ $this->_errorCode = db2_stmt_error();
+ }
+ return false;
+ }
+ } else
+ $stmtid = @db2_exec($this->_connectionID,$sql);
+
+ $this->_lastAffectedRows = 0;
+ if ($stmtid) {
+ if (@db2_num_fields($stmtid) == 0) {
+ $this->_lastAffectedRows = db2_num_rows($stmtid);
+ $stmtid = true;
+ } else {
+ $this->_lastAffectedRows = 0;
+ }
+
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = '';
+ $this->_errorCode = 0;
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ } else {
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = db2_stmt_errormsg();
+ $this->_errorCode = db2_stmt_error();
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ }
+ return $stmtid;
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ $ret = @db2_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $ret;
+ }
+
+ function _affectedrows()
+ {
+ return $this->_lastAffectedRows;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_db2 extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "db2";
+ var $dataProvider = "db2";
+ var $useFetchArray;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+
+ $this->_queryID = $id;
+ }
+
+
+ // returns the field object
+ function FetchField($offset = -1)
+ {
+ $o= new ADOFieldObject();
+ $o->name = @db2_field_name($this->_queryID,$offset);
+ $o->type = @db2_field_type($this->_queryID,$offset);
+ $o->max_length = db2_field_width($this->_queryID,$offset);
+ if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name);
+ else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name);
+ return $o;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1;
+ $this->_numOfFields = @db2_num_fields($this->_queryID);
+ // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
+ if ($this->_numOfRows == 0) $this->_numOfRows = -1;
+ }
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
+ function GetArrayLimit($nrows,$offset=-1)
+ {
+ if ($offset <= 0) {
+ $rs = $this->GetArray($nrows);
+ return $rs;
+ }
+ $savem = $this->fetchMode;
+ $this->fetchMode = ADODB_FETCH_NUM;
+ $this->Move($offset);
+ $this->fetchMode = $savem;
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+
+ function MoveNext()
+ {
+ if ($this->_numOfRows != 0 && !$this->EOF) {
+ $this->_currentRow++;
+
+ $this->fields = @db2_fetch_array($this->_queryID);
+ if ($this->fields) {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+ return true;
+ }
+ }
+ $this->fields = false;
+ $this->EOF = true;
+ return false;
+ }
+
+ function _fetch()
+ {
+
+ $this->fields = db2_fetch_array($this->_queryID);
+ if ($this->fields) {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+ return true;
+ }
+ $this->fields = false;
+ return false;
+ }
+
+ function _close()
+ {
+ return @db2_free_result($this->_queryID);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-db2oci.inc.php b/vendor/adodb/adodb-php/drivers/adodb-db2oci.inc.php
new file mode 100644
index 0000000..850a5d3
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-db2oci.inc.php
@@ -0,0 +1,226 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft Visual FoxPro data driver. Requires ODBC. Works only on MS Windows.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+include(ADODB_DIR."/drivers/adodb-db2.inc.php");
+
+
+if (!defined('ADODB_DB2OCI')){
+define('ADODB_DB2OCI',1);
+
+/*
+// regex code for smart remapping of :0, :1 bind vars to ? ?
+function _colontrack($p)
+{
+global $_COLONARR,$_COLONSZ;
+ $v = (integer) substr($p,1);
+ if ($v > $_COLONSZ) return $p;
+ $_COLONARR[] = $v;
+ return '?';
+}
+
+// smart remapping of :0, :1 bind vars to ? ?
+function _colonscope($sql,$arr)
+{
+global $_COLONARR,$_COLONSZ;
+
+ $_COLONARR = array();
+ $_COLONSZ = sizeof($arr);
+
+ $sql2 = preg_replace("/(:[0-9]+)/e","_colontrack('\\1')",$sql);
+
+ if (empty($_COLONARR)) return array($sql,$arr);
+
+ foreach($_COLONARR as $k => $v) {
+ $arr2[] = $arr[$v];
+ }
+
+ return array($sql2,$arr2);
+}
+*/
+
+/*
+ Smart remapping of :0, :1 bind vars to ? ?
+
+ Handles colons in comments -- and / * * / and in quoted strings.
+*/
+
+function _colonparser($sql,$arr)
+{
+ $lensql = strlen($sql);
+ $arrsize = sizeof($arr);
+ $state = 'NORM';
+ $at = 1;
+ $ch = $sql[0];
+ $ch2 = @$sql[1];
+ $sql2 = '';
+ $arr2 = array();
+ $nprev = 0;
+
+
+ while (strlen($ch)) {
+
+ switch($ch) {
+ case '/':
+ if ($state == 'NORM' && $ch2 == '*') {
+ $state = 'COMMENT';
+
+ $at += 1;
+ $ch = $ch2;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ }
+ break;
+
+ case '*':
+ if ($state == 'COMMENT' && $ch2 == '/') {
+ $state = 'NORM';
+
+ $at += 1;
+ $ch = $ch2;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ }
+ break;
+
+ case "\n":
+ case "\r":
+ if ($state == 'COMMENT2') $state = 'NORM';
+ break;
+
+ case "'":
+ do {
+ $at += 1;
+ $ch = $ch2;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ } while ($ch !== "'");
+ break;
+
+ case ':':
+ if ($state == 'COMMENT' || $state == 'COMMENT2') break;
+
+ //echo "$at=$ch $ch2, ";
+ if ('0' <= $ch2 && $ch2 <= '9') {
+ $n = '';
+ $nat = $at;
+ do {
+ $at += 1;
+ $ch = $ch2;
+ $n .= $ch;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ } while ('0' <= $ch && $ch <= '9');
+ #echo "$n $arrsize ] ";
+ $n = (integer) $n;
+ if ($n < $arrsize) {
+ $sql2 .= substr($sql,$nprev,$nat-$nprev-1).'?';
+ $nprev = $at-1;
+ $arr2[] = $arr[$n];
+ }
+ }
+ break;
+
+ case '-':
+ if ($state == 'NORM') {
+ if ($ch2 == '-') $state = 'COMMENT2';
+ $at += 1;
+ $ch = $ch2;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ }
+ break;
+ }
+
+ $at += 1;
+ $ch = $ch2;
+ $ch2 = $at < $lensql ? $sql[$at] : '';
+ }
+
+ if ($nprev == 0) {
+ $sql2 = $sql;
+ } else {
+ $sql2 .= substr($sql,$nprev);
+ }
+
+ return array($sql2,$arr2);
+}
+
+class ADODB_db2oci extends ADODB_db2 {
+ var $databaseType = "db2oci";
+ var $sysTimeStamp = 'sysdate';
+ var $sysDate = 'trunc(sysdate)';
+ var $_bindInputArray = true;
+
+ function Param($name,$type='C')
+ {
+ return ':'.$name;
+ }
+
+
+ function MetaTables($ttype = false, $schema = false, $mask = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = db2_tables($this->_connectionID);
+
+ $rs = new ADORecordSet_db2($qid);
+
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) {
+ $false = false;
+ return $false;
+ }
+
+ $arr = $rs->GetArray();
+ $rs->Close();
+ $arr2 = array();
+ // adodb_pr($arr);
+ if ($ttype) {
+ $isview = strncmp($ttype,'V',1) === 0;
+ }
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if (!$arr[$i][2]) continue;
+ $type = $arr[$i][3];
+ $schemaval = ($schema) ? $arr[$i][1].'.' : '';
+ $name = $schemaval.$arr[$i][2];
+ $owner = $arr[$i][1];
+ if (substr($name,0,8) == 'EXPLAIN_') continue;
+ if ($ttype) {
+ if ($isview) {
+ if (strncmp($type,'V',1) === 0) $arr2[] = $name;
+ } else if (strncmp($type,'T',1) === 0 && strncmp($owner,'SYS',3) !== 0) $arr2[] = $name;
+ } else if (strncmp($type,'T',1) === 0 && strncmp($owner,'SYS',3) !== 0) $arr2[] = $name;
+ }
+ return $arr2;
+ }
+
+ function _Execute($sql, $inputarr=false )
+ {
+ if ($inputarr) list($sql,$inputarr) = _colonparser($sql, $inputarr);
+ return parent::_Execute($sql, $inputarr);
+ }
+};
+
+
+class ADORecordSet_db2oci extends ADORecordSet_db2 {
+
+ var $databaseType = "db2oci";
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+} //define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-db2ora.inc.php b/vendor/adodb/adodb-php/drivers/adodb-db2ora.inc.php
new file mode 100644
index 0000000..75cc9f5
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-db2ora.inc.php
@@ -0,0 +1,86 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft Visual FoxPro data driver. Requires ODBC. Works only on MS Windows.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+include(ADODB_DIR."/drivers/adodb-db2.inc.php");
+
+
+if (!defined('ADODB_DB2OCI')){
+define('ADODB_DB2OCI',1);
+
+
+/**
+ * Callback function for preg_replace in _colonscope()
+ * @param array $p matched patterns
+ * return string '?' if parameter replaced, :N if not
+ */
+function _colontrack($p)
+{
+ global $_COLONARR, $_COLONSZ;
+ $v = (integer) substr($p[1], 1);
+ if ($v > $_COLONSZ) return $p[1];
+ $_COLONARR[] = $v;
+ return '?';
+}
+
+/**
+ * smart remapping of :0, :1 bind vars to ? ?
+ * @param string $sql SQL statement
+ * @param array $arr parameters
+ * @return array
+ */
+function _colonscope($sql,$arr)
+{
+global $_COLONARR,$_COLONSZ;
+
+ $_COLONARR = array();
+ $_COLONSZ = sizeof($arr);
+
+ $sql2 = preg_replace_callback('/(:[0-9]+)/', '_colontrack', $sql);
+
+ if (empty($_COLONARR)) return array($sql,$arr);
+
+ foreach($_COLONARR as $k => $v) {
+ $arr2[] = $arr[$v];
+ }
+
+ return array($sql2,$arr2);
+}
+
+class ADODB_db2oci extends ADODB_db2 {
+ var $databaseType = "db2oci";
+ var $sysTimeStamp = 'sysdate';
+ var $sysDate = 'trunc(sysdate)';
+
+ function _Execute($sql, $inputarr = false)
+ {
+ if ($inputarr) list($sql,$inputarr) = _colonscope($sql, $inputarr);
+ return parent::_Execute($sql, $inputarr);
+ }
+};
+
+
+class ADORecordSet_db2oci extends ADORecordSet_odbc {
+
+ var $databaseType = "db2oci";
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+} //define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-fbsql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-fbsql.inc.php
new file mode 100644
index 0000000..bc3d0a6
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-fbsql.inc.php
@@ -0,0 +1,267 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Contribution by Frank M. Kromann <frank@frontbase.com>.
+ Set tabs to 8.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_FBSQL_LAYER")) {
+ define("_ADODB_FBSQL_LAYER", 1 );
+
+class ADODB_fbsql extends ADOConnection {
+ var $databaseType = 'fbsql';
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $metaTablesSQL = "SHOW TABLES";
+ var $metaColumnsSQL = "SHOW COLUMNS FROM %s";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $hasLimit = false;
+
+ function __construct()
+ {
+ }
+
+ function _insertid()
+ {
+ return fbsql_insert_id($this->_connectionID);
+ }
+
+ function _affectedrows()
+ {
+ return fbsql_affected_rows($this->_connectionID);
+ }
+
+ function MetaDatabases()
+ {
+ $qid = fbsql_list_dbs($this->_connectionID);
+ $arr = array();
+ $i = 0;
+ $max = fbsql_num_rows($qid);
+ while ($i < $max) {
+ $arr[] = fbsql_tablename($qid,$i);
+ $i += 1;
+ }
+ return $arr;
+ }
+
+ // returns concatenated string
+ function Concat()
+ {
+ $s = "";
+ $arr = func_get_args();
+ $first = true;
+
+ $s = implode(',',$arr);
+ if (sizeof($arr) > 0) return "CONCAT($s)";
+ else return '';
+ }
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ $this->_connectionID = fbsql_connect($argHostname,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ $this->_connectionID = fbsql_pconnect($argHostname,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ if ($this->metaColumnsSQL) {
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+
+ if ($rs === false) return false;
+
+ $retarr = array();
+ while (!$rs->EOF){
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+
+ // split type into type(length):
+ if (preg_match("/^(.+)\((\d+)\)$/", $fld->type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = $query_array[2];
+ } else {
+ $fld->max_length = -1;
+ }
+ $fld->not_null = ($rs->fields[2] != 'YES');
+ $fld->primary_key = ($rs->fields[3] == 'PRI');
+ $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
+ $fld->binary = (strpos($fld->type,'blob') !== false);
+
+ $retarr[strtoupper($fld->name)] = $fld;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ return $retarr;
+ }
+ return false;
+ }
+
+ // returns true or false
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ if ($this->_connectionID) {
+ return @fbsql_select_db($dbName,$this->_connectionID);
+ }
+ else return false;
+ }
+
+
+ // returns queryID or false
+ function _query($sql,$inputarr=false)
+ {
+ return fbsql_query("$sql;",$this->_connectionID);
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+ $this->_errorMsg = @fbsql_error($this->_connectionID);
+ return $this->_errorMsg;
+ }
+
+ /* Returns: the last error number from previous database operation */
+ function ErrorNo()
+ {
+ return @fbsql_errno($this->_connectionID);
+ }
+
+ // returns true or false
+ function _close()
+ {
+ return @fbsql_close($this->_connectionID);
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_fbsql extends ADORecordSet{
+
+ var $databaseType = "fbsql";
+ var $canSeek = true;
+
+ function __construct($queryID,$mode=false)
+ {
+ if (!$mode) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch ($mode) {
+ case ADODB_FETCH_NUM: $this->fetchMode = FBSQL_NUM; break;
+ case ADODB_FETCH_ASSOC: $this->fetchMode = FBSQL_ASSOC; break;
+ case ADODB_FETCH_BOTH:
+ default:
+ $this->fetchMode = FBSQL_BOTH; break;
+ }
+ return parent::__construct($queryID);
+ }
+
+ function _initrs()
+ {
+ GLOBAL $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS) ? @fbsql_num_rows($this->_queryID):-1;
+ $this->_numOfFields = @fbsql_num_fields($this->_queryID);
+ }
+
+
+
+ function FetchField($fieldOffset = -1) {
+ if ($fieldOffset != -1) {
+ $o = @fbsql_fetch_field($this->_queryID, $fieldOffset);
+ //$o->max_length = -1; // fbsql returns the max length less spaces -- so it is unrealiable
+ $f = @fbsql_field_flags($this->_queryID,$fieldOffset);
+ $o->binary = (strpos($f,'binary')!== false);
+ }
+ else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */
+ $o = @fbsql_fetch_field($this->_queryID);// fbsql returns the max length less spaces -- so it is unrealiable
+ //$o->max_length = -1;
+ }
+
+ return $o;
+ }
+
+ function _seek($row)
+ {
+ return @fbsql_data_seek($this->_queryID,$row);
+ }
+
+ function _fetch($ignore_fields=false)
+ {
+ $this->fields = @fbsql_fetch_array($this->_queryID,$this->fetchMode);
+ return ($this->fields == true);
+ }
+
+ function _close() {
+ return @fbsql_free_result($this->_queryID);
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ $len = -1; // fbsql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'CHARACTER':
+ case 'CHARACTER VARYING':
+ case 'BLOB':
+ case 'CLOB':
+ case 'BIT':
+ case 'BIT VARYING':
+ if ($len <= $this->blobSize) return 'C';
+
+ // so we have to check whether binary...
+ case 'IMAGE':
+ case 'LONGBLOB':
+ case 'BLOB':
+ case 'MEDIUMBLOB':
+ return !empty($fieldobj->binary) ? 'B' : 'X';
+
+ case 'DATE': return 'D';
+
+ case 'TIME':
+ case 'TIME WITH TIME ZONE':
+ case 'TIMESTAMP':
+ case 'TIMESTAMP WITH TIME ZONE': return 'T';
+
+ case 'PRIMARY_KEY':
+ return 'R';
+ case 'INTEGER':
+ case 'SMALLINT':
+ case 'BOOLEAN':
+
+ if (!empty($fieldobj->primary_key)) return 'R';
+ else return 'I';
+
+ default: return 'N';
+ }
+ }
+
+} //class
+} // defined
diff --git a/vendor/adodb/adodb-php/drivers/adodb-firebird.inc.php b/vendor/adodb/adodb-php/drivers/adodb-firebird.inc.php
new file mode 100644
index 0000000..42aeb2b
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-firebird.inc.php
@@ -0,0 +1,73 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-ibase.inc.php");
+
+class ADODB_firebird extends ADODB_ibase {
+ var $databaseType = "firebird";
+ var $dialect = 3;
+
+ var $sysTimeStamp = "CURRENT_TIMESTAMP"; //"cast('NOW' as timestamp)";
+
+ function ServerInfo()
+ {
+ $arr['dialect'] = $this->dialect;
+ switch($arr['dialect']) {
+ case '':
+ case '1': $s = 'Firebird Dialect 1'; break;
+ case '2': $s = 'Firebird Dialect 2'; break;
+ default:
+ case '3': $s = 'Firebird Dialect 3'; break;
+ }
+ $arr['version'] = ADOConnection::_findvers($s);
+ $arr['description'] = $s;
+ return $arr;
+ }
+
+ // Note that Interbase 6.5 uses this ROWS instead - don't you love forking wars!
+ // SELECT col1, col2 FROM table ROWS 5 -- get 5 rows
+ // SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs=0)
+ {
+ $nrows = (integer) $nrows;
+ $offset = (integer) $offset;
+ $str = 'SELECT ';
+ if ($nrows >= 0) $str .= "FIRST $nrows ";
+ $str .=($offset>=0) ? "SKIP $offset " : '';
+
+ $sql = preg_replace('/^[ \t]*select/i',$str,$sql);
+ if ($secs)
+ $rs = $this->CacheExecute($secs,$sql,$inputarr);
+ else
+ $rs = $this->Execute($sql,$inputarr);
+
+ return $rs;
+ }
+
+
+};
+
+
+class ADORecordSet_firebird extends ADORecordSet_ibase {
+
+ var $databaseType = "firebird";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ibase.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ibase.inc.php
new file mode 100644
index 0000000..639233e
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ibase.inc.php
@@ -0,0 +1,918 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ Interbase data driver. Requires interbase client. Works on Windows and Unix.
+
+ 3 Jan 2002 -- suggestions by Hans-Peter Oeri <kampfcaspar75@oeri.ch>
+ changed transaction handling and added experimental blob stuff
+
+ Docs to interbase at the website
+ http://www.synectics.co.za/php3/tutorial/IB_PHP3_API.html
+
+ To use gen_id(), see
+ http://www.volny.cz/iprenosil/interbase/ip_ib_code.htm#_code_creategen
+
+ $rs = $conn->Execute('select gen_id(adodb,1) from rdb$database');
+ $id = $rs->fields[0];
+ $conn->Execute("insert into table (id, col1,...) values ($id, $val1,...)");
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB_ibase extends ADOConnection {
+ var $databaseType = "ibase";
+ var $dataProvider = "ibase";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $ibase_datefmt = '%Y-%m-%d'; // For hours,mins,secs change to '%Y-%m-%d %H:%M:%S';
+ var $fmtDate = "'Y-m-d'";
+ var $ibase_timestampfmt = "%Y-%m-%d %H:%M:%S";
+ var $ibase_timefmt = "%H:%M:%S";
+ var $fmtTimeStamp = "'Y-m-d, H:i:s'";
+ var $concat_operator='||';
+ var $_transactionID;
+ var $metaTablesSQL = "select rdb\$relation_name from rdb\$relations where rdb\$relation_name not like 'RDB\$%'";
+ //OPN STUFF start
+ var $metaColumnsSQL = "select a.rdb\$field_name, a.rdb\$null_flag, a.rdb\$default_source, b.rdb\$field_length, b.rdb\$field_scale, b.rdb\$field_sub_type, b.rdb\$field_precision, b.rdb\$field_type from rdb\$relation_fields a, rdb\$fields b where a.rdb\$field_source = b.rdb\$field_name and a.rdb\$relation_name = '%s' order by a.rdb\$field_position asc";
+ //OPN STUFF end
+ var $ibasetrans;
+ var $hasGenID = true;
+ var $_bindInputArray = true;
+ var $buffers = 0;
+ var $dialect = 1;
+ var $sysDate = "cast('TODAY' as timestamp)";
+ var $sysTimeStamp = "CURRENT_TIMESTAMP"; //"cast('NOW' as timestamp)";
+ var $ansiOuter = true;
+ var $hasAffectedRows = false;
+ var $poorAffectedRows = true;
+ var $blobEncodeType = 'C';
+ var $role = false;
+
+ function __construct()
+ {
+ if (defined('IBASE_DEFAULT')) $this->ibasetrans = IBASE_DEFAULT;
+ }
+
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false)
+ {
+ if (!function_exists('ibase_pconnect')) return null;
+ if ($argDatabasename) $argHostname .= ':'.$argDatabasename;
+ $fn = ($persist) ? 'ibase_pconnect':'ibase_connect';
+ if ($this->role)
+ $this->_connectionID = $fn($argHostname,$argUsername,$argPassword,
+ $this->charSet,$this->buffers,$this->dialect,$this->role);
+ else
+ $this->_connectionID = $fn($argHostname,$argUsername,$argPassword,
+ $this->charSet,$this->buffers,$this->dialect);
+
+ if ($this->dialect != 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html
+ $this->replaceQuote = "''";
+ }
+ if ($this->_connectionID === false) {
+ $this->_handleerror();
+ return false;
+ }
+
+ // PHP5 change.
+ if (function_exists('ibase_timefmt')) {
+ ibase_timefmt($this->ibase_datefmt,IBASE_DATE );
+ if ($this->dialect == 1) {
+ ibase_timefmt($this->ibase_datefmt,IBASE_TIMESTAMP );
+ }
+ else {
+ ibase_timefmt($this->ibase_timestampfmt,IBASE_TIMESTAMP );
+ }
+ ibase_timefmt($this->ibase_timefmt,IBASE_TIME );
+
+ } else {
+ ini_set("ibase.timestampformat", $this->ibase_timestampfmt);
+ ini_set("ibase.dateformat", $this->ibase_datefmt);
+ ini_set("ibase.timeformat", $this->ibase_timefmt);
+ }
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,true);
+ }
+
+
+ function MetaPrimaryKeys($table,$owner_notused=false,$internalKey=false)
+ {
+ if ($internalKey) {
+ return array('RDB$DB_KEY');
+ }
+
+ $table = strtoupper($table);
+
+ $sql = 'SELECT S.RDB$FIELD_NAME AFIELDNAME
+ FROM RDB$INDICES I JOIN RDB$INDEX_SEGMENTS S ON I.RDB$INDEX_NAME=S.RDB$INDEX_NAME
+ WHERE I.RDB$RELATION_NAME=\''.$table.'\' and I.RDB$INDEX_NAME like \'RDB$PRIMARY%\'
+ ORDER BY I.RDB$INDEX_NAME,S.RDB$FIELD_POSITION';
+
+ $a = $this->GetCol($sql,false,true);
+ if ($a && sizeof($a)>0) return $a;
+ return false;
+ }
+
+ function ServerInfo()
+ {
+ $arr['dialect'] = $this->dialect;
+ switch($arr['dialect']) {
+ case '':
+ case '1': $s = 'Interbase 5.5 or earlier'; break;
+ case '2': $s = 'Interbase 5.6'; break;
+ default:
+ case '3': $s = 'Interbase 6.0'; break;
+ }
+ $arr['version'] = ADOConnection::_findvers($s);
+ $arr['description'] = $s;
+ return $arr;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->autoCommit = false;
+ $this->_transactionID = $this->_connectionID;//ibase_trans($this->ibasetrans, $this->_connectionID);
+ return $this->_transactionID;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if (!$ok) {
+ return $this->RollbackTrans();
+ }
+ if ($this->transOff) {
+ return true;
+ }
+ if ($this->transCnt) {
+ $this->transCnt -= 1;
+ }
+ $ret = false;
+ $this->autoCommit = true;
+ if ($this->_transactionID) {
+ //print ' commit ';
+ $ret = ibase_commit($this->_transactionID);
+ }
+ $this->_transactionID = false;
+ return $ret;
+ }
+
+ // there are some compat problems with ADODB_COUNTRECS=false and $this->_logsql currently.
+ // it appears that ibase extension cannot support multiple concurrent queryid's
+ function _Execute($sql,$inputarr=false)
+ {
+ global $ADODB_COUNTRECS;
+
+ if ($this->_logsql) {
+ $savecrecs = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = true; // force countrecs
+ $ret = ADOConnection::_Execute($sql,$inputarr);
+ $ADODB_COUNTRECS = $savecrecs;
+ } else {
+ $ret = ADOConnection::_Execute($sql,$inputarr);
+ }
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $ret = false;
+ $this->autoCommit = true;
+ if ($this->_transactionID) {
+ $ret = ibase_rollback($this->_transactionID);
+ }
+ $this->_transactionID = false;
+
+ return $ret;
+ }
+
+ function MetaIndexes ($table, $primary = FALSE, $owner=false)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+ $table = strtoupper($table);
+ $sql = "SELECT * FROM RDB\$INDICES WHERE RDB\$RELATION_NAME = '".$table."'";
+ if (!$primary) {
+ $sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$%'";
+ } else {
+ $sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$FOREIGN%'";
+ }
+ // get index details
+ $rs = $this->Execute($sql);
+ if (!is_object($rs)) {
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ $index = $row[0];
+ if (!isset($indexes[$index])) {
+ if (is_null($row[3])) {
+ $row[3] = 0;
+ }
+ $indexes[$index] = array(
+ 'unique' => ($row[3] == 1),
+ 'columns' => array()
+ );
+ }
+ $sql = "SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '".$index."' ORDER BY RDB\$FIELD_POSITION ASC";
+ $rs1 = $this->Execute($sql);
+ while ($row1 = $rs1->FetchRow()) {
+ $indexes[$index]['columns'][$row1[2]] = $row1[1];
+ }
+ }
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $indexes;
+ }
+
+
+ // See http://community.borland.com/article/0,1410,25844,00.html
+ function RowLock($tables,$where,$col=false)
+ {
+ if ($this->autoCommit) {
+ $this->BeginTrans();
+ }
+ $this->Execute("UPDATE $table SET $col=$col WHERE $where "); // is this correct - jlim?
+ return 1;
+ }
+
+
+ function CreateSequence($seqname = 'adodbseq', $startID = 1)
+ {
+ $ok = $this->Execute(("INSERT INTO RDB\$GENERATORS (RDB\$GENERATOR_NAME) VALUES (UPPER('$seqname'))" ));
+ if (!$ok) return false;
+ return $this->Execute("SET GENERATOR $seqname TO ".($startID-1).';');
+ }
+
+ function DropSequence($seqname = 'adodbseq')
+ {
+ $seqname = strtoupper($seqname);
+ $this->Execute("delete from RDB\$GENERATORS where RDB\$GENERATOR_NAME='$seqname'");
+ }
+
+ function GenID($seqname='adodbseq',$startID=1)
+ {
+ $getnext = ("SELECT Gen_ID($seqname,1) FROM RDB\$DATABASE");
+ $rs = @$this->Execute($getnext);
+ if (!$rs) {
+ $this->Execute(("INSERT INTO RDB\$GENERATORS (RDB\$GENERATOR_NAME) VALUES (UPPER('$seqname'))" ));
+ $this->Execute("SET GENERATOR $seqname TO ".($startID-1).';');
+ $rs = $this->Execute($getnext);
+ }
+ if ($rs && !$rs->EOF) {
+ $this->genID = (integer) reset($rs->fields);
+ }
+ else {
+ $this->genID = 0; // false
+ }
+
+ if ($rs) {
+ $rs->Close();
+ }
+
+ return $this->genID;
+ }
+
+ function SelectDB($dbName)
+ {
+ return false;
+ }
+
+ function _handleerror()
+ {
+ $this->_errorMsg = ibase_errmsg();
+ }
+
+ function ErrorNo()
+ {
+ if (preg_match('/error code = ([\-0-9]*)/i', $this->_errorMsg,$arr)) return (integer) $arr[1];
+ else return 0;
+ }
+
+ function ErrorMsg()
+ {
+ return $this->_errorMsg;
+ }
+
+ function Prepare($sql)
+ {
+ $stmt = ibase_prepare($this->_connectionID,$sql);
+ if (!$stmt) return false;
+ return array($sql,$stmt);
+ }
+
+ // returns query ID if successful, otherwise false
+ // there have been reports of problems with nested queries - the code is probably not re-entrant?
+ function _query($sql,$iarr=false)
+ {
+
+ if (!$this->autoCommit && $this->_transactionID) {
+ $conn = $this->_transactionID;
+ $docommit = false;
+ } else {
+ $conn = $this->_connectionID;
+ $docommit = true;
+ }
+ if (is_array($sql)) {
+ $fn = 'ibase_execute';
+ $sql = $sql[1];
+ if (is_array($iarr)) {
+ if (ADODB_PHPVER >= 0x4050) { // actually 4.0.4
+ if ( !isset($iarr[0]) ) $iarr[0] = ''; // PHP5 compat hack
+ $fnarr = array_merge( array($sql) , $iarr);
+ $ret = call_user_func_array($fn,$fnarr);
+ } else {
+ switch(sizeof($iarr)) {
+ case 1: $ret = $fn($sql,$iarr[0]); break;
+ case 2: $ret = $fn($sql,$iarr[0],$iarr[1]); break;
+ case 3: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2]); break;
+ case 4: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3]); break;
+ case 5: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4]); break;
+ case 6: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5]); break;
+ case 7: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6]); break;
+ default: ADOConnection::outp( "Too many parameters to ibase query $sql");
+ case 8: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6],$iarr[7]); break;
+ }
+ }
+ } else $ret = $fn($sql);
+ } else {
+ $fn = 'ibase_query';
+
+ if (is_array($iarr)) {
+ if (ADODB_PHPVER >= 0x4050) { // actually 4.0.4
+ if (sizeof($iarr) == 0) $iarr[0] = ''; // PHP5 compat hack
+ $fnarr = array_merge( array($conn,$sql) , $iarr);
+ $ret = call_user_func_array($fn,$fnarr);
+ } else {
+ switch(sizeof($iarr)) {
+ case 1: $ret = $fn($conn,$sql,$iarr[0]); break;
+ case 2: $ret = $fn($conn,$sql,$iarr[0],$iarr[1]); break;
+ case 3: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2]); break;
+ case 4: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3]); break;
+ case 5: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4]); break;
+ case 6: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5]); break;
+ case 7: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6]); break;
+ default: ADOConnection::outp( "Too many parameters to ibase query $sql");
+ case 8: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6],$iarr[7]); break;
+ }
+ }
+ } else $ret = $fn($conn,$sql);
+ }
+ if ($docommit && $ret === true) {
+ ibase_commit($this->_connectionID);
+ }
+
+ $this->_handleerror();
+ return $ret;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if (!$this->autoCommit) {
+ @ibase_rollback($this->_connectionID);
+ }
+ return @ibase_close($this->_connectionID);
+ }
+
+ //OPN STUFF start
+ function _ConvertFieldType(&$fld, $ftype, $flen, $fscale, $fsubtype, $fprecision, $dialect3)
+ {
+ $fscale = abs($fscale);
+ $fld->max_length = $flen;
+ $fld->scale = null;
+ switch($ftype){
+ case 7:
+ case 8:
+ if ($dialect3) {
+ switch($fsubtype){
+ case 0:
+ $fld->type = ($ftype == 7 ? 'smallint' : 'integer');
+ break;
+ case 1:
+ $fld->type = 'numeric';
+ $fld->max_length = $fprecision;
+ $fld->scale = $fscale;
+ break;
+ case 2:
+ $fld->type = 'decimal';
+ $fld->max_length = $fprecision;
+ $fld->scale = $fscale;
+ break;
+ } // switch
+ } else {
+ if ($fscale !=0) {
+ $fld->type = 'decimal';
+ $fld->scale = $fscale;
+ $fld->max_length = ($ftype == 7 ? 4 : 9);
+ } else {
+ $fld->type = ($ftype == 7 ? 'smallint' : 'integer');
+ }
+ }
+ break;
+ case 16:
+ if ($dialect3) {
+ switch($fsubtype){
+ case 0:
+ $fld->type = 'decimal';
+ $fld->max_length = 18;
+ $fld->scale = 0;
+ break;
+ case 1:
+ $fld->type = 'numeric';
+ $fld->max_length = $fprecision;
+ $fld->scale = $fscale;
+ break;
+ case 2:
+ $fld->type = 'decimal';
+ $fld->max_length = $fprecision;
+ $fld->scale = $fscale;
+ break;
+ } // switch
+ }
+ break;
+ case 10:
+ $fld->type = 'float';
+ break;
+ case 14:
+ $fld->type = 'char';
+ break;
+ case 27:
+ if ($fscale !=0) {
+ $fld->type = 'decimal';
+ $fld->max_length = 15;
+ $fld->scale = 5;
+ } else {
+ $fld->type = 'double';
+ }
+ break;
+ case 35:
+ if ($dialect3) {
+ $fld->type = 'timestamp';
+ } else {
+ $fld->type = 'date';
+ }
+ break;
+ case 12:
+ $fld->type = 'date';
+ break;
+ case 13:
+ $fld->type = 'time';
+ break;
+ case 37:
+ $fld->type = 'varchar';
+ break;
+ case 40:
+ $fld->type = 'cstring';
+ break;
+ case 261:
+ $fld->type = 'blob';
+ $fld->max_length = -1;
+ break;
+ } // switch
+ }
+ //OPN STUFF end
+
+ // returns array of ADOFieldObjects for current table
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+
+ $ADODB_FETCH_MODE = $save;
+ $false = false;
+ if ($rs === false) {
+ return $false;
+ }
+
+ $retarr = array();
+ //OPN STUFF start
+ $dialect3 = ($this->dialect==3 ? true : false);
+ //OPN STUFF end
+ while (!$rs->EOF) { //print_r($rs->fields);
+ $fld = new ADOFieldObject();
+ $fld->name = trim($rs->fields[0]);
+ //OPN STUFF start
+ $this->_ConvertFieldType($fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3);
+ if (isset($rs->fields[1]) && $rs->fields[1]) {
+ $fld->not_null = true;
+ }
+ if (isset($rs->fields[2])) {
+
+ $fld->has_default = true;
+ $d = substr($rs->fields[2],strlen('default '));
+ switch ($fld->type)
+ {
+ case 'smallint':
+ case 'integer': $fld->default_value = (int) $d; break;
+ case 'char':
+ case 'blob':
+ case 'text':
+ case 'varchar': $fld->default_value = (string) substr($d,1,strlen($d)-2); break;
+ case 'double':
+ case 'float': $fld->default_value = (float) $d; break;
+ default: $fld->default_value = $d; break;
+ }
+ // case 35:$tt = 'TIMESTAMP'; break;
+ }
+ if ((isset($rs->fields[5])) && ($fld->type == 'blob')) {
+ $fld->sub_type = $rs->fields[5];
+ } else {
+ $fld->sub_type = null;
+ }
+ //OPN STUFF end
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[strtoupper($fld->name)] = $fld;
+
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if ( empty($retarr)) return $false;
+ else return $retarr;
+ }
+
+ function BlobEncode( $blob )
+ {
+ $blobid = ibase_blob_create( $this->_connectionID);
+ ibase_blob_add( $blobid, $blob );
+ return ibase_blob_close( $blobid );
+ }
+
+ // since we auto-decode all blob's since 2.42,
+ // BlobDecode should not do any transforms
+ function BlobDecode($blob)
+ {
+ return $blob;
+ }
+
+
+
+
+ // old blobdecode function
+ // still used to auto-decode all blob's
+ function _BlobDecode_old( $blob )
+ {
+ $blobid = ibase_blob_open($this->_connectionID, $blob );
+ $realblob = ibase_blob_get( $blobid,$this->maxblobsize); // 2nd param is max size of blob -- Kevin Boillet <kevinboillet@yahoo.fr>
+ while($string = ibase_blob_get($blobid, 8192)){
+ $realblob .= $string;
+ }
+ ibase_blob_close( $blobid );
+
+ return( $realblob );
+ }
+
+ function _BlobDecode( $blob )
+ {
+ if (ADODB_PHPVER >= 0x5000) {
+ $blob_data = ibase_blob_info($this->_connectionID, $blob );
+ $blobid = ibase_blob_open($this->_connectionID, $blob );
+ } else {
+
+ $blob_data = ibase_blob_info( $blob );
+ $blobid = ibase_blob_open( $blob );
+ }
+
+ if( $blob_data[0] > $this->maxblobsize ) {
+
+ $realblob = ibase_blob_get($blobid, $this->maxblobsize);
+
+ while($string = ibase_blob_get($blobid, 8192)){
+ $realblob .= $string;
+ }
+ } else {
+ $realblob = ibase_blob_get($blobid, $blob_data[0]);
+ }
+
+ ibase_blob_close( $blobid );
+ return( $realblob );
+ }
+
+ function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
+ {
+ $fd = fopen($path,'rb');
+ if ($fd === false) return false;
+ $blob_id = ibase_blob_create($this->_connectionID);
+
+ /* fill with data */
+
+ while ($val = fread($fd,32768)){
+ ibase_blob_add($blob_id, $val);
+ }
+
+ /* close and get $blob_id_str for inserting into table */
+ $blob_id_str = ibase_blob_close($blob_id);
+
+ fclose($fd);
+ return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ $blob_id = ibase_blob_create($this->_connectionID);
+
+ // ibase_blob_add($blob_id, $val);
+
+ // replacement that solves the problem by which only the first modulus 64K /
+ // of $val are stored at the blob field ////////////////////////////////////
+ // Thx Abel Berenstein aberenstein#afip.gov.ar
+ $len = strlen($val);
+ $chunk_size = 32768;
+ $tail_size = $len % $chunk_size;
+ $n_chunks = ($len - $tail_size) / $chunk_size;
+
+ for ($n = 0; $n < $n_chunks; $n++) {
+ $start = $n * $chunk_size;
+ $data = substr($val, $start, $chunk_size);
+ ibase_blob_add($blob_id, $data);
+ }
+
+ if ($tail_size) {
+ $start = $n_chunks * $chunk_size;
+ $data = substr($val, $start, $tail_size);
+ ibase_blob_add($blob_id, $data);
+ }
+ // end replacement /////////////////////////////////////////////////////////
+
+ $blob_id_str = ibase_blob_close($blob_id);
+
+ return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
+
+ }
+
+
+ function OldUpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ $blob_id = ibase_blob_create($this->_connectionID);
+ ibase_blob_add($blob_id, $val);
+ $blob_id_str = ibase_blob_close($blob_id);
+ return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ // Only since Interbase 6.0 - uses EXTRACT
+ // problem - does not zero-fill the day and month yet
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysDate;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '||';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "extract(year from $col)";
+ break;
+ case 'M':
+ case 'm':
+ $s .= "extract(month from $col)";
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "cast(((extract(month from $col)+2) / 3) as integer)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "(extract(day from $col))";
+ break;
+ case 'H':
+ case 'h':
+ $s .= "(extract(hour from $col))";
+ break;
+ case 'I':
+ case 'i':
+ $s .= "(extract(minute from $col))";
+ break;
+ case 'S':
+ case 's':
+ $s .= "CAST((extract(second from $col)) AS INTEGER)";
+ break;
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ break;
+ }
+ }
+ return $s;
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_ibase extends ADORecordSet
+{
+
+ var $databaseType = "ibase";
+ var $bind=false;
+ var $_cacheType;
+
+ function __construct($id,$mode=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode;
+ parent::__construct($id);
+ }
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved. */
+
+ function FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $ibf = ibase_field_info($this->_queryID,$fieldOffset);
+
+ $name = empty($ibf['alias']) ? $ibf['name'] : $ibf['alias'];
+
+ switch (ADODB_ASSOC_CASE) {
+ case ADODB_ASSOC_CASE_UPPER:
+ $fld->name = strtoupper($name);
+ break;
+ case ADODB_ASSOC_CASE_LOWER:
+ $fld->name = strtolower($name);
+ break;
+ case ADODB_ASSOC_CASE_NATIVE:
+ default:
+ $fld->name = $name;
+ break;
+ }
+
+ $fld->type = $ibf['type'];
+ $fld->max_length = $ibf['length'];
+
+ /* This needs to be populated from the metadata */
+ $fld->not_null = false;
+ $fld->has_default = false;
+ $fld->default_value = 'null';
+ return $fld;
+ }
+
+ function _initrs()
+ {
+ $this->_numOfRows = -1;
+ $this->_numOfFields = @ibase_num_fields($this->_queryID);
+
+ // cache types for blob decode check
+ for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) {
+ $f1 = $this->FetchField($i);
+ $this->_cacheType[] = $f1->type;
+ }
+ }
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ function _fetch()
+ {
+ $f = @ibase_fetch_row($this->_queryID);
+ if ($f === false) {
+ $this->fields = false;
+ return false;
+ }
+ // OPN stuff start - optimized
+ // fix missing nulls and decode blobs automatically
+
+ global $ADODB_ANSI_PADDING_OFF;
+ //$ADODB_ANSI_PADDING_OFF=1;
+ $rtrim = !empty($ADODB_ANSI_PADDING_OFF);
+
+ for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) {
+ if ($this->_cacheType[$i]=="BLOB") {
+ if (isset($f[$i])) {
+ $f[$i] = $this->connection->_BlobDecode($f[$i]);
+ } else {
+ $f[$i] = null;
+ }
+ } else {
+ if (!isset($f[$i])) {
+ $f[$i] = null;
+ } else if ($rtrim && is_string($f[$i])) {
+ $f[$i] = rtrim($f[$i]);
+ }
+ }
+ }
+ // OPN stuff end
+
+ $this->fields = $f;
+ if ($this->fetchMode == ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ } else if ($this->fetchMode == ADODB_FETCH_BOTH) {
+ $this->fields = array_merge($this->fields,$this->GetRowAssoc());
+ }
+ return true;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+
+ }
+
+
+ function _close()
+ {
+ return @ibase_free_result($this->_queryID);
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ switch (strtoupper($t)) {
+ case 'CHAR':
+ return 'C';
+
+ case 'TEXT':
+ case 'VARCHAR':
+ case 'VARYING':
+ if ($len <= $this->blobSize) return 'C';
+ return 'X';
+ case 'BLOB':
+ return 'B';
+
+ case 'TIMESTAMP':
+ case 'DATE': return 'D';
+ case 'TIME': return 'T';
+ //case 'T': return 'T';
+
+ //case 'L': return 'L';
+ case 'INT':
+ case 'SHORT':
+ case 'INTEGER': return 'I';
+ default: return 'N';
+ }
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-informix.inc.php b/vendor/adodb/adodb-php/drivers/adodb-informix.inc.php
new file mode 100644
index 0000000..84661e2
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-informix.inc.php
@@ -0,0 +1,41 @@
+<?php
+/**
+* @version v5.20.14 06-Jan-2019
+* @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+* @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+* Released under both BSD license and Lesser GPL library license.
+* Whenever there is any discrepancy between the two licenses,
+* the BSD license will take precedence.
+*
+* Set tabs to 4 for best viewing.
+*
+* Latest version is available at http://adodb.org/
+*
+* Informix 9 driver that supports SELECT FIRST
+*
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-informix72.inc.php');
+
+class ADODB_informix extends ADODB_informix72 {
+ var $databaseType = "informix";
+ var $hasTop = 'FIRST';
+ var $ansiOuter = true;
+
+ function IfNull( $field, $ifNull )
+ {
+ return " NVL($field, $ifNull) "; // if Informix 9.X or 10.X
+ }
+}
+
+class ADORecordset_informix extends ADORecordset_informix72 {
+ var $databaseType = "informix";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-informix72.inc.php b/vendor/adodb/adodb-php/drivers/adodb-informix72.inc.php
new file mode 100644
index 0000000..c8bdd1a
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-informix72.inc.php
@@ -0,0 +1,525 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim. All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Informix port by Mitchell T. Young (mitch@youngfamily.org)
+
+ Further mods by "Samuel CARRIERE" <samuel_carriere@hotmail.com>
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('IFX_SCROLL')) define('IFX_SCROLL',1);
+
+class ADODB_informix72 extends ADOConnection {
+ var $databaseType = "informix72";
+ var $dataProvider = "informix";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $substr = 'substr';
+ var $metaTablesSQL="select tabname,tabtype from systables where tabtype in ('T','V') and owner!='informix'"; //Don't get informix tables and pseudo-tables
+
+
+ var $metaColumnsSQL =
+ "select c.colname, c.coltype, c.collength, d.default,c.colno
+ from syscolumns c, systables t,outer sysdefaults d
+ where c.tabid=t.tabid and d.tabid=t.tabid and d.colno=c.colno
+ and tabname='%s' order by c.colno";
+
+ var $metaPrimaryKeySQL =
+ "select part1,part2,part3,part4,part5,part6,part7,part8 from
+ systables t,sysconstraints s,sysindexes i where t.tabname='%s'
+ and s.tabid=t.tabid and s.constrtype='P'
+ and i.idxname=s.idxname";
+
+ var $concat_operator = '||';
+
+ var $lastQuery = false;
+ var $has_insertid = true;
+
+ var $_autocommit = true;
+ var $_bindInputArray = true; // set to true if ADOConnection.Execute() permits binding of array parameters.
+ var $sysDate = 'TODAY';
+ var $sysTimeStamp = 'CURRENT';
+ var $cursorType = IFX_SCROLL; // IFX_SCROLL or IFX_HOLD or 0
+
+ function __construct()
+ {
+ // alternatively, use older method:
+ //putenv("DBDATE=Y4MD-");
+
+ // force ISO date format
+ putenv('GL_DATE=%Y-%m-%d');
+
+ if (function_exists('ifx_byteasvarchar')) {
+ ifx_byteasvarchar(1); // Mode "0" will return a blob id, and mode "1" will return a varchar with text content.
+ ifx_textasvarchar(1); // Mode "0" will return a blob id, and mode "1" will return a varchar with text content.
+ ifx_blobinfile_mode(0); // Mode "0" means save Byte-Blobs in memory, and mode "1" means save Byte-Blobs in a file.
+ }
+ }
+
+ function ServerInfo()
+ {
+ if (isset($this->version)) return $this->version;
+
+ $arr['description'] = $this->GetOne("select DBINFO('version','full') from systables where tabid = 1");
+ $arr['version'] = $this->GetOne("select DBINFO('version','major') || DBINFO('version','minor') from systables where tabid = 1");
+ $this->version = $arr;
+ return $arr;
+ }
+
+
+
+ function _insertid()
+ {
+ $sqlca =ifx_getsqlca($this->lastQuery);
+ return @$sqlca["sqlerrd1"];
+ }
+
+ function _affectedrows()
+ {
+ if ($this->lastQuery) {
+ return @ifx_affected_rows ($this->lastQuery);
+ }
+ return 0;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->Execute('BEGIN');
+ $this->_autocommit = false;
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('COMMIT');
+ $this->_autocommit = true;
+ return true;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('ROLLBACK');
+ $this->_autocommit = true;
+ return true;
+ }
+
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if ($this->_autocommit) $this->BeginTrans();
+ return $this->GetOne("select $col from $tables where $where for update");
+ }
+
+ /* Returns: the last error message from previous database operation
+ Note: This function is NOT available for Microsoft SQL Server. */
+
+ function ErrorMsg()
+ {
+ if (!empty($this->_logsql)) return $this->_errorMsg;
+ $this->_errorMsg = ifx_errormsg();
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ preg_match("/.*SQLCODE=([^\]]*)/",ifx_error(),$parse);
+ if (is_array($parse) && isset($parse[1])) return (int)$parse[1];
+ return 0;
+ }
+
+
+ function MetaProcedures($NamePattern = false, $catalog = null, $schemaPattern = null)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+
+ }
+ $procedures = array ();
+
+ // get index details
+
+ $likepattern = '';
+ if ($NamePattern) {
+ $likepattern = " WHERE procname LIKE '".$NamePattern."'";
+ }
+
+ $rs = $this->Execute('SELECT procname, isproc FROM sysprocedures'.$likepattern);
+
+ if (is_object($rs)) {
+ // parse index data into array
+
+ while ($row = $rs->FetchRow()) {
+ $procedures[$row[0]] = array(
+ 'type' => ($row[1] == 'f' ? 'FUNCTION' : 'PROCEDURE'),
+ 'catalog' => '',
+ 'schema' => '',
+ 'remarks' => ''
+ );
+ }
+ }
+
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $procedures;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ if (!empty($this->metaColumnsSQL)) {
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if ($rs === false) return $false;
+ $rspkey = $this->Execute(sprintf($this->metaPrimaryKeySQL,$table)); //Added to get primary key colno items
+
+ $retarr = array();
+ while (!$rs->EOF) { //print_r($rs->fields);
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+/* //!eos.
+ $rs->fields[1] is not the correct adodb type
+ $rs->fields[2] is not correct max_length, because can include not-null bit
+
+ $fld->type = $rs->fields[1];
+ $fld->primary_key=$rspkey->fields && array_search($rs->fields[4],$rspkey->fields); //Added to set primary key flag
+ $fld->max_length = $rs->fields[2];*/
+ $pr=ifx_props($rs->fields[1],$rs->fields[2]); //!eos
+ $fld->type = $pr[0] ;//!eos
+ $fld->primary_key=$rspkey->fields && array_search($rs->fields[4],$rspkey->fields);
+ $fld->max_length = $pr[1]; //!eos
+ $fld->precision = $pr[2] ;//!eos
+ $fld->not_null = $pr[3]=="N"; //!eos
+
+ if (trim($rs->fields[3]) != "AAAAAA 0") {
+ $fld->has_default = 1;
+ $fld->default_value = $rs->fields[3];
+ } else {
+ $fld->has_default = 0;
+ }
+
+ $retarr[strtolower($fld->name)] = $fld;
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ $rspkey->Close(); //!eos
+ return $retarr;
+ }
+
+ return $false;
+ }
+
+ function xMetaColumns($table)
+ {
+ return ADOConnection::MetaColumns($table,false);
+ }
+
+ function MetaForeignKeys($table, $owner=false, $upper=false) //!Eos
+ {
+ $sql = "
+ select tr.tabname,updrule,delrule,
+ i.part1 o1,i2.part1 d1,i.part2 o2,i2.part2 d2,i.part3 o3,i2.part3 d3,i.part4 o4,i2.part4 d4,
+ i.part5 o5,i2.part5 d5,i.part6 o6,i2.part6 d6,i.part7 o7,i2.part7 d7,i.part8 o8,i2.part8 d8
+ from systables t,sysconstraints s,sysindexes i,
+ sysreferences r,systables tr,sysconstraints s2,sysindexes i2
+ where t.tabname='$table'
+ and s.tabid=t.tabid and s.constrtype='R' and r.constrid=s.constrid
+ and i.idxname=s.idxname and tr.tabid=r.ptabid
+ and s2.constrid=r.primary and i2.idxname=s2.idxname";
+
+ $rs = $this->Execute($sql);
+ if (!$rs || $rs->EOF) return false;
+ $arr = $rs->GetArray();
+ $a = array();
+ foreach($arr as $v) {
+ $coldest=$this->metaColumnNames($v["tabname"]);
+ $colorig=$this->metaColumnNames($table);
+ $colnames=array();
+ for($i=1;$i<=8 && $v["o$i"] ;$i++) {
+ $colnames[]=$coldest[$v["d$i"]-1]."=".$colorig[$v["o$i"]-1];
+ }
+ if($upper)
+ $a[strtoupper($v["tabname"])] = $colnames;
+ else
+ $a[$v["tabname"]] = $colnames;
+ }
+ return $a;
+ }
+
+ function UpdateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
+ {
+ $type = ($blobtype == 'TEXT') ? 1 : 0;
+ $blobid = ifx_create_blob($type,0,$val);
+ return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blobid));
+ }
+
+ function BlobDecode($blobid)
+ {
+ return function_exists('ifx_byteasvarchar') ? $blobid : @ifx_get_blob($blobid);
+ }
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('ifx_connect')) return null;
+
+ $dbs = $argDatabasename . "@" . $argHostname;
+ if ($argHostname) putenv("INFORMIXSERVER=$argHostname");
+ putenv("INFORMIXSERVER=".trim($argHostname));
+ $this->_connectionID = ifx_connect($dbs,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ #if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('ifx_connect')) return null;
+
+ $dbs = $argDatabasename . "@" . $argHostname;
+ putenv("INFORMIXSERVER=".trim($argHostname));
+ $this->_connectionID = ifx_pconnect($dbs,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ #if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+/*
+ // ifx_do does not accept bind parameters - weird ???
+ function Prepare($sql)
+ {
+ $stmt = ifx_prepare($sql);
+ if (!$stmt) return $sql;
+ else return array($sql,$stmt);
+ }
+*/
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ global $ADODB_COUNTRECS;
+
+ // String parameters have to be converted using ifx_create_char
+ if ($inputarr) {
+ foreach($inputarr as $v) {
+ if (gettype($v) == 'string') {
+ $tab[] = ifx_create_char($v);
+ }
+ else {
+ $tab[] = $v;
+ }
+ }
+ }
+
+ // In case of select statement, we use a scroll cursor in order
+ // to be able to call "move", or "movefirst" statements
+ if (!$ADODB_COUNTRECS && preg_match("/^\s*select/is", $sql)) {
+ if ($inputarr) {
+ $this->lastQuery = ifx_query($sql,$this->_connectionID, $this->cursorType, $tab);
+ }
+ else {
+ $this->lastQuery = ifx_query($sql,$this->_connectionID, $this->cursorType);
+ }
+ }
+ else {
+ if ($inputarr) {
+ $this->lastQuery = ifx_query($sql,$this->_connectionID, $tab);
+ }
+ else {
+ $this->lastQuery = ifx_query($sql,$this->_connectionID);
+ }
+ }
+
+ // Following line have been commented because autocommit mode is
+ // not supported by informix SE 7.2
+
+ //if ($this->_autocommit) ifx_query('COMMIT',$this->_connectionID);
+
+ return $this->lastQuery;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ $this->lastQuery = false;
+ if($this->_connectionID) {
+ return ifx_close($this->_connectionID);
+ }
+ return true;
+ }
+}
+
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_informix72 extends ADORecordSet {
+
+ var $databaseType = "informix72";
+ var $canSeek = true;
+ var $_fieldprops = false;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+ return parent::__construct($id);
+ }
+
+
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved. */
+ function FetchField($fieldOffset = -1)
+ {
+ if (empty($this->_fieldprops)) {
+ $fp = ifx_fieldproperties($this->_queryID);
+ foreach($fp as $k => $v) {
+ $o = new ADOFieldObject;
+ $o->name = $k;
+ $arr = explode(';',$v); //"SQLTYPE;length;precision;scale;ISNULLABLE"
+ $o->type = $arr[0];
+ $o->max_length = $arr[1];
+ $this->_fieldprops[] = $o;
+ $o->not_null = $arr[4]=="N";
+ }
+ }
+ $ret = $this->_fieldprops[$fieldOffset];
+ return $ret;
+ }
+
+ function _initrs()
+ {
+ $this->_numOfRows = -1; // ifx_affected_rows not reliable, only returns estimate -- ($ADODB_COUNTRECS)? ifx_affected_rows($this->_queryID):-1;
+ $this->_numOfFields = ifx_num_fields($this->_queryID);
+ }
+
+ function _seek($row)
+ {
+ return @ifx_fetch_row($this->_queryID, (int) $row);
+ }
+
+ function MoveLast()
+ {
+ $this->fields = @ifx_fetch_row($this->_queryID, "LAST");
+ if ($this->fields) $this->EOF = false;
+ $this->_currentRow = -1;
+
+ if ($this->fetchMode == ADODB_FETCH_NUM) {
+ foreach($this->fields as $v) {
+ $arr[] = $v;
+ }
+ $this->fields = $arr;
+ }
+
+ return true;
+ }
+
+ function MoveFirst()
+ {
+ $this->fields = @ifx_fetch_row($this->_queryID, "FIRST");
+ if ($this->fields) $this->EOF = false;
+ $this->_currentRow = 0;
+
+ if ($this->fetchMode == ADODB_FETCH_NUM) {
+ foreach($this->fields as $v) {
+ $arr[] = $v;
+ }
+ $this->fields = $arr;
+ }
+
+ return true;
+ }
+
+ function _fetch($ignore_fields=false)
+ {
+
+ $this->fields = @ifx_fetch_row($this->_queryID);
+
+ if (!is_array($this->fields)) return false;
+
+ if ($this->fetchMode == ADODB_FETCH_NUM) {
+ foreach($this->fields as $v) {
+ $arr[] = $v;
+ }
+ $this->fields = $arr;
+ }
+ return true;
+ }
+
+ /* close() only needs to be called if you are worried about using too much memory while your script
+ is running. All associated result memory for the specified result identifier will automatically be freed. */
+ function _close()
+ {
+ if($this->_queryID) {
+ return ifx_free_result($this->_queryID);
+ }
+ return true;
+ }
+
+}
+/** !Eos
+* Auxiliar function to Parse coltype,collength. Used by Metacolumns
+* return: array ($mtype,$length,$precision,$nullable) (similar to ifx_fieldpropierties)
+*/
+function ifx_props($coltype,$collength){
+ $itype=fmod($coltype+1,256);
+ $nullable=floor(($coltype+1) /256) ?"N":"Y";
+ $mtype=substr(" CIIFFNNDN TBXCC ",$itype,1);
+ switch ($itype){
+ case 2:
+ $length=4;
+ case 6:
+ case 9:
+ case 14:
+ $length=floor($collength/256);
+ $precision=fmod($collength,256);
+ break;
+ default:
+ $precision=0;
+ $length=$collength;
+ }
+ return array($mtype,$length,$precision,$nullable);
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-ldap.inc.php b/vendor/adodb/adodb-php/drivers/adodb-ldap.inc.php
new file mode 100644
index 0000000..42b286c
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-ldap.inc.php
@@ -0,0 +1,428 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ Revision 1: (02/25/2005) Updated codebase to include the _inject_bind_options function. This allows
+ users to access the options in the ldap_set_option function appropriately. Most importantly
+ LDAP Version 3 is now supported. See the examples for more information. Also fixed some minor
+ bugs that surfaced when PHP error levels were set high.
+
+ Joshua Eldridge (joshuae74#hotmail.com)
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('LDAP_ASSOC')) {
+ define('LDAP_ASSOC',ADODB_FETCH_ASSOC);
+ define('LDAP_NUM',ADODB_FETCH_NUM);
+ define('LDAP_BOTH',ADODB_FETCH_BOTH);
+}
+
+class ADODB_ldap extends ADOConnection {
+ var $databaseType = 'ldap';
+ var $dataProvider = 'ldap';
+
+ # Connection information
+ var $username = false;
+ var $password = false;
+
+ # Used during searches
+ var $filter;
+ var $dn;
+ var $version;
+ var $port = 389;
+
+ # Options configuration information
+ var $LDAP_CONNECT_OPTIONS;
+
+ # error on binding, eg. "Binding: invalid credentials"
+ var $_bind_errmsg = "Binding: %s";
+
+ function __construct()
+ {
+ }
+
+ // returns true or false
+
+ function _connect( $host, $username, $password, $ldapbase)
+ {
+ global $LDAP_CONNECT_OPTIONS;
+
+ if ( !function_exists( 'ldap_connect' ) ) return null;
+
+ if (strpos($host,'ldap://') === 0 || strpos($host,'ldaps://') === 0) {
+ $this->_connectionID = @ldap_connect($host);
+ } else {
+ $conn_info = array( $host,$this->port);
+
+ if ( strstr( $host, ':' ) ) {
+ $conn_info = explode( ':', $host );
+ }
+
+ $this->_connectionID = @ldap_connect( $conn_info[0], $conn_info[1] );
+ }
+ if (!$this->_connectionID) {
+ $e = 'Could not connect to ' . $conn_info[0];
+ $this->_errorMsg = $e;
+ if ($this->debug) ADOConnection::outp($e);
+ return false;
+ }
+ if( count( $LDAP_CONNECT_OPTIONS ) > 0 ) {
+ $this->_inject_bind_options( $LDAP_CONNECT_OPTIONS );
+ }
+
+ if ($username) {
+ $bind = @ldap_bind( $this->_connectionID, $username, $password );
+ } else {
+ $username = 'anonymous';
+ $bind = @ldap_bind( $this->_connectionID );
+ }
+
+ if (!$bind) {
+ $e = sprintf($this->_bind_errmsg,ldap_error($this->_connectionID));
+ $this->_errorMsg = $e;
+ if ($this->debug) ADOConnection::outp($e);
+ return false;
+ }
+ $this->_errorMsg = '';
+ $this->database = $ldapbase;
+ return $this->_connectionID;
+ }
+
+/*
+ Valid Domain Values for LDAP Options:
+
+ LDAP_OPT_DEREF (integer)
+ LDAP_OPT_SIZELIMIT (integer)
+ LDAP_OPT_TIMELIMIT (integer)
+ LDAP_OPT_PROTOCOL_VERSION (integer)
+ LDAP_OPT_ERROR_NUMBER (integer)
+ LDAP_OPT_REFERRALS (boolean)
+ LDAP_OPT_RESTART (boolean)
+ LDAP_OPT_HOST_NAME (string)
+ LDAP_OPT_ERROR_STRING (string)
+ LDAP_OPT_MATCHED_DN (string)
+ LDAP_OPT_SERVER_CONTROLS (array)
+ LDAP_OPT_CLIENT_CONTROLS (array)
+
+ Make sure to set this BEFORE calling Connect()
+
+ Example:
+
+ $LDAP_CONNECT_OPTIONS = Array(
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_DEREF,
+ "OPTION_VALUE"=>2
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_SIZELIMIT,
+ "OPTION_VALUE"=>100
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_TIMELIMIT,
+ "OPTION_VALUE"=>30
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_PROTOCOL_VERSION,
+ "OPTION_VALUE"=>3
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_ERROR_NUMBER,
+ "OPTION_VALUE"=>13
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_REFERRALS,
+ "OPTION_VALUE"=>FALSE
+ ),
+ Array (
+ "OPTION_NAME"=>LDAP_OPT_RESTART,
+ "OPTION_VALUE"=>FALSE
+ )
+ );
+*/
+
+ function _inject_bind_options( $options ) {
+ foreach( $options as $option ) {
+ ldap_set_option( $this->_connectionID, $option["OPTION_NAME"], $option["OPTION_VALUE"] )
+ or die( "Unable to set server option: " . $option["OPTION_NAME"] );
+ }
+ }
+
+ /* returns _queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ $rs = @ldap_search( $this->_connectionID, $this->database, $sql );
+ $this->_errorMsg = ($rs) ? '' : 'Search error on '.$sql.': '.ldap_error($this->_connectionID);
+ return $rs;
+ }
+
+ function ErrorMsg()
+ {
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ return @ldap_errno($this->_connectionID);
+ }
+
+ /* closes the LDAP connection */
+ function _close()
+ {
+ @ldap_close( $this->_connectionID );
+ $this->_connectionID = false;
+ }
+
+ function SelectDB($db) {
+ $this->database = $db;
+ return true;
+ } // SelectDB
+
+ function ServerInfo()
+ {
+ if( !empty( $this->version ) ) {
+ return $this->version;
+ }
+
+ $version = array();
+ /*
+ Determines how aliases are handled during search.
+ LDAP_DEREF_NEVER (0x00)
+ LDAP_DEREF_SEARCHING (0x01)
+ LDAP_DEREF_FINDING (0x02)
+ LDAP_DEREF_ALWAYS (0x03)
+ The LDAP_DEREF_SEARCHING value means aliases are dereferenced during the search but
+ not when locating the base object of the search. The LDAP_DEREF_FINDING value means
+ aliases are dereferenced when locating the base object but not during the search.
+ Default: LDAP_DEREF_NEVER
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_DEREF, $version['LDAP_OPT_DEREF'] ) ;
+ switch ( $version['LDAP_OPT_DEREF'] ) {
+ case 0:
+ $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_NEVER';
+ case 1:
+ $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_SEARCHING';
+ case 2:
+ $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_FINDING';
+ case 3:
+ $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_ALWAYS';
+ }
+
+ /*
+ A limit on the number of entries to return from a search.
+ LDAP_NO_LIMIT (0) means no limit.
+ Default: LDAP_NO_LIMIT
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_SIZELIMIT, $version['LDAP_OPT_SIZELIMIT'] );
+ if ( $version['LDAP_OPT_SIZELIMIT'] == 0 ) {
+ $version['LDAP_OPT_SIZELIMIT'] = 'LDAP_NO_LIMIT';
+ }
+
+ /*
+ A limit on the number of seconds to spend on a search.
+ LDAP_NO_LIMIT (0) means no limit.
+ Default: LDAP_NO_LIMIT
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_TIMELIMIT, $version['LDAP_OPT_TIMELIMIT'] );
+ if ( $version['LDAP_OPT_TIMELIMIT'] == 0 ) {
+ $version['LDAP_OPT_TIMELIMIT'] = 'LDAP_NO_LIMIT';
+ }
+
+ /*
+ Determines whether the LDAP library automatically follows referrals returned by LDAP servers or not.
+ LDAP_OPT_ON
+ LDAP_OPT_OFF
+ Default: ON
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_REFERRALS, $version['LDAP_OPT_REFERRALS'] );
+ if ( $version['LDAP_OPT_REFERRALS'] == 0 ) {
+ $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_OFF';
+ } else {
+ $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_ON';
+ }
+
+ /*
+ Determines whether LDAP I/O operations are automatically restarted if they abort prematurely.
+ LDAP_OPT_ON
+ LDAP_OPT_OFF
+ Default: OFF
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_RESTART, $version['LDAP_OPT_RESTART'] );
+ if ( $version['LDAP_OPT_RESTART'] == 0 ) {
+ $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_OFF';
+ } else {
+ $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_ON';
+ }
+
+ /*
+ This option indicates the version of the LDAP protocol used when communicating with the primary LDAP server.
+ LDAP_VERSION2 (2)
+ LDAP_VERSION3 (3)
+ Default: LDAP_VERSION2 (2)
+ */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_PROTOCOL_VERSION, $version['LDAP_OPT_PROTOCOL_VERSION'] );
+ if ( $version['LDAP_OPT_PROTOCOL_VERSION'] == 2 ) {
+ $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION2';
+ } else {
+ $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION3';
+ }
+
+ /* The host name (or list of hosts) for the primary LDAP server. */
+ ldap_get_option( $this->_connectionID, LDAP_OPT_HOST_NAME, $version['LDAP_OPT_HOST_NAME'] );
+ ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_NUMBER, $version['LDAP_OPT_ERROR_NUMBER'] );
+ ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_STRING, $version['LDAP_OPT_ERROR_STRING'] );
+ ldap_get_option( $this->_connectionID, LDAP_OPT_MATCHED_DN, $version['LDAP_OPT_MATCHED_DN'] );
+
+ return $this->version = $version;
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_ldap extends ADORecordSet{
+
+ var $databaseType = "ldap";
+ var $canSeek = false;
+ var $_entryID; /* keeps track of the entry resource identifier */
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch ($mode)
+ {
+ case ADODB_FETCH_NUM:
+ $this->fetchMode = LDAP_NUM;
+ break;
+ case ADODB_FETCH_ASSOC:
+ $this->fetchMode = LDAP_ASSOC;
+ break;
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default:
+ $this->fetchMode = LDAP_BOTH;
+ break;
+ }
+
+ parent::__construct($queryID);
+ }
+
+ function _initrs()
+ {
+ /*
+ This could be teaked to respect the $COUNTRECS directive from ADODB
+ It's currently being used in the _fetch() function and the
+ GetAssoc() function
+ */
+ $this->_numOfRows = ldap_count_entries( $this->connection->_connectionID, $this->_queryID );
+ }
+
+ /*
+ Return whole recordset as a multi-dimensional associative array
+ */
+ function GetAssoc($force_array = false, $first2cols = false)
+ {
+ $records = $this->_numOfRows;
+ $results = array();
+ for ( $i=0; $i < $records; $i++ ) {
+ foreach ( $this->fields as $k=>$v ) {
+ if ( is_array( $v ) ) {
+ if ( $v['count'] == 1 ) {
+ $results[$i][$k] = $v[0];
+ } else {
+ array_shift( $v );
+ $results[$i][$k] = $v;
+ }
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ function GetRowAssoc($upper = ADODB_ASSOC_CASE)
+ {
+ $results = array();
+ foreach ( $this->fields as $k=>$v ) {
+ if ( is_array( $v ) ) {
+ if ( $v['count'] == 1 ) {
+ $results[$k] = $v[0];
+ } else {
+ array_shift( $v );
+ $results[$k] = $v;
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ function GetRowNums()
+ {
+ $results = array();
+ foreach ( $this->fields as $k=>$v ) {
+ static $i = 0;
+ if (is_array( $v )) {
+ if ( $v['count'] == 1 ) {
+ $results[$i] = $v[0];
+ } else {
+ array_shift( $v );
+ $results[$i] = $v;
+ }
+ $i++;
+ }
+ }
+ return $results;
+ }
+
+ function _fetch()
+ {
+ if ( $this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0 ) {
+ return false;
+ }
+
+ if ( $this->_currentRow == 0 ) {
+ $this->_entryID = ldap_first_entry( $this->connection->_connectionID, $this->_queryID );
+ } else {
+ $this->_entryID = ldap_next_entry( $this->connection->_connectionID, $this->_entryID );
+ }
+
+ $this->fields = ldap_get_attributes( $this->connection->_connectionID, $this->_entryID );
+ $this->_numOfFields = $this->fields['count'];
+
+ switch ( $this->fetchMode ) {
+
+ case LDAP_ASSOC:
+ $this->fields = $this->GetRowAssoc();
+ break;
+
+ case LDAP_NUM:
+ $this->fields = array_merge($this->GetRowNums(),$this->GetRowAssoc());
+ break;
+
+ case LDAP_BOTH:
+ default:
+ $this->fields = $this->GetRowNums();
+ break;
+ }
+
+ return is_array( $this->fields );
+ }
+
+ function _close() {
+ @ldap_free_result( $this->_queryID );
+ $this->_queryID = false;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mssql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mssql.inc.php
new file mode 100644
index 0000000..b9aab8e
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mssql.inc.php
@@ -0,0 +1,1194 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Native mssql driver. Requires mssql client. Works on Windows.
+ To configure for Unix, see
+ http://phpbuilder.com/columns/alberto20000919.php3
+
+*/
+
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+//----------------------------------------------------------------
+// MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002
+// and this causes tons of problems because localized versions of
+// MSSQL will return the dates in dmy or mdy order; and also the
+// month strings depends on what language has been configured. The
+// following two variables allow you to control the localization
+// settings - Ugh.
+//
+// MORE LOCALIZATION INFO
+// ----------------------
+// To configure datetime, look for and modify sqlcommn.loc,
+// typically found in c:\mssql\install
+// Also read :
+// http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918
+// Alternatively use:
+// CONVERT(char(12),datecol,120)
+//----------------------------------------------------------------
+
+
+// has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc
+if (ADODB_PHPVER >= 0x4300) {
+// docs say 4.2.0, but testing shows only since 4.3.0 does it work!
+ ini_set('mssql.datetimeconvert',0);
+} else {
+global $ADODB_mssql_mths; // array, months must be upper-case
+
+
+ $ADODB_mssql_date_order = 'mdy';
+ $ADODB_mssql_mths = array(
+ 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,
+ 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
+}
+
+//---------------------------------------------------------------------------
+// Call this to autoset $ADODB_mssql_date_order at the beginning of your code,
+// just after you connect to the database. Supports mdy and dmy only.
+// Not required for PHP 4.2.0 and above.
+function AutoDetect_MSSQL_Date_Order($conn)
+{
+global $ADODB_mssql_date_order;
+ $adate = $conn->GetOne('select getdate()');
+ if ($adate) {
+ $anum = (int) $adate;
+ if ($anum > 0) {
+ if ($anum > 31) {
+ //ADOConnection::outp( "MSSQL: YYYY-MM-DD date format not supported currently");
+ } else
+ $ADODB_mssql_date_order = 'dmy';
+ } else
+ $ADODB_mssql_date_order = 'mdy';
+ }
+}
+
+class ADODB_mssql extends ADOConnection {
+ var $databaseType = "mssql";
+ var $dataProvider = "mssql";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d\TH:i:s'";
+ var $hasInsertID = true;
+ var $substr = "substring";
+ var $length = 'len';
+ var $hasAffectedRows = true;
+ var $metaDatabasesSQL = "select name from sysdatabases where name <> 'master'";
+ var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))";
+ var $metaColumnsSQL = # xtype==61 is datetime
+ "select c.name,t.name,c.length,c.isnullable, c.status,
+ (case when c.xusertype=61 then 0 else c.xprec end),
+ (case when c.xusertype=61 then 0 else c.xscale end)
+ from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'";
+ var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
+ var $hasGenID = true;
+ var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ var $sysTimeStamp = 'GetDate()';
+ var $_has_mssql_init;
+ var $maxParameterLen = 4000;
+ var $arrayClass = 'ADORecordSet_array_mssql';
+ var $uniqueSort = true;
+ var $leftOuter = '*=';
+ var $rightOuter = '=*';
+ var $ansiOuter = true; // for mssql7 or later
+ var $poorAffectedRows = true;
+ var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
+ var $uniqueOrderBy = true;
+ var $_bindInputArray = true;
+ var $forceNewConnect = false;
+
+ function __construct()
+ {
+ $this->_has_mssql_init = (strnatcmp(PHP_VERSION,'4.1.0')>=0);
+ }
+
+ function ServerInfo()
+ {
+ global $ADODB_FETCH_MODE;
+
+
+ if ($this->fetchMode === false) {
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ } else
+ $savem = $this->SetFetchMode(ADODB_FETCH_NUM);
+
+ if (0) {
+ $stmt = $this->PrepareSP('sp_server_info');
+ $val = 2;
+ $this->Parameter($stmt,$val,'attribute_id');
+ $row = $this->GetRow($stmt);
+ }
+
+ $row = $this->GetRow("execute sp_server_info 2");
+
+
+ if ($this->fetchMode === false) {
+ $ADODB_FETCH_MODE = $savem;
+ } else
+ $this->SetFetchMode($savem);
+
+ $arr['description'] = $row[2];
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " ISNULL($field, $ifNull) "; // if MS SQL Server
+ }
+
+ function _insertid()
+ {
+ // SCOPE_IDENTITY()
+ // Returns the last IDENTITY value inserted into an IDENTITY column in
+ // the same scope. A scope is a module -- a stored procedure, trigger,
+ // function, or batch. Thus, two statements are in the same scope if
+ // they are in the same stored procedure, function, or batch.
+ if ($this->lastInsID !== false) {
+ return $this->lastInsID; // InsID from sp_executesql call
+ } else {
+ return $this->GetOne($this->identitySQL);
+ }
+ }
+
+
+
+ /**
+ * Correctly quotes a string so that all strings are escaped. We prefix and append
+ * to the string single-quotes.
+ * An example is $db->qstr("Don't bother",magic_quotes_runtime());
+ *
+ * @param s the string to quote
+ * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc().
+ * This undoes the stupidity of magic quotes for GPC.
+ *
+ * @return quoted string to be sent back to database
+ */
+ function qstr($s,$magic_quotes=false)
+ {
+ if (!$magic_quotes) {
+ return "'".str_replace("'",$this->replaceQuote,$s)."'";
+ }
+
+ // undo magic quotes for " unless sybase is on
+ $sybase = ini_get('magic_quotes_sybase');
+ if (!$sybase) {
+ $s = str_replace('\\"','"',$s);
+ if ($this->replaceQuote == "\\'") // ' already quoted, no need to change anything
+ return "'$s'";
+ else {// change \' to '' for sybase/mssql
+ $s = str_replace('\\\\','\\',$s);
+ return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
+ }
+ } else {
+ return "'".$s."'";
+ }
+ }
+// moodle change end - see readme_moodle.txt
+
+ function _affectedrows()
+ {
+ return $this->GetOne('select @@rowcount');
+ }
+
+ var $_dropSeqSQL = "drop table %s";
+
+ function CreateSequence($seq='adodbseq',$start=1)
+ {
+
+ $this->Execute('BEGIN TRANSACTION adodbseq');
+ $start -= 1;
+ $this->Execute("create table $seq (id float(53))");
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ $this->Execute('ROLLBACK TRANSACTION adodbseq');
+ return false;
+ }
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return true;
+ }
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ //$this->debug=1;
+ $this->Execute('BEGIN TRANSACTION adodbseq');
+ $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
+ if (!$ok) {
+ $this->Execute("create table $seq (id float(53))");
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ $this->Execute('ROLLBACK TRANSACTION adodbseq');
+ return false;
+ }
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return $start;
+ }
+ $num = $this->GetOne("select id from $seq");
+ $this->Execute('COMMIT TRANSACTION adodbseq');
+ return $num;
+
+ // in old implementation, pre 1.90, we returned GUID...
+ //return $this->GetOne("SELECT CONVERT(varchar(255), NEWID()) AS 'Char'");
+ }
+
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ if ($nrows > 0 && $offset <= 0) {
+ $sql = preg_replace(
+ '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop." $nrows ",$sql);
+
+ if ($secs2cache)
+ $rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
+ else
+ $rs = $this->Execute($sql,$inputarr);
+ } else
+ $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+
+ return $rs;
+ }
+
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '+';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "datename(yyyy,$col)";
+ break;
+ case 'M':
+ $s .= "convert(char(3),$col,0)";
+ break;
+ case 'm':
+ $s .= "replace(str(month($col),2),' ','0')";
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "datename(quarter,$col)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "replace(str(day($col),2),' ','0')";
+ break;
+ case 'h':
+ $s .= "substring(convert(char(14),$col,0),13,2)";
+ break;
+
+ case 'H':
+ $s .= "replace(str(datepart(hh,$col),2),' ','0')";
+ break;
+
+ case 'i':
+ $s .= "replace(str(datepart(mi,$col),2),' ','0')";
+ break;
+ case 's':
+ $s .= "replace(str(datepart(ss,$col),2),' ','0')";
+ break;
+ case 'a':
+ case 'A':
+ $s .= "substring(convert(char(19),$col,0),18,2)";
+ break;
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ break;
+ }
+ }
+ return $s;
+ }
+
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $ok = $this->Execute('BEGIN TRAN');
+ return $ok;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ $ok = $this->Execute('COMMIT TRAN');
+ return $ok;
+ }
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $ok = $this->Execute('ROLLBACK TRAN');
+ return $ok;
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET TRANSACTION ".$transaction_mode);
+ }
+
+ /*
+ Usage:
+
+ $this->BeginTrans();
+ $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables
+
+ # some operation on both tables table1 and table2
+
+ $this->CommitTrans();
+
+ See http://www.swynk.com/friends/achigrik/SQL70Locks.asp
+ */
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if ($col == '1 as adodbignore') $col = 'top 1 null as ignore';
+ if (!$this->transCnt) $this->BeginTrans();
+ return $this->GetOne("select $col from $tables with (ROWLOCK,HOLDLOCK) where $where");
+ }
+
+
+ function MetaColumns($table, $normalize=true)
+ {
+// $arr = ADOConnection::MetaColumns($table);
+// return $arr;
+
+ $this->_findschema($table,$schema);
+ if ($schema) {
+ $dbName = $this->database;
+ $this->SelectDB($schema);
+ }
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+
+ if ($schema) {
+ $this->SelectDB($dbName);
+ }
+
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF){
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+
+ $fld->not_null = (!$rs->fields[3]);
+ $fld->auto_increment = ($rs->fields[4] == 128); // sys.syscolumns status field. 0x80 = 128 ref: http://msdn.microsoft.com/en-us/library/ms186816.aspx
+
+ if (isset($rs->fields[5]) && $rs->fields[5]) {
+ if ($rs->fields[5]>0) $fld->max_length = $rs->fields[5];
+ $fld->scale = $rs->fields[6];
+ if ($fld->scale>0) $fld->max_length += 1;
+ } else
+ $fld->max_length = $rs->fields[2];
+
+ if ($save == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $retarr;
+
+ }
+
+
+ function MetaIndexes($table,$primary=false, $owner=false)
+ {
+ $table = $this->qstr($table);
+
+ $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
+ CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK,
+ CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
+ FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
+ INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
+ INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
+ WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table
+ ORDER BY O.name, I.Name, K.keyno";
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute($sql);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return FALSE;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ if ($primary && !$row[5]) continue;
+
+ $indexes[$row[0]]['unique'] = $row[6];
+ $indexes[$row[0]]['columns'][] = $row[1];
+ }
+ return $indexes;
+ }
+
+ function MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $table = $this->qstr(strtoupper($table));
+
+ $sql =
+"select object_name(constid) as constraint_name,
+ col_name(fkeyid, fkey) as column_name,
+ object_name(rkeyid) as referenced_table_name,
+ col_name(rkeyid, rkey) as referenced_column_name
+from sysforeignkeys
+where upper(object_name(fkeyid)) = $table
+order by constraint_name, referenced_table_name, keyno";
+
+ $constraints = $this->GetArray($sql);
+
+ $ADODB_FETCH_MODE = $save;
+
+ $arr = false;
+ foreach($constraints as $constr) {
+ //print_r($constr);
+ $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3];
+ }
+ if (!$arr) return false;
+
+ $arr2 = false;
+
+ foreach($arr as $k => $v) {
+ foreach($v as $a => $b) {
+ if ($upper) $a = strtoupper($a);
+ $arr2[$a] = $b;
+ }
+ }
+ return $arr2;
+ }
+
+ //From: Fernando Moreira <FMoreira@imediata.pt>
+ function MetaDatabases()
+ {
+ if(@mssql_select_db("master")) {
+ $qry=$this->metaDatabasesSQL;
+ if($rs=@mssql_query($qry,$this->_connectionID)){
+ $tmpAr=$ar=array();
+ while($tmpAr=@mssql_fetch_row($rs))
+ $ar[]=$tmpAr[0];
+ @mssql_select_db($this->database);
+ if(sizeof($ar))
+ return($ar);
+ else
+ return(false);
+ } else {
+ @mssql_select_db($this->database);
+ return(false);
+ }
+ }
+ return(false);
+ }
+
+ // "Stein-Aksel Basma" <basma@accelero.no>
+ // tested with MSSQL 2000
+ function MetaPrimaryKeys($table, $owner=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = '';
+ $this->_findschema($table,$schema);
+ if (!$schema) $schema = $this->database;
+ if ($schema) $schema = "and k.table_catalog like '$schema%'";
+
+ $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k,
+ information_schema.table_constraints tc
+ where tc.constraint_name = k.constraint_name and tc.constraint_type =
+ 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position ";
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $a = $this->GetCol($sql);
+ $ADODB_FETCH_MODE = $savem;
+
+ if ($a && sizeof($a)>0) return $a;
+ $false = false;
+ return $false;
+ }
+
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(($mask));
+ $this->metaTablesSQL .= " AND name like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ if ($this->_connectionID) {
+ return @mssql_select_db($dbName);
+ }
+ else return false;
+ }
+
+ function ErrorMsg()
+ {
+ if (empty($this->_errorMsg)){
+ $this->_errorMsg = mssql_get_last_message();
+ }
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode;
+ if (empty($this->_errorMsg)) {
+ $this->_errorMsg = mssql_get_last_message();
+ }
+ $id = @mssql_query("select @@ERROR",$this->_connectionID);
+ if (!$id) return false;
+ $arr = mssql_fetch_array($id);
+ @mssql_free_result($id);
+ if (is_array($arr)) return $arr[0];
+ else return -1;
+ }
+
+ // returns true or false, newconnect supported since php 5.1.0.
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$newconnect=false)
+ {
+ if (!function_exists('mssql_pconnect')) return null;
+ $this->_connectionID = mssql_connect($argHostname,$argUsername,$argPassword,$newconnect);
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('mssql_pconnect')) return null;
+ $this->_connectionID = mssql_pconnect($argHostname,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+
+ // persistent connections can forget to rollback on crash, so we do it here.
+ if ($this->autoRollback) {
+ $cnt = $this->GetOne('select @@TRANCOUNT');
+ while (--$cnt >= 0) $this->Execute('ROLLBACK TRAN');
+ }
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, true);
+ }
+
+ function Prepare($sql)
+ {
+ $sqlarr = explode('?',$sql);
+ if (sizeof($sqlarr) <= 1) return $sql;
+ $sql2 = $sqlarr[0];
+ for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) {
+ $sql2 .= '@P'.($i-1) . $sqlarr[$i];
+ }
+ return array($sql,$this->qstr($sql2),$max,$sql2);
+ }
+
+ function PrepareSP($sql,$param=true)
+ {
+ if (!$this->_has_mssql_init) {
+ ADOConnection::outp( "PrepareSP: mssql_init only available since PHP 4.1.0");
+ return $sql;
+ }
+ $stmt = mssql_init($sql,$this->_connectionID);
+ if (!$stmt) return $sql;
+ return array($sql,$stmt);
+ }
+
+ // returns concatenated string
+ // MSSQL requires integers to be cast as strings
+ // automatically cast every datatype to VARCHAR(255)
+ // @author David Rogers (introspectshun)
+ function Concat()
+ {
+ $s = "";
+ $arr = func_get_args();
+
+ // Split single record on commas, if possible
+ if (sizeof($arr) == 1) {
+ foreach ($arr as $arg) {
+ $args = explode(',', $arg);
+ }
+ $arr = $args;
+ }
+
+ array_walk(
+ $arr,
+ function(&$value, $key) {
+ $value = "CAST(" . $value . " AS VARCHAR(255))";
+ }
+ );
+ $s = implode('+',$arr);
+ if (sizeof($arr) > 0) return "$s";
+
+ return '';
+ }
+
+ /*
+ Usage:
+ $stmt = $db->PrepareSP('SP_RUNSOMETHING'); -- takes 2 params, @myid and @group
+
+ # note that the parameter does not have @ in front!
+ $db->Parameter($stmt,$id,'myid');
+ $db->Parameter($stmt,$group,'group',false,64);
+ $db->Execute($stmt);
+
+ @param $stmt Statement returned by Prepare() or PrepareSP().
+ @param $var PHP variable to bind to. Can set to null (for isNull support).
+ @param $name Name of stored procedure variable name to bind to.
+ @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8.
+ @param [$maxLen] Holds an maximum length of the variable.
+ @param [$type] The data type of $var. Legal values depend on driver.
+
+ See mssql_bind documentation at php.net.
+ */
+ function Parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=4000, $type=false)
+ {
+ if (!$this->_has_mssql_init) {
+ ADOConnection::outp( "Parameter: mssql_bind only available since PHP 4.1.0");
+ return false;
+ }
+
+ $isNull = is_null($var); // php 4.0.4 and above...
+
+ if ($type === false)
+ switch(gettype($var)) {
+ default:
+ case 'string': $type = SQLVARCHAR; break;
+ case 'double': $type = SQLFLT8; break;
+ case 'integer': $type = SQLINT4; break;
+ case 'boolean': $type = SQLINT1; break; # SQLBIT not supported in 4.1.0
+ }
+
+ if ($this->debug) {
+ $prefix = ($isOutput) ? 'Out' : 'In';
+ $ztype = (empty($type)) ? 'false' : $type;
+ ADOConnection::outp( "{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);");
+ }
+ /*
+ See http://phplens.com/lens/lensforum/msgs.php?id=7231
+
+ RETVAL is HARD CODED into php_mssql extension:
+ The return value (a long integer value) is treated like a special OUTPUT parameter,
+ called "RETVAL" (without the @). See the example at mssql_execute to
+ see how it works. - type: one of this new supported PHP constants.
+ SQLTEXT, SQLVARCHAR,SQLCHAR, SQLINT1,SQLINT2, SQLINT4, SQLBIT,SQLFLT8
+ */
+ if ($name !== 'RETVAL') $name = '@'.$name;
+ return mssql_bind($stmt[1], $name, $var, $type, $isOutput, $isNull, $maxLen);
+ }
+
+ /*
+ Unfortunately, it appears that mssql cannot handle varbinary > 255 chars
+ So all your blobs must be of type "image".
+
+ Remember to set in php.ini the following...
+
+ ; Valid range 0 - 2147483647. Default = 4096.
+ mssql.textlimit = 0 ; zero to pass through
+
+ ; Valid range 0 - 2147483647. Default = 4096.
+ mssql.textsize = 0 ; zero to pass through
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+
+ if (strtoupper($blobtype) == 'CLOB') {
+ $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where";
+ return $this->Execute($sql) != false;
+ }
+ $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where";
+ return $this->Execute($sql) != false;
+ }
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ $this->_errorMsg = false;
+ if (is_array($inputarr)) {
+
+ # bind input params with sp_executesql:
+ # see http://www.quest-pipelines.com/newsletter-v3/0402_F.htm
+ # works only with sql server 7 and newer
+ $getIdentity = false;
+ if (!is_array($sql) && preg_match('/^\\s*insert/i', $sql)) {
+ $getIdentity = true;
+ $sql .= (preg_match('/;\\s*$/i', $sql) ? ' ' : '; ') . $this->identitySQL;
+ }
+ if (!is_array($sql)) $sql = $this->Prepare($sql);
+ $params = '';
+ $decl = '';
+ $i = 0;
+ foreach($inputarr as $v) {
+ if ($decl) {
+ $decl .= ', ';
+ $params .= ', ';
+ }
+ if (is_string($v)) {
+ $len = strlen($v);
+ if ($len == 0) $len = 1;
+
+ if ($len > 4000 ) {
+ // NVARCHAR is max 4000 chars. Let's use NTEXT
+ $decl .= "@P$i NTEXT";
+ } else {
+ $decl .= "@P$i NVARCHAR($len)";
+ }
+
+
+ if (substr($v,0,1) == "'" && substr($v,-1,1) == "'")
+ /*
+ * String is already fully quoted
+ */
+ $inputVar = $v;
+ else
+ $inputVar = $this->qstr($v);
+
+ $params .= "@P$i=N" . $inputVar;
+ } else if (is_integer($v)) {
+ $decl .= "@P$i INT";
+ $params .= "@P$i=".$v;
+ } else if (is_float($v)) {
+ $decl .= "@P$i FLOAT";
+ $params .= "@P$i=".$v;
+ } else if (is_bool($v)) {
+ $decl .= "@P$i INT"; # Used INT just in case BIT in not supported on the user's MSSQL version. It will cast appropriately.
+ $params .= "@P$i=".(($v)?'1':'0'); # True == 1 in MSSQL BIT fields and acceptable for storing logical true in an int field
+ } else {
+ $decl .= "@P$i CHAR"; # Used char because a type is required even when the value is to be NULL.
+ $params .= "@P$i=NULL";
+ }
+ $i += 1;
+ }
+ $decl = $this->qstr($decl);
+ if ($this->debug) ADOConnection::outp("<font size=-1>sp_executesql N{$sql[1]},N$decl,$params</font>");
+ $rez = mssql_query("sp_executesql N{$sql[1]},N$decl,$params", $this->_connectionID);
+ if ($getIdentity) {
+ $arr = @mssql_fetch_row($rez);
+ $this->lastInsID = isset($arr[0]) ? $arr[0] : false;
+ @mssql_data_seek($rez, 0);
+ }
+
+ } else if (is_array($sql)) {
+ # PrepareSP()
+ $rez = mssql_execute($sql[1]);
+ $this->lastInsID = false;
+
+ } else {
+ $rez = mssql_query($sql,$this->_connectionID);
+ $this->lastInsID = false;
+ }
+ return $rez;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if ($this->transCnt) $this->RollbackTrans();
+ $rez = @mssql_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $rez;
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_mssql::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_mssql::UnixTimeStamp($v);
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_mssql extends ADORecordSet {
+
+ var $databaseType = "mssql";
+ var $canSeek = true;
+ var $hasFetchAssoc; // see http://phplens.com/lens/lensforum/msgs.php?id=6083
+ // _mths works only in non-localised system
+
+ function __construct($id,$mode=false)
+ {
+ // freedts check...
+ $this->hasFetchAssoc = function_exists('mssql_fetch_assoc');
+
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+
+ }
+ $this->fetchMode = $mode;
+ return parent::__construct($id,$mode);
+ }
+
+
+ function _initrs()
+ {
+ GLOBAL $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS)? @mssql_num_rows($this->_queryID):-1;
+ $this->_numOfFields = @mssql_num_fields($this->_queryID);
+ }
+
+
+ //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se>
+ // get next resultset - requires PHP 4.0.5 or later
+ function NextRecordSet()
+ {
+ if (!mssql_next_result($this->_queryID)) return false;
+ $this->_inited = false;
+ $this->bind = false;
+ $this->_currentRow = -1;
+ $this->Init();
+ return true;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved. */
+
+ function FetchField($fieldOffset = -1)
+ {
+ if ($fieldOffset != -1) {
+ $f = @mssql_fetch_field($this->_queryID, $fieldOffset);
+ }
+ else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */
+ $f = @mssql_fetch_field($this->_queryID);
+ }
+ $false = false;
+ if (empty($f)) return $false;
+ return $f;
+ }
+
+ function _seek($row)
+ {
+ return @mssql_data_seek($this->_queryID, $row);
+ }
+
+ // speedup
+ function MoveNext()
+ {
+ if ($this->EOF) return false;
+
+ $this->_currentRow++;
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ if ($this->fetchMode & ADODB_FETCH_NUM) {
+ //ADODB_FETCH_BOTH mode
+ $this->fields = @mssql_fetch_array($this->_queryID);
+ }
+ else {
+ if ($this->hasFetchAssoc) {// only for PHP 4.2.0 or later
+ $this->fields = @mssql_fetch_assoc($this->_queryID);
+ } else {
+ $flds = @mssql_fetch_array($this->_queryID);
+ if (is_array($flds)) {
+ $fassoc = array();
+ foreach($flds as $k => $v) {
+ if (is_numeric($k)) continue;
+ $fassoc[$k] = $v;
+ }
+ $this->fields = $fassoc;
+ } else
+ $this->fields = false;
+ }
+ }
+
+ if (is_array($this->fields)) {
+ if (ADODB_ASSOC_CASE == 0) {
+ foreach($this->fields as $k=>$v) {
+ $kn = strtolower($k);
+ if ($kn <> $k) {
+ unset($this->fields[$k]);
+ $this->fields[$kn] = $v;
+ }
+ }
+ } else if (ADODB_ASSOC_CASE == 1) {
+ foreach($this->fields as $k=>$v) {
+ $kn = strtoupper($k);
+ if ($kn <> $k) {
+ unset($this->fields[$k]);
+ $this->fields[$kn] = $v;
+ }
+ }
+ }
+ }
+ } else {
+ $this->fields = @mssql_fetch_row($this->_queryID);
+ }
+ if ($this->fields) return true;
+ $this->EOF = true;
+
+ return false;
+ }
+
+
+ // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4
+ // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot!
+ function _fetch($ignore_fields=false)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ if ($this->fetchMode & ADODB_FETCH_NUM) {
+ //ADODB_FETCH_BOTH mode
+ $this->fields = @mssql_fetch_array($this->_queryID);
+ } else {
+ if ($this->hasFetchAssoc) // only for PHP 4.2.0 or later
+ $this->fields = @mssql_fetch_assoc($this->_queryID);
+ else {
+ $this->fields = @mssql_fetch_array($this->_queryID);
+ if (@is_array($$this->fields)) {
+ $fassoc = array();
+ foreach($$this->fields as $k => $v) {
+ if (is_integer($k)) continue;
+ $fassoc[$k] = $v;
+ }
+ $this->fields = $fassoc;
+ }
+ }
+ }
+
+ if (!$this->fields) {
+ } else if (ADODB_ASSOC_CASE == 0) {
+ foreach($this->fields as $k=>$v) {
+ $kn = strtolower($k);
+ if ($kn <> $k) {
+ unset($this->fields[$k]);
+ $this->fields[$kn] = $v;
+ }
+ }
+ } else if (ADODB_ASSOC_CASE == 1) {
+ foreach($this->fields as $k=>$v) {
+ $kn = strtoupper($k);
+ if ($kn <> $k) {
+ unset($this->fields[$k]);
+ $this->fields[$kn] = $v;
+ }
+ }
+ }
+ } else {
+ $this->fields = @mssql_fetch_row($this->_queryID);
+ }
+ return $this->fields;
+ }
+
+ /* close() only needs to be called if you are worried about using too much memory while your script
+ is running. All associated result memory for the specified result identifier will automatically be freed. */
+
+ function _close()
+ {
+ if($this->_queryID) {
+ $rez = mssql_free_result($this->_queryID);
+ $this->_queryID = false;
+ return $rez;
+ }
+ return true;
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_mssql::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_mssql::UnixTimeStamp($v);
+ }
+
+}
+
+
+class ADORecordSet_array_mssql extends ADORecordSet_array {
+ function __construct($id=-1,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+
+ if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
+
+ global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+
+ //Dec 30 2000 12:00AM
+ if ($ADODB_mssql_date_order == 'dmy') {
+ if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+ return parent::UnixDate($v);
+ }
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[1];
+ $themth = substr(strtoupper($rr[2]),0,3);
+ } else {
+ if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+ return parent::UnixDate($v);
+ }
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[2];
+ $themth = substr(strtoupper($rr[1]),0,3);
+ }
+ $themth = $ADODB_mssql_mths[$themth];
+ if ($themth <= 0) return false;
+ // h-m-s-MM-DD-YY
+ return mktime(0,0,0,$themth,$theday,$rr[3]);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+
+ if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
+
+ global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+
+ //Dec 30 2000 12:00AM
+ if ($ADODB_mssql_date_order == 'dmy') {
+ if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+ ,$v, $rr)) return parent::UnixTimeStamp($v);
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[1];
+ $themth = substr(strtoupper($rr[2]),0,3);
+ } else {
+ if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+ ,$v, $rr)) return parent::UnixTimeStamp($v);
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[2];
+ $themth = substr(strtoupper($rr[1]),0,3);
+ }
+
+ $themth = $ADODB_mssql_mths[$themth];
+ if ($themth <= 0) return false;
+
+ switch (strtoupper($rr[6])) {
+ case 'P':
+ if ($rr[4]<12) $rr[4] += 12;
+ break;
+ case 'A':
+ if ($rr[4]==12) $rr[4] = 0;
+ break;
+ default:
+ break;
+ }
+ // h-m-s-MM-DD-YY
+ return mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]);
+ }
+}
+
+/*
+Code Example 1:
+
+select object_name(constid) as constraint_name,
+ object_name(fkeyid) as table_name,
+ col_name(fkeyid, fkey) as column_name,
+ object_name(rkeyid) as referenced_table_name,
+ col_name(rkeyid, rkey) as referenced_column_name
+from sysforeignkeys
+where object_name(fkeyid) = x
+order by constraint_name, table_name, referenced_table_name, keyno
+
+Code Example 2:
+select constraint_name,
+ column_name,
+ ordinal_position
+from information_schema.key_column_usage
+where constraint_catalog = db_name()
+and table_name = x
+order by constraint_name, ordinal_position
+
+http://www.databasejournal.com/scripts/article.php/1440551
+*/
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mssql_n.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mssql_n.inc.php
new file mode 100644
index 0000000..28b29bb
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mssql_n.inc.php
@@ -0,0 +1,250 @@
+<?php
+
+/// $Id $
+
+///////////////////////////////////////////////////////////////////////////
+// //
+// NOTICE OF COPYRIGHT //
+// //
+// ADOdb - Database Abstraction Library for PHP //
+// //
+// Latest version is available at http://adodb.org //
+// //
+// Copyright (c) 2000-2014 John Lim (jlim\@natsoft.com.my) //
+// All rights reserved. //
+// Released under both BSD license and LGPL library license. //
+// Whenever there is any discrepancy between the two licenses, //
+// the BSD license will take precedence //
+// //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment //
+// http://moodle.com //
+// //
+// Copyright (C) 2001-3001 Martin Dougiamas http://dougiamas.com //
+// (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation; either version 2 of the License, or //
+// (at your option) any later version. //
+// //
+// This program 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 General Public License for more details: //
+// //
+// http://www.gnu.org/copyleft/gpl.html //
+// //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+* MSSQL Driver with auto-prepended "N" for correct unicode storage
+* of SQL literal strings. Intended to be used with MSSQL drivers that
+* are sending UCS-2 data to MSSQL (FreeTDS and ODBTP) in order to get
+* true cross-db compatibility from the application point of view.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+// one useful constant
+if (!defined('SINGLEQUOTE')) define('SINGLEQUOTE', "'");
+
+include_once(ADODB_DIR.'/drivers/adodb-mssql.inc.php');
+
+class ADODB_mssql_n extends ADODB_mssql {
+ var $databaseType = "mssql_n";
+
+ function _query($sql,$inputarr=false)
+ {
+ $sql = $this->_appendN($sql);
+ return ADODB_mssql::_query($sql,$inputarr);
+ }
+
+ /**
+ * This function will intercept all the literals used in the SQL, prepending the "N" char to them
+ * in order to allow mssql to store properly data sent in the correct UCS-2 encoding (by freeTDS
+ * and ODBTP) keeping SQL compatibility at ADOdb level (instead of hacking every project to add
+ * the "N" notation when working against MSSQL.
+ *
+ * The orginal note indicated that this hack should only be used if ALL the char-based columns
+ * in your DB are of type nchar, nvarchar and ntext, but testing seems to indicate that SQL server
+ * doesn't seem to care if the statement is used against char etc fields.
+ *
+ * @todo This function should raise an ADOdb error if one of the transformations fail
+ *
+ * @param mixed $inboundData Either a string containing an SQL statement
+ * or an array with resources from prepared statements
+ *
+ * @return mixed
+ */
+ function _appendN($inboundData) {
+
+ $inboundIsArray = false;
+
+ if (is_array($inboundData))
+ {
+ $inboundIsArray = true;
+ $inboundArray = $inboundData;
+ } else
+ $inboundArray = (array)$inboundData;
+
+ /*
+ * All changes will be placed here
+ */
+ $outboundArray = $inboundArray;
+
+ foreach($inboundArray as $inboundKey=>$inboundValue)
+ {
+
+ if (is_resource($inboundValue))
+ {
+ /*
+ * Prepared statement resource
+ */
+ if ($this->debug)
+ ADOConnection::outp("{$this->databaseType} index $inboundKey value is resource, continue");
+
+ continue;
+ }
+
+ if (strpos($inboundValue, SINGLEQUOTE) === false)
+ {
+ /*
+ * Check we have something to manipulate
+ */
+ if ($this->debug)
+ ADOConnection::outp("{$this->databaseType} index $inboundKey value $inboundValue has no single quotes, continue");
+ continue;
+ }
+
+ /*
+ * Check we haven't an odd number of single quotes (this can cause problems below
+ * and should be considered one wrong SQL). Exit with debug info.
+ */
+ if ((substr_count($inboundValue, SINGLEQUOTE) & 1))
+ {
+ if ($this->debug)
+ ADOConnection::outp("{$this->databaseType} internal transformation: not converted. Wrong number of quotes (odd)");
+
+ break;
+ }
+
+ /*
+ * Check we haven't any backslash + single quote combination. It should mean wrong
+ * backslashes use (bad magic_quotes_sybase?). Exit with debug info.
+ */
+ $regexp = '/(\\\\' . SINGLEQUOTE . '[^' . SINGLEQUOTE . '])/';
+ if (preg_match($regexp, $inboundValue))
+ {
+ if ($this->debug)
+ ADOConnection::outp("{$this->databaseType} internal transformation: not converted. Found bad use of backslash + single quote");
+
+ break;
+ }
+
+ /*
+ * Remove pairs of single-quotes
+ */
+ $pairs = array();
+ $regexp = '/(' . SINGLEQUOTE . SINGLEQUOTE . ')/';
+ preg_match_all($regexp, $inboundValue, $list_of_pairs);
+
+ if ($list_of_pairs)
+ {
+ foreach (array_unique($list_of_pairs[0]) as $key=>$value)
+ $pairs['<@#@#@PAIR-'.$key.'@#@#@>'] = $value;
+
+
+ if (!empty($pairs))
+ $inboundValue = str_replace($pairs, array_keys($pairs), $inboundValue);
+
+ }
+
+ /*
+ * Remove the rest of literals present in the query
+ */
+ $literals = array();
+ $regexp = '/(N?' . SINGLEQUOTE . '.*?' . SINGLEQUOTE . ')/is';
+ preg_match_all($regexp, $inboundValue, $list_of_literals);
+
+ if ($list_of_literals)
+ {
+ foreach (array_unique($list_of_literals[0]) as $key=>$value)
+ $literals['<#@#@#LITERAL-'.$key.'#@#@#>'] = $value;
+
+
+ if (!empty($literals))
+ $inboundValue = str_replace($literals, array_keys($literals), $inboundValue);
+ }
+
+ /*
+ * Analyse literals to prepend the N char to them if their contents aren't numeric
+ */
+ if (!empty($literals))
+ {
+ foreach ($literals as $key=>$value) {
+ if (!is_numeric(trim($value, SINGLEQUOTE)))
+ /*
+ * Non numeric string, prepend our dear N, whilst
+ * Trimming potentially existing previous "N"
+ */
+ $literals[$key] = 'N' . trim($value, 'N');
+
+ }
+ }
+
+ /*
+ * Re-apply literals to the text
+ */
+ if (!empty($literals))
+ $inboundValue = str_replace(array_keys($literals), $literals, $inboundValue);
+
+
+ /*
+ * Any pairs followed by N' must be switched to N' followed by those pairs
+ * (or strings beginning with single quotes will fail)
+ */
+ $inboundValue = preg_replace("/((<@#@#@PAIR-(\d+)@#@#@>)+)N'/", "N'$1", $inboundValue);
+
+ /*
+ * Re-apply pairs of single-quotes to the text
+ */
+ if (!empty($pairs))
+ $inboundValue = str_replace(array_keys($pairs), $pairs, $inboundValue);
+
+
+ /*
+ * Print transformation if debug = on
+ */
+ if (strcmp($inboundValue,$inboundArray[$inboundKey]) <> 0 && $this->debug)
+ ADOConnection::outp("{$this->databaseType} internal transformation: {$inboundArray[$inboundKey]} to {$inboundValue}");
+
+ if (strcmp($inboundValue,$inboundArray[$inboundKey]) <> 0)
+ /*
+ * Place the transformed value into the outbound array
+ */
+ $outboundArray[$inboundKey] = $inboundValue;
+ }
+
+ /*
+ * Any transformations are in the $outboundArray
+ */
+ if ($inboundIsArray)
+ return $outboundArray;
+
+ /*
+ * We passed a string in originally
+ */
+ return $outboundArray[0];
+
+ }
+
+}
+
+class ADORecordset_mssql_n extends ADORecordset_mssql {
+ var $databaseType = "mssql_n";
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mssqlnative.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mssqlnative.inc.php
new file mode 100644
index 0000000..d5e412f
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mssqlnative.inc.php
@@ -0,0 +1,1200 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Native mssql driver. Requires mssql client. Works on Windows.
+ http://www.microsoft.com/sql/technologies/php/default.mspx
+ To configure for Unix, see
+ http://phpbuilder.com/columns/alberto20000919.php3
+
+ $stream = sqlsrv_get_field($stmt, $index, SQLSRV_SQLTYPE_STREAM(SQLSRV_ENC_BINARY));
+ stream_filter_append($stream, "convert.iconv.ucs-2/utf-8"); // Voila, UTF-8 can be read directly from $stream
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!function_exists('sqlsrv_configure')) {
+ die("mssqlnative extension not installed");
+}
+
+if (!function_exists('sqlsrv_set_error_handling')) {
+ function sqlsrv_set_error_handling($constant) {
+ sqlsrv_configure("WarningsReturnAsErrors", $constant);
+ }
+}
+if (!function_exists('sqlsrv_log_set_severity')) {
+ function sqlsrv_log_set_severity($constant) {
+ sqlsrv_configure("LogSeverity", $constant);
+ }
+}
+if (!function_exists('sqlsrv_log_set_subsystems')) {
+ function sqlsrv_log_set_subsystems($constant) {
+ sqlsrv_configure("LogSubsystems", $constant);
+ }
+}
+
+
+//----------------------------------------------------------------
+// MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002
+// and this causes tons of problems because localized versions of
+// MSSQL will return the dates in dmy or mdy order; and also the
+// month strings depends on what language has been configured. The
+// following two variables allow you to control the localization
+// settings - Ugh.
+//
+// MORE LOCALIZATION INFO
+// ----------------------
+// To configure datetime, look for and modify sqlcommn.loc,
+// typically found in c:\mssql\install
+// Also read :
+// http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918
+// Alternatively use:
+// CONVERT(char(12),datecol,120)
+//
+// Also if your month is showing as month-1,
+// e.g. Jan 13, 2002 is showing as 13/0/2002, then see
+// http://phplens.com/lens/lensforum/msgs.php?id=7048&x=1
+// it's a localisation problem.
+//----------------------------------------------------------------
+
+
+// has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc
+if (ADODB_PHPVER >= 0x4300) {
+// docs say 4.2.0, but testing shows only since 4.3.0 does it work!
+ ini_set('mssql.datetimeconvert',0);
+} else {
+ global $ADODB_mssql_mths; // array, months must be upper-case
+ $ADODB_mssql_date_order = 'mdy';
+ $ADODB_mssql_mths = array(
+ 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,
+ 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
+}
+
+class ADODB_mssqlnative extends ADOConnection {
+ var $databaseType = "mssqlnative";
+ var $dataProvider = "mssqlnative";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d\TH:i:s'";
+ var $hasInsertID = true;
+ var $substr = "substring";
+ var $length = 'len';
+ var $hasAffectedRows = true;
+ var $poorAffectedRows = false;
+ var $metaDatabasesSQL = "select name from sys.sysdatabases where name <> 'master'";
+ var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))";
+ var $metaColumnsSQL =
+ "select c.name,
+ t.name as type,
+ c.length,
+ c.xprec as precision,
+ c.xscale as scale,
+ c.isnullable as nullable,
+ c.cdefault as default_value,
+ c.xtype,
+ t.length as type_length,
+ sc.is_identity
+ from syscolumns c
+ join systypes t on t.xusertype=c.xusertype
+ join sysobjects o on o.id=c.id
+ join sys.tables st on st.name=o.name
+ join sys.columns sc on sc.object_id = st.object_id and sc.name=c.name
+ where o.name='%s'";
+ var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
+ var $hasGenID = true;
+ var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ var $sysTimeStamp = 'GetDate()';
+ var $maxParameterLen = 4000;
+ var $arrayClass = 'ADORecordSet_array_mssqlnative';
+ var $uniqueSort = true;
+ var $leftOuter = '*=';
+ var $rightOuter = '=*';
+ var $ansiOuter = true; // for mssql7 or later
+ var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
+ var $uniqueOrderBy = true;
+ var $_bindInputArray = true;
+ var $_dropSeqSQL = "drop table %s";
+ var $connectionInfo = array();
+ var $cachedSchemaFlush = false;
+ var $sequences = false;
+ var $mssql_version = '';
+
+ function __construct()
+ {
+ if ($this->debug) {
+ ADOConnection::outp("<pre>");
+ sqlsrv_set_error_handling( SQLSRV_ERRORS_LOG_ALL );
+ sqlsrv_log_set_severity( SQLSRV_LOG_SEVERITY_ALL );
+ sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
+ sqlsrv_configure('WarningsReturnAsErrors', 0);
+ } else {
+ sqlsrv_set_error_handling(0);
+ sqlsrv_log_set_severity(0);
+ sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
+ sqlsrv_configure('WarningsReturnAsErrors', 0);
+ }
+ }
+
+ /**
+ * Initializes the SQL Server version.
+ * Dies if connected to a non-supported version (2000 and older)
+ */
+ function ServerVersion() {
+ $data = $this->ServerInfo();
+ preg_match('/^\d{2}/', $data['version'], $matches);
+ $version = (int)reset($matches);
+
+ // We only support SQL Server 2005 and up
+ if($version < 9) {
+ die("SQL SERVER VERSION {$data['version']} NOT SUPPORTED IN mssqlnative DRIVER");
+ }
+
+ $this->mssql_version = $version;
+ }
+
+ function ServerInfo() {
+ global $ADODB_FETCH_MODE;
+ static $arr = false;
+ if (is_array($arr))
+ return $arr;
+ if ($this->fetchMode === false) {
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ } elseif ($this->fetchMode >=0 && $this->fetchMode <=2) {
+ $savem = $this->fetchMode;
+ } else
+ $savem = $this->SetFetchMode(ADODB_FETCH_NUM);
+
+ $arrServerInfo = sqlsrv_server_info($this->_connectionID);
+ $ADODB_FETCH_MODE = $savem;
+ $arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase'];
+ $arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " ISNULL($field, $ifNull) "; // if MS SQL Server
+ }
+
+ function _insertid()
+ {
+ // SCOPE_IDENTITY()
+ // Returns the last IDENTITY value inserted into an IDENTITY column in
+ // the same scope. A scope is a module -- a stored procedure, trigger,
+ // function, or batch. Thus, two statements are in the same scope if
+ // they are in the same stored procedure, function, or batch.
+ return $this->lastInsertID;
+ }
+
+ function _affectedrows()
+ {
+ if ($this->_queryID)
+ return sqlsrv_rows_affected($this->_queryID);
+ }
+
+ function GenID($seq='adodbseq',$start=1) {
+ if (!$this->mssql_version)
+ $this->ServerVersion();
+ switch($this->mssql_version){
+ case 9:
+ case 10:
+ return $this->GenID2008($seq, $start);
+ break;
+ default:
+ return $this->GenID2012($seq, $start);
+ break;
+ }
+ }
+
+ function CreateSequence($seq='adodbseq',$start=1)
+ {
+ if (!$this->mssql_version)
+ $this->ServerVersion();
+
+ switch($this->mssql_version){
+ case 9:
+ case 10:
+ return $this->CreateSequence2008($seq, $start);
+ break;
+ default:
+ return $this->CreateSequence2012($seq, $start);
+ break;
+ }
+
+ }
+
+ /**
+ * For Server 2005,2008, duplicate a sequence with an identity table
+ */
+ function CreateSequence2008($seq='adodbseq',$start=1)
+ {
+ if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)");
+ sqlsrv_begin_transaction($this->_connectionID);
+ $start -= 1;
+ $this->Execute("create table $seq (id int)");//was float(53)
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK");
+ sqlsrv_rollback($this->_connectionID);
+ return false;
+ }
+ sqlsrv_commit($this->_connectionID);
+ return true;
+ }
+
+ /**
+ * Proper Sequences Only available to Server 2012 and up
+ */
+ function CreateSequence2012($seq='adodbseq',$start=1){
+ if (!$this->sequences){
+ $sql = "SELECT name FROM sys.sequences";
+ $this->sequences = $this->GetCol($sql);
+ }
+ $ok = $this->Execute("CREATE SEQUENCE $seq START WITH $start INCREMENT BY 1");
+ if (!$ok)
+ die("CANNOT CREATE SEQUENCE" . print_r(sqlsrv_errors(),true));
+ $this->sequences[] = $seq;
+ }
+
+ /**
+ * For Server 2005,2008, duplicate a sequence with an identity table
+ */
+ function GenID2008($seq='adodbseq',$start=1)
+ {
+ if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)");
+ sqlsrv_begin_transaction($this->_connectionID);
+ $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
+ if (!$ok) {
+ $start -= 1;
+ $this->Execute("create table $seq (id int)");//was float(53)
+ $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
+ if (!$ok) {
+ if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK");
+ sqlsrv_rollback($this->_connectionID);
+ return false;
+ }
+ }
+ $num = $this->GetOne("select id from $seq");
+ sqlsrv_commit($this->_connectionID);
+ return $num;
+ }
+ /**
+ * Only available to Server 2012 and up
+ * Cannot do this the normal adodb way by trapping an error if the
+ * sequence does not exist because sql server will auto create a
+ * sequence with the starting number of -9223372036854775808
+ */
+ function GenID2012($seq='adodbseq',$start=1)
+ {
+
+ /*
+ * First time in create an array of sequence names that we
+ * can use in later requests to see if the sequence exists
+ * the overhead is creating a list of sequences every time
+ * we need access to at least 1. If we really care about
+ * performance, we could maybe flag a 'nocheck' class variable
+ */
+ if (!$this->sequences){
+ $sql = "SELECT name FROM sys.sequences";
+ $this->sequences = $this->GetCol($sql);
+ }
+ if (!is_array($this->sequences)
+ || is_array($this->sequences) && !in_array($seq,$this->sequences)){
+ $this->CreateSequence2012($seq, $start);
+
+ }
+ $num = $this->GetOne("SELECT NEXT VALUE FOR $seq");
+ return $num;
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '+';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "datename(yyyy,$col)";
+ break;
+ case 'M':
+ $s .= "convert(char(3),$col,0)";
+ break;
+ case 'm':
+ $s .= "replace(str(month($col),2),' ','0')";
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "datename(quarter,$col)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "replace(str(day($col),2),' ','0')";
+ break;
+ case 'h':
+ $s .= "substring(convert(char(14),$col,0),13,2)";
+ break;
+
+ case 'H':
+ $s .= "replace(str(datepart(hh,$col),2),' ','0')";
+ break;
+
+ case 'i':
+ $s .= "replace(str(datepart(mi,$col),2),' ','0')";
+ break;
+ case 's':
+ $s .= "replace(str(datepart(ss,$col),2),' ','0')";
+ break;
+ case 'a':
+ case 'A':
+ $s .= "substring(convert(char(19),$col,0),18,2)";
+ break;
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ break;
+ }
+ }
+ return $s;
+ }
+
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ if ($this->debug) ADOConnection::outp('<hr>begin transaction');
+ sqlsrv_begin_transaction($this->_connectionID);
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if ($this->debug) ADOConnection::outp('<hr>commit transaction');
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ sqlsrv_commit($this->_connectionID);
+ return true;
+ }
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->debug) ADOConnection::outp('<hr>rollback transaction');
+ if ($this->transCnt) $this->transCnt -= 1;
+ sqlsrv_rollback($this->_connectionID);
+ return true;
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET TRANSACTION ".$transaction_mode);
+ }
+
+ /*
+ Usage:
+
+ $this->BeginTrans();
+ $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables
+
+ # some operation on both tables table1 and table2
+
+ $this->CommitTrans();
+
+ See http://www.swynk.com/friends/achigrik/SQL70Locks.asp
+ */
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if ($col == '1 as adodbignore') $col = 'top 1 null as ignore';
+ if (!$this->transCnt) $this->BeginTrans();
+ return $this->GetOne("select $col from $tables with (ROWLOCK,HOLDLOCK) where $where");
+ }
+
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ if ($this->_connectionID) {
+ $rs = $this->Execute('USE '.$dbName);
+ if($rs) {
+ return true;
+ } else return false;
+ }
+ else return false;
+ }
+
+ function ErrorMsg()
+ {
+ $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL);
+ if($retErrors != null) {
+ foreach($retErrors as $arrError) {
+ $this->_errorMsg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n";
+ $this->_errorMsg .= "Error Code: ".$arrError[ 'code']."\n";
+ $this->_errorMsg .= "Message: ".$arrError[ 'message']."\n";
+ }
+ }
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ $err = sqlsrv_errors(SQLSRV_ERR_ALL);
+ if($err[0]) return $err[0]['code'];
+ else return 0;
+ }
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('sqlsrv_connect')) return null;
+ $connectionInfo = $this->connectionInfo;
+ $connectionInfo["Database"]=$argDatabasename;
+ $connectionInfo["UID"]=$argUsername;
+ $connectionInfo["PWD"]=$argPassword;
+
+ foreach ($this->connectionParameters as $parameter=>$value)
+ $connectionInfo[$parameter] = $value;
+
+ if ($this->debug) ADOConnection::outp("<hr>connecting... hostname: $argHostname params: ".var_export($connectionInfo,true));
+ //if ($this->debug) ADOConnection::outp("<hr>_connectionID before: ".serialize($this->_connectionID));
+ if(!($this->_connectionID = sqlsrv_connect($argHostname,$connectionInfo))) {
+ if ($this->debug) ADOConnection::outp( "<hr><b>errors</b>: ".print_r( sqlsrv_errors(), true));
+ return false;
+ }
+ //if ($this->debug) ADOConnection::outp(" _connectionID after: ".serialize($this->_connectionID));
+ //if ($this->debug) ADOConnection::outp("<hr>defined functions: <pre>".var_export(get_defined_functions(),true)."</pre>");
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ //return null;//not implemented. NOTE: Persistent connections have no effect if PHP is used as a CGI program. (FastCGI!)
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
+ }
+
+ function Prepare($sql)
+ {
+ return $sql; // prepare does not work properly with bind parameters as bind parameters are managed by sqlsrv_prepare!
+
+ $stmt = sqlsrv_prepare( $this->_connectionID, $sql);
+ if (!$stmt) return $sql;
+ return array($sql,$stmt);
+ }
+
+ // returns concatenated string
+ // MSSQL requires integers to be cast as strings
+ // automatically cast every datatype to VARCHAR(255)
+ // @author David Rogers (introspectshun)
+ function Concat()
+ {
+ $s = "";
+ $arr = func_get_args();
+
+ // Split single record on commas, if possible
+ if (sizeof($arr) == 1) {
+ foreach ($arr as $arg) {
+ $args = explode(',', $arg);
+ }
+ $arr = $args;
+ }
+
+ array_walk(
+ $arr,
+ function(&$value, $key) {
+ $value = "CAST(" . $value . " AS VARCHAR(255))";
+ }
+ );
+ $s = implode('+',$arr);
+ if (sizeof($arr) > 0) return "$s";
+
+ return '';
+ }
+
+ /*
+ Unfortunately, it appears that mssql cannot handle varbinary > 255 chars
+ So all your blobs must be of type "image".
+
+ Remember to set in php.ini the following...
+
+ ; Valid range 0 - 2147483647. Default = 4096.
+ mssql.textlimit = 0 ; zero to pass through
+
+ ; Valid range 0 - 2147483647. Default = 4096.
+ mssql.textsize = 0 ; zero to pass through
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+
+ if (strtoupper($blobtype) == 'CLOB') {
+ $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where";
+ return $this->Execute($sql) != false;
+ }
+ $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where";
+ return $this->Execute($sql) != false;
+ }
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ $this->_errorMsg = false;
+
+ if (is_array($sql)) $sql = $sql[1];
+
+ $insert = false;
+ // handle native driver flaw for retrieving the last insert ID
+ if(preg_match('/^\W*insert[\s\w()[\]",.]+values\s*\((?:[^;\']|\'\'|(?:(?:\'\')*\'[^\']+\'(?:\'\')*))*;?$/i', $sql)) {
+ $insert = true;
+ $sql .= '; '.$this->identitySQL; // select scope_identity()
+ }
+ if($inputarr) {
+ $rez = sqlsrv_query($this->_connectionID, $sql, $inputarr);
+ } else {
+ $rez = sqlsrv_query($this->_connectionID,$sql);
+ }
+
+ if ($this->debug) ADOConnection::outp("<hr>running query: ".var_export($sql,true)."<hr>input array: ".var_export($inputarr,true)."<hr>result: ".var_export($rez,true));
+
+ if(!$rez) {
+ $rez = false;
+ } else if ($insert) {
+ // retrieve the last insert ID (where applicable)
+ while ( sqlsrv_next_result($rez) ) {
+ sqlsrv_fetch($rez);
+ $this->lastInsertID = sqlsrv_get_field($rez, 0);
+ }
+ }
+ return $rez;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if ($this->transCnt) $this->RollbackTrans();
+ $rez = @sqlsrv_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $rez;
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_mssqlnative::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
+ }
+
+ function MetaIndexes($table,$primary=false, $owner = false)
+ {
+ $table = $this->qstr($table);
+
+ $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
+ CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK,
+ CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
+ FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
+ INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
+ INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
+ WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table
+ ORDER BY O.name, I.Name, K.keyno";
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute($sql);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return FALSE;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ if (!$primary && $row[5]) continue;
+
+ $indexes[$row[0]]['unique'] = $row[6];
+ $indexes[$row[0]]['columns'][] = $row[1];
+ }
+ return $indexes;
+ }
+
+ function MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $table = $this->qstr(strtoupper($table));
+
+ $sql =
+ "select object_name(constid) as constraint_name,
+ col_name(fkeyid, fkey) as column_name,
+ object_name(rkeyid) as referenced_table_name,
+ col_name(rkeyid, rkey) as referenced_column_name
+ from sysforeignkeys
+ where upper(object_name(fkeyid)) = $table
+ order by constraint_name, referenced_table_name, keyno";
+
+ $constraints =& $this->GetArray($sql);
+
+ $ADODB_FETCH_MODE = $save;
+
+ $arr = false;
+ foreach($constraints as $constr) {
+ //print_r($constr);
+ $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3];
+ }
+ if (!$arr) return false;
+
+ $arr2 = false;
+
+ foreach($arr as $k => $v) {
+ foreach($v as $a => $b) {
+ if ($upper) $a = strtoupper($a);
+ $arr2[$a] = $b;
+ }
+ }
+ return $arr2;
+ }
+
+ //From: Fernando Moreira <FMoreira@imediata.pt>
+ function MetaDatabases()
+ {
+ $this->SelectDB("master");
+ $rs =& $this->Execute($this->metaDatabasesSQL);
+ $rows = $rs->GetRows();
+ $ret = array();
+ for($i=0;$i<count($rows);$i++) {
+ $ret[] = $rows[$i][0];
+ }
+ $this->SelectDB($this->database);
+ if($ret)
+ return $ret;
+ else
+ return false;
+ }
+
+ // "Stein-Aksel Basma" <basma@accelero.no>
+ // tested with MSSQL 2000
+ function MetaPrimaryKeys($table, $owner=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = '';
+ $this->_findschema($table,$schema);
+ if (!$schema) $schema = $this->database;
+ if ($schema) $schema = "and k.table_catalog like '$schema%'";
+
+ $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k,
+ information_schema.table_constraints tc
+ where tc.constraint_name = k.constraint_name and tc.constraint_type =
+ 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position ";
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $a = $this->GetCol($sql);
+ $ADODB_FETCH_MODE = $savem;
+
+ if ($a && sizeof($a)>0) return $a;
+ $false = false;
+ return $false;
+ }
+
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(($mask));
+ $this->metaTablesSQL .= " AND name like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+ function MetaColumns($table, $upper=true, $schema=false){
+
+ # start adg
+ static $cached_columns = array();
+ if ($this->cachedSchemaFlush)
+ $cached_columns = array();
+
+ if (array_key_exists($table,$cached_columns)){
+ return $cached_columns[$table];
+ }
+ # end adg
+
+ if (!$this->mssql_version)
+ $this->ServerVersion();
+
+ $this->_findschema($table,$schema);
+ if ($schema) {
+ $dbName = $this->database;
+ $this->SelectDB($schema);
+ }
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+
+ if ($schema) {
+ $this->SelectDB($dbName);
+ }
+
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF){
+
+ $fld = new ADOFieldObject();
+ if (array_key_exists(0,$rs->fields)) {
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+ $fld->precision = $rs->fields[3];
+ $fld->scale = $rs->fields[4];
+ $fld->not_null =!$rs->fields[5];
+ $fld->has_default = $rs->fields[6];
+ $fld->xtype = $rs->fields[7];
+ $fld->type_length = $rs->fields[8];
+ $fld->auto_increment= $rs->fields[9];
+ } else {
+ $fld->name = $rs->fields['name'];
+ $fld->type = $rs->fields['type'];
+ $fld->max_length = $rs->fields['length'];
+ $fld->precision = $rs->fields['precision'];
+ $fld->scale = $rs->fields['scale'];
+ $fld->not_null =!$rs->fields['nullable'];
+ $fld->has_default = $rs->fields['default_value'];
+ $fld->xtype = $rs->fields['xtype'];
+ $fld->type_length = $rs->fields['type_length'];
+ $fld->auto_increment= $rs->fields['is_identity'];
+ }
+
+ if ($save == ADODB_FETCH_NUM)
+ $retarr[] = $fld;
+ else
+ $retarr[strtoupper($fld->name)] = $fld;
+
+ $rs->MoveNext();
+
+ }
+ $rs->Close();
+ # start adg
+ $cached_columns[$table] = $retarr;
+ # end adg
+ return $retarr;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_mssqlnative extends ADORecordSet {
+
+ var $databaseType = "mssqlnative";
+ var $canSeek = false;
+ var $fieldOffset = 0;
+ // _mths works only in non-localised system
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+
+ }
+ $this->fetchMode = $mode;
+ return parent::__construct($id,$mode);
+ }
+
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ # KMN # if ($this->connection->debug) ADOConnection::outp("(before) ADODB_COUNTRECS: {$ADODB_COUNTRECS} _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
+ /*$retRowsAff = sqlsrv_rows_affected($this->_queryID);//"If you need to determine the number of rows a query will return before retrieving the actual results, appending a SELECT COUNT ... query would let you get that information, and then a call to next_result would move you to the "real" results."
+ ADOConnection::outp("rowsaff: ".serialize($retRowsAff));
+ $this->_numOfRows = ($ADODB_COUNTRECS)? $retRowsAff:-1;*/
+ $this->_numOfRows = -1;//not supported
+ $fieldmeta = sqlsrv_field_metadata($this->_queryID);
+ $this->_numOfFields = ($fieldmeta)? count($fieldmeta):-1;
+ # KMN # if ($this->connection->debug) ADOConnection::outp("(after) _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
+ /*
+ * Copy the oracle method and cache the metadata at init time
+ */
+ if ($this->_numOfFields>0) {
+ $this->_fieldobjs = array();
+ $max = $this->_numOfFields;
+ for ($i=0;$i<$max; $i++) $this->_fieldobjs[] = $this->_FetchField($i);
+ }
+
+ }
+
+
+ //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se>
+ // get next resultset - requires PHP 4.0.5 or later
+ function NextRecordSet()
+ {
+ if (!sqlsrv_next_result($this->_queryID)) return false;
+ $this->_inited = false;
+ $this->bind = false;
+ $this->_currentRow = -1;
+ $this->Init();
+ return true;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved.
+ Designed By jcortinap#jc.com.mx
+ */
+ function _FetchField($fieldOffset = -1)
+ {
+ $_typeConversion = array(
+ -155 => 'datetimeoffset',
+ -154 => 'char',
+ -152 => 'xml',
+ -151 => 'udt',
+ -11 => 'uniqueidentifier',
+ -10 => 'ntext',
+ -9 => 'nvarchar',
+ -8 => 'nchar',
+ -7 => 'bit',
+ -6 => 'tinyint',
+ -5 => 'bigint',
+ -4 => 'image',
+ -3 => 'varbinary',
+ -2 => 'timestamp',
+ -1 => 'text',
+ 1 => 'char',
+ 2 => 'numeric',
+ 3 => 'decimal',
+ 4 => 'int',
+ 5 => 'smallint',
+ 6 => 'float',
+ 7 => 'real',
+ 12 => 'varchar',
+ 91 => 'date',
+ 93 => 'datetime'
+ );
+
+ $fa = @sqlsrv_field_metadata($this->_queryID);
+ if ($fieldOffset != -1) {
+ $fa = $fa[$fieldOffset];
+ }
+ $false = false;
+ if (empty($fa)) {
+ $f = false;//PHP Notice: Only variable references should be returned by reference
+ }
+ else
+ {
+ // Convert to an object
+ $fa = array_change_key_case($fa, CASE_LOWER);
+ $fb = array();
+ if ($fieldOffset != -1)
+ {
+ $fb = array(
+ 'name' => $fa['name'],
+ 'max_length' => $fa['size'],
+ 'column_source' => $fa['name'],
+ 'type' => $_typeConversion[$fa['type']]
+ );
+ }
+ else
+ {
+ foreach ($fa as $key => $value)
+ {
+ $fb[] = array(
+ 'name' => $value['name'],
+ 'max_length' => $value['size'],
+ 'column_source' => $value['name'],
+ 'type' => $_typeConversion[$value['type']]
+ );
+ }
+ }
+ $f = (object) $fb;
+ }
+ return $f;
+ }
+
+ /*
+ * Fetchfield copies the oracle method, it loads the field information
+ * into the _fieldobjs array once, to save multiple calls to the
+ * sqlsrv_field_metadata function
+ *
+ * @author KM Newnham
+ * @date 02/20/2013
+ */
+ function FetchField($fieldOffset = -1)
+ {
+ return $this->_fieldobjs[$fieldOffset];
+ }
+
+ function _seek($row)
+ {
+ return false;//There is no support for cursors in the driver at this time. All data is returned via forward-only streams.
+ }
+
+ // speedup
+ function MoveNext()
+ {
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("movenext()");
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("eof (beginning): ".$this->EOF);
+ if ($this->EOF) return false;
+
+ $this->_currentRow++;
+ // # KMN # if ($this->connection->debug) ADOConnection::outp("_currentRow: ".$this->_currentRow);
+
+ if ($this->_fetch()) return true;
+ $this->EOF = true;
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("eof (end): ".$this->EOF);
+
+ return false;
+ }
+
+
+ // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4
+ // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot!
+ function _fetch($ignore_fields=false)
+ {
+ # KMN # if ($this->connection->debug) ADOConnection::outp("_fetch()");
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ if ($this->fetchMode & ADODB_FETCH_NUM) {
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: both");
+ $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_BOTH);
+ } else {
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: assoc");
+ $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_ASSOC);
+ }
+
+ if (is_array($this->fields)) {
+ if (ADODB_ASSOC_CASE == 0) {
+ foreach($this->fields as $k=>$v) {
+ $this->fields[strtolower($k)] = $v;
+ }
+ } else if (ADODB_ASSOC_CASE == 1) {
+ foreach($this->fields as $k=>$v) {
+ $this->fields[strtoupper($k)] = $v;
+ }
+ }
+ }
+ } else {
+ //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: num");
+ $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_NUMERIC);
+ }
+ if(is_array($this->fields) && array_key_exists(1,$this->fields) && !array_key_exists(0,$this->fields)) {//fix fetch numeric keys since they're not 0 based
+ $arrFixed = array();
+ foreach($this->fields as $key=>$value) {
+ if(is_numeric($key)) {
+ $arrFixed[$key-1] = $value;
+ } else {
+ $arrFixed[$key] = $value;
+ }
+ }
+ //if($this->connection->debug) ADOConnection::outp("<hr>fixing non 0 based return array, old: ".print_r($this->fields,true)." new: ".print_r($arrFixed,true));
+ $this->fields = $arrFixed;
+ }
+ if(is_array($this->fields)) {
+ foreach($this->fields as $key=>$value) {
+ if (is_object($value) && method_exists($value, 'format')) {//is DateTime object
+ $this->fields[$key] = $value->format("Y-m-d\TH:i:s\Z");
+ }
+ }
+ }
+ if($this->fields === null) $this->fields = false;
+ # KMN # if ($this->connection->debug) ADOConnection::outp("<hr>after _fetch, fields: <pre>".print_r($this->fields,true)." backtrace: ".adodb_backtrace(false));
+ return $this->fields;
+ }
+
+ /* close() only needs to be called if you are worried about using too much memory while your script
+ is running. All associated result memory for the specified result identifier will automatically be freed. */
+ function _close()
+ {
+ if(is_object($this->_queryID)) {
+ $rez = sqlsrv_free_stmt($this->_queryID);
+ $this->_queryID = false;
+ return $rez;
+ }
+ return true;
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_mssqlnative::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
+ }
+}
+
+
+class ADORecordSet_array_mssqlnative extends ADORecordSet_array {
+ function __construct($id=-1,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+ // mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+
+ if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
+
+ global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+
+ //Dec 30 2000 12:00AM
+ if ($ADODB_mssql_date_order == 'dmy') {
+ if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+ return parent::UnixDate($v);
+ }
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[1];
+ $themth = substr(strtoupper($rr[2]),0,3);
+ } else {
+ if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+ return parent::UnixDate($v);
+ }
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[2];
+ $themth = substr(strtoupper($rr[1]),0,3);
+ }
+ $themth = $ADODB_mssql_mths[$themth];
+ if ($themth <= 0) return false;
+ // h-m-s-MM-DD-YY
+ return adodb_mktime(0,0,0,$themth,$theday,$rr[3]);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+
+ if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
+
+ global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+
+ //Dec 30 2000 12:00AM
+ if ($ADODB_mssql_date_order == 'dmy') {
+ if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+ ,$v, $rr)) return parent::UnixTimeStamp($v);
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[1];
+ $themth = substr(strtoupper($rr[2]),0,3);
+ } else {
+ if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+ ,$v, $rr)) return parent::UnixTimeStamp($v);
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $theday = $rr[2];
+ $themth = substr(strtoupper($rr[1]),0,3);
+ }
+
+ $themth = $ADODB_mssql_mths[$themth];
+ if ($themth <= 0) return false;
+
+ switch (strtoupper($rr[6])) {
+ case 'P':
+ if ($rr[4]<12) $rr[4] += 12;
+ break;
+ case 'A':
+ if ($rr[4]==12) $rr[4] = 0;
+ break;
+ default:
+ break;
+ }
+ // h-m-s-MM-DD-YY
+ return adodb_mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]);
+ }
+}
+
+/*
+Code Example 1:
+
+select object_name(constid) as constraint_name,
+ object_name(fkeyid) as table_name,
+ col_name(fkeyid, fkey) as column_name,
+ object_name(rkeyid) as referenced_table_name,
+ col_name(rkeyid, rkey) as referenced_column_name
+from sysforeignkeys
+where object_name(fkeyid) = x
+order by constraint_name, table_name, referenced_table_name, keyno
+
+Code Example 2:
+select constraint_name,
+ column_name,
+ ordinal_position
+from information_schema.key_column_usage
+where constraint_catalog = db_name()
+and table_name = x
+order by constraint_name, ordinal_position
+
+http://www.databasejournal.com/scripts/article.php/1440551
+*/
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mssqlpo.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mssqlpo.inc.php
new file mode 100644
index 0000000..6168849
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mssqlpo.inc.php
@@ -0,0 +1,58 @@
+<?php
+/**
+* @version v5.20.14 06-Jan-2019
+* @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+* @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+* Released under both BSD license and Lesser GPL library license.
+* Whenever there is any discrepancy between the two licenses,
+* the BSD license will take precedence.
+*
+* Set tabs to 4 for best viewing.
+*
+* Latest version is available at http://adodb.org/
+*
+* Portable MSSQL Driver that supports || instead of +
+*
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+
+/*
+ The big difference between mssqlpo and it's parent mssql is that mssqlpo supports
+ the more standard || string concatenation operator.
+*/
+
+include_once(ADODB_DIR.'/drivers/adodb-mssql.inc.php');
+
+class ADODB_mssqlpo extends ADODB_mssql {
+ var $databaseType = "mssqlpo";
+ var $concat_operator = '||';
+
+ function PrepareSP($sql, $param = true)
+ {
+ if (!$this->_has_mssql_init) {
+ ADOConnection::outp( "PrepareSP: mssql_init only available since PHP 4.1.0");
+ return $sql;
+ }
+ if (is_string($sql)) $sql = str_replace('||','+',$sql);
+ $stmt = mssql_init($sql,$this->_connectionID);
+ if (!$stmt) return $sql;
+ return array($sql,$stmt);
+ }
+
+ function _query($sql,$inputarr=false)
+ {
+ if (is_string($sql)) $sql = str_replace('||','+',$sql);
+ return ADODB_mssql::_query($sql,$inputarr);
+ }
+}
+
+class ADORecordset_mssqlpo extends ADORecordset_mssql {
+ var $databaseType = "mssqlpo";
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mysql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mysql.inc.php
new file mode 100644
index 0000000..f839d85
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mysql.inc.php
@@ -0,0 +1,893 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ This driver only supports the original non-transactional MySQL driver. It
+ is deprected in PHP version 5.5 and removed in PHP version 7. It is deprecated
+ as of ADOdb version 5.20.0. Use the mysqli driver instead, which supports both
+ transactional and non-transactional updates
+
+ Requires mysql client. Works on Windows and Unix.
+
+ 28 Feb 2001: MetaColumns bug fix - suggested by Freek Dijkstra (phpeverywhere@macfreek.com)
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_MYSQL_LAYER")) {
+ define("_ADODB_MYSQL_LAYER", 1 );
+
+class ADODB_mysql extends ADOConnection {
+ var $databaseType = 'mysql';
+ var $dataProvider = 'mysql';
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $metaTablesSQL = "SELECT
+ TABLE_NAME,
+ CASE WHEN TABLE_TYPE = 'VIEW' THEN 'V' ELSE 'T' END
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA=";
+ var $metaColumnsSQL = "SHOW COLUMNS FROM `%s`";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $hasLimit = true;
+ var $hasMoveFirst = true;
+ var $hasGenID = true;
+ var $isoDates = true; // accepts dates in ISO format
+ var $sysDate = 'CURDATE()';
+ var $sysTimeStamp = 'NOW()';
+ var $hasTransactions = false;
+ var $forceNewConnect = false;
+ var $poorAffectedRows = true;
+ var $clientFlags = 0;
+ var $charSet = '';
+ var $substr = "substring";
+ var $nameQuote = '`'; /// string to use to quote identifiers and names
+ var $compat323 = false; // true if compat with mysql 3.23
+
+ function __construct()
+ {
+ if (defined('ADODB_EXTENSION')) $this->rsPrefix .= 'ext_';
+ }
+
+
+ // SetCharSet - switch the client encoding
+ function SetCharSet($charset_name)
+ {
+ if (!function_exists('mysql_set_charset')) {
+ return false;
+ }
+
+ if ($this->charSet !== $charset_name) {
+ $ok = @mysql_set_charset($charset_name,$this->_connectionID);
+ if ($ok) {
+ $this->charSet = $charset_name;
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ function ServerInfo()
+ {
+ $arr['description'] = ADOConnection::GetOne("select version()");
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " IFNULL($field, $ifNull) "; // if MySQL
+ }
+
+ function MetaProcedures($NamePattern = false, $catalog = null, $schemaPattern = null)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $procedures = array ();
+
+ // get index details
+
+ $likepattern = '';
+ if ($NamePattern) {
+ $likepattern = " LIKE '".$NamePattern."'";
+ }
+ $rs = $this->Execute('SHOW PROCEDURE STATUS'.$likepattern);
+ if (is_object($rs)) {
+
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ $procedures[$row[1]] = array(
+ 'type' => 'PROCEDURE',
+ 'catalog' => '',
+ 'schema' => '',
+ 'remarks' => $row[7],
+ );
+ }
+ }
+
+ $rs = $this->Execute('SHOW FUNCTION STATUS'.$likepattern);
+ if (is_object($rs)) {
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ $procedures[$row[1]] = array(
+ 'type' => 'FUNCTION',
+ 'catalog' => '',
+ 'schema' => '',
+ 'remarks' => $row[7]
+ );
+ }
+ }
+
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $procedures;
+ }
+
+ /**
+ * Retrieves a list of tables based on given criteria
+ *
+ * @param string $ttype Table type = 'TABLE', 'VIEW' or false=both (default)
+ * @param string $showSchema schema name, false = current schema (default)
+ * @param string $mask filters the table by name
+ *
+ * @return array list of tables
+ */
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $save = $this->metaTablesSQL;
+ if ($showSchema && is_string($showSchema)) {
+ $this->metaTablesSQL .= $this->qstr($showSchema);
+ } else {
+ $this->metaTablesSQL .= "schema()";
+ }
+
+ if ($mask) {
+ $mask = $this->qstr($mask);
+ $this->metaTablesSQL .= " AND table_name LIKE $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ $this->metaTablesSQL = $save;
+ return $ret;
+ }
+
+
+ function MetaIndexes ($table, $primary = FALSE, $owner=false)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ // get index details
+ $rs = $this->Execute(sprintf('SHOW INDEX FROM %s',$table));
+
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return $false;
+ }
+
+ $indexes = array ();
+
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ if ($primary == FALSE AND $row[2] == 'PRIMARY') {
+ continue;
+ }
+
+ if (!isset($indexes[$row[2]])) {
+ $indexes[$row[2]] = array(
+ 'unique' => ($row[1] == 0),
+ 'columns' => array()
+ );
+ }
+
+ $indexes[$row[2]]['columns'][$row[3] - 1] = $row[4];
+ }
+
+ // sort columns by order in the index
+ foreach ( array_keys ($indexes) as $index )
+ {
+ ksort ($indexes[$index]['columns']);
+ }
+
+ return $indexes;
+ }
+
+
+ // if magic quotes disabled, use mysql_real_escape_string()
+ function qstr($s,$magic_quotes=false)
+ {
+ if (is_null($s)) return 'NULL';
+ if (!$magic_quotes) {
+
+ if (ADODB_PHPVER >= 0x4300) {
+ if (is_resource($this->_connectionID))
+ return "'".mysql_real_escape_string($s,$this->_connectionID)."'";
+ }
+ if ($this->replaceQuote[0] == '\\'){
+ $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
+ }
+ return "'".str_replace("'",$this->replaceQuote,$s)."'";
+ }
+
+ // undo magic quotes for "
+ $s = str_replace('\\"','"',$s);
+ return "'$s'";
+ }
+
+ function _insertid()
+ {
+ return ADOConnection::GetOne('SELECT LAST_INSERT_ID()');
+ //return mysql_insert_id($this->_connectionID);
+ }
+
+ function GetOne($sql,$inputarr=false)
+ {
+ global $ADODB_GETONE_EOF;
+ if ($this->compat323 == false && strncasecmp($sql,'sele',4) == 0) {
+ $rs = $this->SelectLimit($sql,1,-1,$inputarr);
+ if ($rs) {
+ $rs->Close();
+ if ($rs->EOF) return $ADODB_GETONE_EOF;
+ return reset($rs->fields);
+ }
+ } else {
+ return ADOConnection::GetOne($sql,$inputarr);
+ }
+ return false;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->debug) ADOConnection::outp("Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driver");
+ }
+
+ function _affectedrows()
+ {
+ return mysql_affected_rows($this->_connectionID);
+ }
+
+ // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html
+ // Reference on Last_Insert_ID on the recommended way to simulate sequences
+ var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
+ var $_genSeqSQL = "create table if not exists %s (id int not null)";
+ var $_genSeqCountSQL = "select count(*) from %s";
+ var $_genSeq2SQL = "insert into %s values (%s)";
+ var $_dropSeqSQL = "drop table if exists %s";
+
+ function CreateSequence($seqname='adodbseq',$startID=1)
+ {
+ if (empty($this->_genSeqSQL)) return false;
+ $u = strtoupper($seqname);
+
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) return false;
+ return $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
+ }
+
+
+ function GenID($seqname='adodbseq',$startID=1)
+ {
+ // post-nuke sets hasGenID to false
+ if (!$this->hasGenID) return false;
+
+ $savelog = $this->_logsql;
+ $this->_logsql = false;
+ $getnext = sprintf($this->_genIDSQL,$seqname);
+ $holdtransOK = $this->_transOK; // save the current status
+ $rs = @$this->Execute($getnext);
+ if (!$rs) {
+ if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset
+ $u = strtoupper($seqname);
+ $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ $cnt = $this->GetOne(sprintf($this->_genSeqCountSQL,$seqname));
+ if (!$cnt) $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
+ $rs = $this->Execute($getnext);
+ }
+
+ if ($rs) {
+ $this->genID = mysql_insert_id($this->_connectionID);
+ $rs->Close();
+ } else
+ $this->genID = 0;
+
+ $this->_logsql = $savelog;
+ return $this->genID;
+ }
+
+ function MetaDatabases()
+ {
+ $qid = mysql_list_dbs($this->_connectionID);
+ $arr = array();
+ $i = 0;
+ $max = mysql_num_rows($qid);
+ while ($i < $max) {
+ $db = mysql_tablename($qid,$i);
+ if ($db != 'mysql') $arr[] = $db;
+ $i += 1;
+ }
+ return $arr;
+ }
+
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = 'DATE_FORMAT('.$col.",'";
+ $concat = false;
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ $ch = $fmt[$i];
+ switch($ch) {
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ /** FALL THROUGH */
+ case '-':
+ case '/':
+ $s .= $ch;
+ break;
+
+ case 'Y':
+ case 'y':
+ $s .= '%Y';
+ break;
+ case 'M':
+ $s .= '%b';
+ break;
+
+ case 'm':
+ $s .= '%m';
+ break;
+ case 'D':
+ case 'd':
+ $s .= '%d';
+ break;
+
+ case 'Q':
+ case 'q':
+ $s .= "'),Quarter($col)";
+
+ if ($len > $i+1) $s .= ",DATE_FORMAT($col,'";
+ else $s .= ",('";
+ $concat = true;
+ break;
+
+ case 'H':
+ $s .= '%H';
+ break;
+
+ case 'h':
+ $s .= '%I';
+ break;
+
+ case 'i':
+ $s .= '%i';
+ break;
+
+ case 's':
+ $s .= '%s';
+ break;
+
+ case 'a':
+ case 'A':
+ $s .= '%p';
+ break;
+
+ case 'w':
+ $s .= '%w';
+ break;
+
+ case 'W':
+ $s .= '%U';
+ break;
+
+ case 'l':
+ $s .= '%W';
+ break;
+ }
+ }
+ $s.="')";
+ if ($concat) $s = "CONCAT($s)";
+ return $s;
+ }
+
+
+ // returns concatenated string
+ // much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator
+ function Concat()
+ {
+ $s = "";
+ $arr = func_get_args();
+
+ // suggestion by andrew005@mnogo.ru
+ $s = implode(',',$arr);
+ if (strlen($s) > 0) return "CONCAT($s)";
+ else return '';
+ }
+
+ function OffsetDate($dayFraction,$date=false)
+ {
+ if (!$date) $date = $this->sysDate;
+
+ $fraction = $dayFraction * 24 * 3600;
+ return '('. $date . ' + INTERVAL ' . $fraction.' SECOND)';
+
+// return "from_unixtime(unix_timestamp($date)+$fraction)";
+ }
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!empty($this->port)) $argHostname .= ":".$this->port;
+
+ if (ADODB_PHPVER >= 0x4300)
+ $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword,
+ $this->forceNewConnect,$this->clientFlags);
+ else if (ADODB_PHPVER >= 0x4200)
+ $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword,
+ $this->forceNewConnect);
+ else
+ $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword);
+
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!empty($this->port)) $argHostname .= ":".$this->port;
+
+ if (ADODB_PHPVER >= 0x4300)
+ $this->_connectionID = mysql_pconnect($argHostname,$argUsername,$argPassword,$this->clientFlags);
+ else
+ $this->_connectionID = mysql_pconnect($argHostname,$argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ if ($this->autoRollback) $this->RollbackTrans();
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ $this->forceNewConnect = true;
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $this->_findschema($table,$schema);
+ if ($schema) {
+ $dbName = $this->database;
+ $this->SelectDB($schema);
+ }
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+
+ if ($schema) {
+ $this->SelectDB($dbName);
+ }
+
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF){
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $type = $rs->fields[1];
+
+ // split type into type(length):
+ $fld->scale = null;
+ if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1;
+ } elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ } elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $arr = explode(",",$query_array[2]);
+ $fld->enums = $arr;
+ $zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6
+ $fld->max_length = ($zlen > 0) ? $zlen : 1;
+ } else {
+ $fld->type = $type;
+ $fld->max_length = -1;
+ }
+ $fld->not_null = ($rs->fields[2] != 'YES');
+ $fld->primary_key = ($rs->fields[3] == 'PRI');
+ $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
+ $fld->binary = (strpos($type,'blob') !== false || strpos($type,'binary') !== false);
+ $fld->unsigned = (strpos($type,'unsigned') !== false);
+ $fld->zerofill = (strpos($type,'zerofill') !== false);
+
+ if (!$fld->binary) {
+ $d = $rs->fields[4];
+ if ($d != '' && $d != 'NULL') {
+ $fld->has_default = true;
+ $fld->default_value = $d;
+ } else {
+ $fld->has_default = false;
+ }
+ }
+
+ if ($save == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $retarr;
+ }
+
+ // returns true or false
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ if ($this->_connectionID) {
+ return @mysql_select_db($dbName,$this->_connectionID);
+ }
+ else return false;
+ }
+
+ // parameters use PostgreSQL convention, not MySQL
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr =($offset>=0) ? ((integer)$offset)."," : '';
+ // jason judge, see http://phplens.com/lens/lensforum/msgs.php?id=9220
+ if ($nrows < 0) $nrows = '18446744073709551615';
+
+ if ($secs)
+ $rs = $this->CacheExecute($secs,$sql." LIMIT $offsetStr".((integer)$nrows),$inputarr);
+ else
+ $rs = $this->Execute($sql." LIMIT $offsetStr".((integer)$nrows),$inputarr);
+ return $rs;
+ }
+
+ // returns queryID or false
+ function _query($sql,$inputarr=false)
+ {
+
+ return mysql_query($sql,$this->_connectionID);
+ /*
+ global $ADODB_COUNTRECS;
+ if($ADODB_COUNTRECS)
+ return mysql_query($sql,$this->_connectionID);
+ else
+ return @mysql_unbuffered_query($sql,$this->_connectionID); // requires PHP >= 4.0.6
+ */
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+
+ if ($this->_logsql) return $this->_errorMsg;
+ if (empty($this->_connectionID)) $this->_errorMsg = @mysql_error();
+ else $this->_errorMsg = @mysql_error($this->_connectionID);
+ return $this->_errorMsg;
+ }
+
+ /* Returns: the last error number from previous database operation */
+ function ErrorNo()
+ {
+ if ($this->_logsql) return $this->_errorCode;
+ if (empty($this->_connectionID)) return @mysql_errno();
+ else return @mysql_errno($this->_connectionID);
+ }
+
+ // returns true or false
+ function _close()
+ {
+ @mysql_close($this->_connectionID);
+
+ $this->charSet = '';
+ $this->_connectionID = false;
+ }
+
+
+ /*
+ * Maximum size of C field
+ */
+ function CharMax()
+ {
+ return 255;
+ }
+
+ /*
+ * Maximum size of X field
+ */
+ function TextMax()
+ {
+ return 4294967295;
+ }
+
+ // "Innox - Juan Carlos Gonzalez" <jgonzalez#innox.com.mx>
+ function MetaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE )
+ {
+ global $ADODB_FETCH_MODE;
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC || $this->fetchMode == ADODB_FETCH_ASSOC) $associative = true;
+
+ if ( !empty($owner) ) {
+ $table = "$owner.$table";
+ }
+ $a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table));
+ if ($associative) {
+ $create_sql = isset($a_create_table["Create Table"]) ? $a_create_table["Create Table"] : $a_create_table["Create View"];
+ } else {
+ $create_sql = $a_create_table[1];
+ }
+
+ $matches = array();
+
+ if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false;
+ $foreign_keys = array();
+ $num_keys = count($matches[0]);
+ for ( $i = 0; $i < $num_keys; $i ++ ) {
+ $my_field = explode('`, `', $matches[1][$i]);
+ $ref_table = $matches[2][$i];
+ $ref_field = explode('`, `', $matches[3][$i]);
+
+ if ( $upper ) {
+ $ref_table = strtoupper($ref_table);
+ }
+
+ // see https://sourceforge.net/p/adodb/bugs/100/
+ if (!isset($foreign_keys[$ref_table])) {
+ $foreign_keys[$ref_table] = array();
+ }
+ $num_fields = count($my_field);
+ for ( $j = 0; $j < $num_fields; $j ++ ) {
+ if ( $associative ) {
+ $foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j];
+ } else {
+ $foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}";
+ }
+ }
+ }
+
+ return $foreign_keys;
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+
+class ADORecordSet_mysql extends ADORecordSet{
+
+ var $databaseType = "mysql";
+ var $canSeek = true;
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch ($mode)
+ {
+ case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
+ case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default:
+ $this->fetchMode = MYSQL_BOTH; break;
+ }
+ $this->adodbFetchMode = $mode;
+ parent::__construct($queryID);
+ }
+
+ function _initrs()
+ {
+ //GLOBAL $ADODB_COUNTRECS;
+ // $this->_numOfRows = ($ADODB_COUNTRECS) ? @mysql_num_rows($this->_queryID):-1;
+ $this->_numOfRows = @mysql_num_rows($this->_queryID);
+ $this->_numOfFields = @mysql_num_fields($this->_queryID);
+ }
+
+ function FetchField($fieldOffset = -1)
+ {
+ if ($fieldOffset != -1) {
+ $o = @mysql_fetch_field($this->_queryID, $fieldOffset);
+ $f = @mysql_field_flags($this->_queryID,$fieldOffset);
+ if ($o) $o->max_length = @mysql_field_len($this->_queryID,$fieldOffset); // suggested by: Jim Nicholson (jnich#att.com)
+ //$o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable
+ if ($o) $o->binary = (strpos($f,'binary')!== false);
+ }
+ else { /* The $fieldOffset argument is not provided thus its -1 */
+ $o = @mysql_fetch_field($this->_queryID);
+ //if ($o) $o->max_length = @mysql_field_len($this->_queryID); // suggested by: Jim Nicholson (jnich#att.com)
+ $o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable
+ }
+
+ return $o;
+ }
+
+ function GetRowAssoc($upper = ADODB_ASSOC_CASE)
+ {
+ if ($this->fetchMode == MYSQL_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
+ $row = $this->fields;
+ }
+ else {
+ $row = ADORecordSet::GetRowAssoc($upper);
+ }
+ return $row;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ // added @ by "Michael William Miller" <mille562@pilot.msu.edu>
+ if ($this->fetchMode != MYSQL_NUM) return @$this->fields[$colname];
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _seek($row)
+ {
+ if ($this->_numOfRows == 0) return false;
+ return @mysql_data_seek($this->_queryID,$row);
+ }
+
+ function MoveNext()
+ {
+ //return adodb_movenext($this);
+ //if (defined('ADODB_EXTENSION')) return adodb_movenext($this);
+ if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
+ $this->_updatefields();
+ $this->_currentRow += 1;
+ return true;
+ }
+ if (!$this->EOF) {
+ $this->_currentRow += 1;
+ $this->EOF = true;
+ }
+ return false;
+ }
+
+ function _fetch()
+ {
+ $this->fields = @mysql_fetch_array($this->_queryID,$this->fetchMode);
+ $this->_updatefields();
+ return is_array($this->fields);
+ }
+
+ function _close() {
+ @mysql_free_result($this->_queryID);
+ $this->_queryID = false;
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'STRING':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'TINYBLOB':
+ case 'TINYTEXT':
+ case 'ENUM':
+ case 'SET':
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ case 'LONGTEXT':
+ case 'MEDIUMTEXT':
+ return 'X';
+
+ // php_mysql extension always returns 'blob' even if 'text'
+ // so we have to check whether binary...
+ case 'IMAGE':
+ case 'LONGBLOB':
+ case 'BLOB':
+ case 'MEDIUMBLOB':
+ case 'BINARY':
+ return !empty($fieldobj->binary) ? 'B' : 'X';
+
+ case 'YEAR':
+ case 'DATE': return 'D';
+
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP': return 'T';
+
+ case 'INT':
+ case 'INTEGER':
+ case 'BIGINT':
+ case 'TINYINT':
+ case 'MEDIUMINT':
+ case 'SMALLINT':
+
+ if (!empty($fieldobj->primary_key)) return 'R';
+ else return 'I';
+
+ default: return 'N';
+ }
+ }
+
+}
+
+class ADORecordSet_ext_mysql extends ADORecordSet_mysql {
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ function MoveNext()
+ {
+ return @adodb_movenext($this);
+ }
+}
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mysqli.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mysqli.inc.php
new file mode 100644
index 0000000..37b8448
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mysqli.inc.php
@@ -0,0 +1,1295 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ This is the preferred driver for MySQL connections, and supports both transactional
+ and non-transactional table types. You can use this as a drop-in replacement for both
+ the mysql and mysqlt drivers. As of ADOdb Version 5.20.0, all other native MySQL drivers
+ are deprecated
+
+ Requires mysql client. Works on Windows and Unix.
+
+21 October 2003: MySQLi extension implementation by Arjen de Rijke (a.de.rijke@xs4all.nl)
+Based on adodb 3.40
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_MYSQLI_LAYER")) {
+ define("_ADODB_MYSQLI_LAYER", 1 );
+
+ // PHP5 compat...
+ if (! defined("MYSQLI_BINARY_FLAG")) define("MYSQLI_BINARY_FLAG", 128);
+ if (!defined('MYSQLI_READ_DEFAULT_GROUP')) define('MYSQLI_READ_DEFAULT_GROUP',1);
+
+ // disable adodb extension - currently incompatible.
+ global $ADODB_EXTENSION; $ADODB_EXTENSION = false;
+
+class ADODB_mysqli extends ADOConnection {
+ var $databaseType = 'mysqli';
+ var $dataProvider = 'mysql';
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $metaTablesSQL = "SELECT
+ TABLE_NAME,
+ CASE WHEN TABLE_TYPE = 'VIEW' THEN 'V' ELSE 'T' END
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA=";
+ var $metaColumnsSQL = "SHOW COLUMNS FROM `%s`";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $hasLimit = true;
+ var $hasMoveFirst = true;
+ var $hasGenID = true;
+ var $isoDates = true; // accepts dates in ISO format
+ var $sysDate = 'CURDATE()';
+ var $sysTimeStamp = 'NOW()';
+ var $hasTransactions = true;
+ var $forceNewConnect = false;
+ var $poorAffectedRows = true;
+ var $clientFlags = 0;
+ var $substr = "substring";
+ var $port = 3306; //Default to 3306 to fix HHVM bug
+ var $socket = ''; //Default to empty string to fix HHVM bug
+ var $_bindInputArray = false;
+ var $nameQuote = '`'; /// string to use to quote identifiers and names
+ var $optionFlags = array(array(MYSQLI_READ_DEFAULT_GROUP,0));
+ var $arrayClass = 'ADORecordSet_array_mysqli';
+ var $multiQuery = false;
+
+ function __construct()
+ {
+ // if(!extension_loaded("mysqli"))
+ //trigger_error("You must have the mysqli extension installed.", E_USER_ERROR);
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET SESSION TRANSACTION ".$transaction_mode);
+ }
+
+ // returns true or false
+ // To add: parameter int $port,
+ // parameter string $socket
+ function _connect($argHostname = NULL,
+ $argUsername = NULL,
+ $argPassword = NULL,
+ $argDatabasename = NULL, $persist=false)
+ {
+ if(!extension_loaded("mysqli")) {
+ return null;
+ }
+ $this->_connectionID = @mysqli_init();
+
+ if (is_null($this->_connectionID)) {
+ // mysqli_init only fails if insufficient memory
+ if ($this->debug) {
+ ADOConnection::outp("mysqli_init() failed : " . $this->ErrorMsg());
+ }
+ return false;
+ }
+ /*
+ I suggest a simple fix which would enable adodb and mysqli driver to
+ read connection options from the standard mysql configuration file
+ /etc/my.cnf - "Bastien Duclaux" <bduclaux#yahoo.com>
+ */
+ foreach($this->optionFlags as $arr) {
+ mysqli_options($this->_connectionID,$arr[0],$arr[1]);
+ }
+
+ //http ://php.net/manual/en/mysqli.persistconns.php
+ if ($persist && PHP_VERSION > 5.2 && strncmp($argHostname,'p:',2) != 0) $argHostname = 'p:'.$argHostname;
+
+ #if (!empty($this->port)) $argHostname .= ":".$this->port;
+ $ok = @mysqli_real_connect($this->_connectionID,
+ $argHostname,
+ $argUsername,
+ $argPassword,
+ $argDatabasename,
+ # PHP7 compat: port must be int. Use default port if cast yields zero
+ (int)$this->port != 0 ? (int)$this->port : 3306,
+ $this->socket,
+ $this->clientFlags);
+
+ if ($ok) {
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ } else {
+ if ($this->debug) {
+ ADOConnection::outp("Could not connect : " . $this->ErrorMsg());
+ }
+ $this->_connectionID = null;
+ return false;
+ }
+ }
+
+ // returns true or false
+ // How to force a persistent connection
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, true);
+ }
+
+ // When is this used? Close old connection first?
+ // In _connect(), check $this->forceNewConnect?
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ $this->forceNewConnect = true;
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " IFNULL($field, $ifNull) "; // if MySQL
+ }
+
+ // do not use $ADODB_COUNTRECS
+ function GetOne($sql,$inputarr=false)
+ {
+ global $ADODB_GETONE_EOF;
+
+ $ret = false;
+ $rs = $this->Execute($sql,$inputarr);
+ if ($rs) {
+ if ($rs->EOF) $ret = $ADODB_GETONE_EOF;
+ else $ret = reset($rs->fields);
+ $rs->Close();
+ }
+ return $ret;
+ }
+
+ function ServerInfo()
+ {
+ $arr['description'] = $this->GetOne("select version()");
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+
+ //$this->Execute('SET AUTOCOMMIT=0');
+ mysqli_autocommit($this->_connectionID, false);
+ $this->Execute('BEGIN');
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('COMMIT');
+
+ //$this->Execute('SET AUTOCOMMIT=1');
+ mysqli_autocommit($this->_connectionID, true);
+ return true;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('ROLLBACK');
+ //$this->Execute('SET AUTOCOMMIT=1');
+ mysqli_autocommit($this->_connectionID, true);
+ return true;
+ }
+
+ function RowLock($tables,$where='',$col='1 as adodbignore')
+ {
+ if ($this->transCnt==0) $this->BeginTrans();
+ if ($where) $where = ' where '.$where;
+ $rs = $this->Execute("select $col from $tables $where for update");
+ return !empty($rs);
+ }
+
+ /**
+ * Quotes a string to be sent to the database
+ * When there is no active connection,
+ * @param string $s The string to quote
+ * @param boolean $magic_quotes If false, use mysqli_real_escape_string()
+ * if you are quoting a string extracted from a POST/GET variable,
+ * then pass get_magic_quotes_gpc() as the second parameter. This will
+ * ensure that the variable is not quoted twice, once by qstr() and
+ * once by the magic_quotes_gpc.
+ * Eg. $s = $db->qstr(_GET['name'],get_magic_quotes_gpc());
+ * @return string Quoted string
+ */
+ function qstr($s, $magic_quotes = false)
+ {
+ if (is_null($s)) return 'NULL';
+ if (!$magic_quotes) {
+ // mysqli_real_escape_string() throws a warning when the given
+ // connection is invalid
+ if (PHP_VERSION >= 5 && $this->_connectionID) {
+ return "'" . mysqli_real_escape_string($this->_connectionID, $s) . "'";
+ }
+
+ if ($this->replaceQuote[0] == '\\') {
+ $s = adodb_str_replace(array('\\',"\0"), array('\\\\',"\\\0") ,$s);
+ }
+ return "'" . str_replace("'", $this->replaceQuote, $s) . "'";
+ }
+ // undo magic quotes for "
+ $s = str_replace('\\"','"',$s);
+ return "'$s'";
+ }
+
+ function _insertid()
+ {
+ $result = @mysqli_insert_id($this->_connectionID);
+ if ($result == -1) {
+ if ($this->debug) ADOConnection::outp("mysqli_insert_id() failed : " . $this->ErrorMsg());
+ }
+ return $result;
+ }
+
+ // Only works for INSERT, UPDATE and DELETE query's
+ function _affectedrows()
+ {
+ $result = @mysqli_affected_rows($this->_connectionID);
+ if ($result == -1) {
+ if ($this->debug) ADOConnection::outp("mysqli_affected_rows() failed : " . $this->ErrorMsg());
+ }
+ return $result;
+ }
+
+ // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html
+ // Reference on Last_Insert_ID on the recommended way to simulate sequences
+ var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
+ var $_genSeqSQL = "create table if not exists %s (id int not null)";
+ var $_genSeqCountSQL = "select count(*) from %s";
+ var $_genSeq2SQL = "insert into %s values (%s)";
+ var $_dropSeqSQL = "drop table if exists %s";
+
+ function CreateSequence($seqname='adodbseq',$startID=1)
+ {
+ if (empty($this->_genSeqSQL)) return false;
+ $u = strtoupper($seqname);
+
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) return false;
+ return $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
+ }
+
+ function GenID($seqname='adodbseq',$startID=1)
+ {
+ // post-nuke sets hasGenID to false
+ if (!$this->hasGenID) return false;
+
+ $getnext = sprintf($this->_genIDSQL,$seqname);
+ $holdtransOK = $this->_transOK; // save the current status
+ $rs = @$this->Execute($getnext);
+ if (!$rs) {
+ if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset
+ $u = strtoupper($seqname);
+ $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ $cnt = $this->GetOne(sprintf($this->_genSeqCountSQL,$seqname));
+ if (!$cnt) $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
+ $rs = $this->Execute($getnext);
+ }
+
+ if ($rs) {
+ $this->genID = mysqli_insert_id($this->_connectionID);
+ $rs->Close();
+ } else
+ $this->genID = 0;
+
+ return $this->genID;
+ }
+
+ function MetaDatabases()
+ {
+ $query = "SHOW DATABASES";
+ $ret = $this->Execute($query);
+ if ($ret && is_object($ret)){
+ $arr = array();
+ while (!$ret->EOF){
+ $db = $ret->Fields('Database');
+ if ($db != 'mysql') $arr[] = $db;
+ $ret->MoveNext();
+ }
+ return $arr;
+ }
+ return $ret;
+ }
+
+
+ function MetaIndexes ($table, $primary = FALSE, $owner = false)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ // get index details
+ $rs = $this->Execute(sprintf('SHOW INDEXES FROM %s',$table));
+
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return $false;
+ }
+
+ $indexes = array ();
+
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ if ($primary == FALSE AND $row[2] == 'PRIMARY') {
+ continue;
+ }
+
+ if (!isset($indexes[$row[2]])) {
+ $indexes[$row[2]] = array(
+ 'unique' => ($row[1] == 0),
+ 'columns' => array()
+ );
+ }
+
+ $indexes[$row[2]]['columns'][$row[3] - 1] = $row[4];
+ }
+
+ // sort columns by order in the index
+ foreach ( array_keys ($indexes) as $index )
+ {
+ ksort ($indexes[$index]['columns']);
+ }
+
+ return $indexes;
+ }
+
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = 'DATE_FORMAT('.$col.",'";
+ $concat = false;
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= '%Y';
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "'),Quarter($col)";
+
+ if ($len > $i+1) $s .= ",DATE_FORMAT($col,'";
+ else $s .= ",('";
+ $concat = true;
+ break;
+ case 'M':
+ $s .= '%b';
+ break;
+
+ case 'm':
+ $s .= '%m';
+ break;
+ case 'D':
+ case 'd':
+ $s .= '%d';
+ break;
+
+ case 'H':
+ $s .= '%H';
+ break;
+
+ case 'h':
+ $s .= '%I';
+ break;
+
+ case 'i':
+ $s .= '%i';
+ break;
+
+ case 's':
+ $s .= '%s';
+ break;
+
+ case 'a':
+ case 'A':
+ $s .= '%p';
+ break;
+
+ case 'w':
+ $s .= '%w';
+ break;
+
+ case 'l':
+ $s .= '%W';
+ break;
+
+ default:
+
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $ch;
+ break;
+ }
+ }
+ $s.="')";
+ if ($concat) $s = "CONCAT($s)";
+ return $s;
+ }
+
+ // returns concatenated string
+ // much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator
+ function Concat()
+ {
+ $s = "";
+ $arr = func_get_args();
+
+ // suggestion by andrew005@mnogo.ru
+ $s = implode(',',$arr);
+ if (strlen($s) > 0) return "CONCAT($s)";
+ else return '';
+ }
+
+ // dayFraction is a day in floating point
+ function OffsetDate($dayFraction,$date=false)
+ {
+ if (!$date) $date = $this->sysDate;
+
+ $fraction = $dayFraction * 24 * 3600;
+ return $date . ' + INTERVAL ' . $fraction.' SECOND';
+
+// return "from_unixtime(unix_timestamp($date)+$fraction)";
+ }
+
+ function MetaProcedures($NamePattern = false, $catalog = null, $schemaPattern = null)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $procedures = array ();
+
+ // get index details
+
+ $likepattern = '';
+ if ($NamePattern) {
+ $likepattern = " LIKE '".$NamePattern."'";
+ }
+ $rs = $this->Execute('SHOW PROCEDURE STATUS'.$likepattern);
+ if (is_object($rs)) {
+
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ $procedures[$row[1]] = array(
+ 'type' => 'PROCEDURE',
+ 'catalog' => '',
+ 'schema' => '',
+ 'remarks' => $row[7],
+ );
+ }
+ }
+
+ $rs = $this->Execute('SHOW FUNCTION STATUS'.$likepattern);
+ if (is_object($rs)) {
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ $procedures[$row[1]] = array(
+ 'type' => 'FUNCTION',
+ 'catalog' => '',
+ 'schema' => '',
+ 'remarks' => $row[7]
+ );
+ }
+ }
+
+ // restore fetchmode
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $procedures;
+ }
+
+ /**
+ * Retrieves a list of tables based on given criteria
+ *
+ * @param string $ttype Table type = 'TABLE', 'VIEW' or false=both (default)
+ * @param string $showSchema schema name, false = current schema (default)
+ * @param string $mask filters the table by name
+ *
+ * @return array list of tables
+ */
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $save = $this->metaTablesSQL;
+ if ($showSchema && is_string($showSchema)) {
+ $this->metaTablesSQL .= $this->qstr($showSchema);
+ } else {
+ $this->metaTablesSQL .= "schema()";
+ }
+
+ if ($mask) {
+ $mask = $this->qstr($mask);
+ $this->metaTablesSQL .= " AND table_name LIKE $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ $this->metaTablesSQL = $save;
+ return $ret;
+ }
+
+ // "Innox - Juan Carlos Gonzalez" <jgonzalez#innox.com.mx>
+ function MetaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE )
+ {
+ global $ADODB_FETCH_MODE;
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC || $this->fetchMode == ADODB_FETCH_ASSOC) $associative = true;
+
+ if ( !empty($owner) ) {
+ $table = "$owner.$table";
+ }
+ $a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table));
+ if ($associative) {
+ $create_sql = isset($a_create_table["Create Table"]) ? $a_create_table["Create Table"] : $a_create_table["Create View"];
+ } else $create_sql = $a_create_table[1];
+
+ $matches = array();
+
+ if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false;
+ $foreign_keys = array();
+ $num_keys = count($matches[0]);
+ for ( $i = 0; $i < $num_keys; $i ++ ) {
+ $my_field = explode('`, `', $matches[1][$i]);
+ $ref_table = $matches[2][$i];
+ $ref_field = explode('`, `', $matches[3][$i]);
+
+ if ( $upper ) {
+ $ref_table = strtoupper($ref_table);
+ }
+
+ // see https://sourceforge.net/p/adodb/bugs/100/
+ if (!isset($foreign_keys[$ref_table])) {
+ $foreign_keys[$ref_table] = array();
+ }
+ $num_fields = count($my_field);
+ for ( $j = 0; $j < $num_fields; $j ++ ) {
+ if ( $associative ) {
+ $foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j];
+ } else {
+ $foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}";
+ }
+ }
+ }
+
+ return $foreign_keys;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $false = false;
+ if (!$this->metaColumnsSQL)
+ return $false;
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false)
+ $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs))
+ return $false;
+
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $type = $rs->fields[1];
+
+ // split type into type(length):
+ $fld->scale = null;
+ if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1;
+ } elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ } elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $arr = explode(",",$query_array[2]);
+ $fld->enums = $arr;
+ $zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6
+ $fld->max_length = ($zlen > 0) ? $zlen : 1;
+ } else {
+ $fld->type = $type;
+ $fld->max_length = -1;
+ }
+ $fld->not_null = ($rs->fields[2] != 'YES');
+ $fld->primary_key = ($rs->fields[3] == 'PRI');
+ $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
+ $fld->binary = (strpos($type,'blob') !== false);
+ $fld->unsigned = (strpos($type,'unsigned') !== false);
+ $fld->zerofill = (strpos($type,'zerofill') !== false);
+
+ if (!$fld->binary) {
+ $d = $rs->fields[4];
+ if ($d != '' && $d != 'NULL') {
+ $fld->has_default = true;
+ $fld->default_value = $d;
+ } else {
+ $fld->has_default = false;
+ }
+ }
+
+ if ($save == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $retarr;
+ }
+
+ // returns true or false
+ function SelectDB($dbName)
+ {
+// $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+
+ if ($this->_connectionID) {
+ $result = @mysqli_select_db($this->_connectionID, $dbName);
+ if (!$result) {
+ ADOConnection::outp("Select of database " . $dbName . " failed. " . $this->ErrorMsg());
+ }
+ return $result;
+ }
+ return false;
+ }
+
+ // parameters use PostgreSQL convention, not MySQL
+ function SelectLimit($sql,
+ $nrows = -1,
+ $offset = -1,
+ $inputarr = false,
+ $secs = 0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr = ($offset >= 0) ? "$offset," : '';
+ if ($nrows < 0) $nrows = '18446744073709551615';
+
+ if ($secs)
+ $rs = $this->CacheExecute($secs, $sql . " LIMIT $offsetStr$nrows" , $inputarr );
+ else
+ $rs = $this->Execute($sql . " LIMIT $offsetStr$nrows" , $inputarr );
+
+ return $rs;
+ }
+
+
+ function Prepare($sql)
+ {
+ return $sql;
+ $stmt = $this->_connectionID->prepare($sql);
+ if (!$stmt) {
+ echo $this->ErrorMsg();
+ return $sql;
+ }
+ return array($sql,$stmt);
+ }
+
+
+ // returns queryID or false
+ function _query($sql, $inputarr)
+ {
+ global $ADODB_COUNTRECS;
+ // Move to the next recordset, or return false if there is none. In a stored proc
+ // call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
+ // returns false. I think this is because the last "recordset" is actually just the
+ // return value of the stored proc (ie the number of rows affected).
+ // Commented out for reasons of performance. You should retrieve every recordset yourself.
+ // if (!mysqli_next_result($this->connection->_connectionID)) return false;
+
+ if (is_array($sql)) {
+
+ // Prepare() not supported because mysqli_stmt_execute does not return a recordset, but
+ // returns as bound variables.
+
+ $stmt = $sql[1];
+ $a = '';
+ foreach($inputarr as $k => $v) {
+ if (is_string($v)) $a .= 's';
+ else if (is_integer($v)) $a .= 'i';
+ else $a .= 'd';
+ }
+
+ $fnarr = array_merge( array($stmt,$a) , $inputarr);
+ $ret = call_user_func_array('mysqli_stmt_bind_param',$fnarr);
+ $ret = mysqli_stmt_execute($stmt);
+ return $ret;
+ }
+
+ /*
+ if (!$mysql_res = mysqli_query($this->_connectionID, $sql, ($ADODB_COUNTRECS) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)) {
+ if ($this->debug) ADOConnection::outp("Query: " . $sql . " failed. " . $this->ErrorMsg());
+ return false;
+ }
+
+ return $mysql_res;
+ */
+
+ if ($this->multiQuery) {
+ $rs = mysqli_multi_query($this->_connectionID, $sql.';');
+ if ($rs) {
+ $rs = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->_connectionID ) : @mysqli_use_result( $this->_connectionID );
+ return $rs ? $rs : true; // mysqli_more_results( $this->_connectionID )
+ }
+ } else {
+ $rs = mysqli_query($this->_connectionID, $sql, $ADODB_COUNTRECS ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
+
+ if ($rs) return $rs;
+ }
+
+ if($this->debug)
+ ADOConnection::outp("Query: " . $sql . " failed. " . $this->ErrorMsg());
+
+ return false;
+
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+ if (empty($this->_connectionID))
+ $this->_errorMsg = @mysqli_connect_error();
+ else
+ $this->_errorMsg = @mysqli_error($this->_connectionID);
+ return $this->_errorMsg;
+ }
+
+ /* Returns: the last error number from previous database operation */
+ function ErrorNo()
+ {
+ if (empty($this->_connectionID))
+ return @mysqli_connect_errno();
+ else
+ return @mysqli_errno($this->_connectionID);
+ }
+
+ // returns true or false
+ function _close()
+ {
+ @mysqli_close($this->_connectionID);
+ $this->_connectionID = false;
+ }
+
+ /*
+ * Maximum size of C field
+ */
+ function CharMax()
+ {
+ return 255;
+ }
+
+ /*
+ * Maximum size of X field
+ */
+ function TextMax()
+ {
+ return 4294967295;
+ }
+
+
+ // this is a set of functions for managing client encoding - very important if the encodings
+ // of your database and your output target (i.e. HTML) don't match
+ // for instance, you may have UTF8 database and server it on-site as latin1 etc.
+ // GetCharSet - get the name of the character set the client is using now
+ // Under Windows, the functions should work with MySQL 4.1.11 and above, the set of charsets supported
+ // depends on compile flags of mysql distribution
+
+ function GetCharSet()
+ {
+ //we will use ADO's builtin property charSet
+ if (!method_exists($this->_connectionID,'character_set_name'))
+ return false;
+
+ $this->charSet = @$this->_connectionID->character_set_name();
+ if (!$this->charSet) {
+ return false;
+ } else {
+ return $this->charSet;
+ }
+ }
+
+ // SetCharSet - switch the client encoding
+ function SetCharSet($charset_name)
+ {
+ if (!method_exists($this->_connectionID,'set_charset')) {
+ return false;
+ }
+
+ if ($this->charSet !== $charset_name) {
+ $if = @$this->_connectionID->set_charset($charset_name);
+ return ($if === true & $this->GetCharSet() == $charset_name);
+ } else {
+ return true;
+ }
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_mysqli extends ADORecordSet{
+
+ var $databaseType = "mysqli";
+ var $canSeek = true;
+
+ function __construct($queryID, $mode = false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+
+ switch ($mode) {
+ case ADODB_FETCH_NUM:
+ $this->fetchMode = MYSQLI_NUM;
+ break;
+ case ADODB_FETCH_ASSOC:
+ $this->fetchMode = MYSQLI_ASSOC;
+ break;
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default:
+ $this->fetchMode = MYSQLI_BOTH;
+ break;
+ }
+ $this->adodbFetchMode = $mode;
+ parent::__construct($queryID);
+ }
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+
+ $this->_numOfRows = $ADODB_COUNTRECS ? @mysqli_num_rows($this->_queryID) : -1;
+ $this->_numOfFields = @mysqli_num_fields($this->_queryID);
+ }
+
+/*
+1 = MYSQLI_NOT_NULL_FLAG
+2 = MYSQLI_PRI_KEY_FLAG
+4 = MYSQLI_UNIQUE_KEY_FLAG
+8 = MYSQLI_MULTIPLE_KEY_FLAG
+16 = MYSQLI_BLOB_FLAG
+32 = MYSQLI_UNSIGNED_FLAG
+64 = MYSQLI_ZEROFILL_FLAG
+128 = MYSQLI_BINARY_FLAG
+256 = MYSQLI_ENUM_FLAG
+512 = MYSQLI_AUTO_INCREMENT_FLAG
+1024 = MYSQLI_TIMESTAMP_FLAG
+2048 = MYSQLI_SET_FLAG
+32768 = MYSQLI_NUM_FLAG
+16384 = MYSQLI_PART_KEY_FLAG
+32768 = MYSQLI_GROUP_FLAG
+65536 = MYSQLI_UNIQUE_FLAG
+131072 = MYSQLI_BINCMP_FLAG
+*/
+
+ function FetchField($fieldOffset = -1)
+ {
+ $fieldnr = $fieldOffset;
+ if ($fieldOffset != -1) {
+ $fieldOffset = @mysqli_field_seek($this->_queryID, $fieldnr);
+ }
+ $o = @mysqli_fetch_field($this->_queryID);
+ if (!$o) return false;
+
+ //Fix for HHVM
+ if ( !isset($o->flags) ) {
+ $o->flags = 0;
+ }
+ /* Properties of an ADOFieldObject as set by MetaColumns */
+ $o->primary_key = $o->flags & MYSQLI_PRI_KEY_FLAG;
+ $o->not_null = $o->flags & MYSQLI_NOT_NULL_FLAG;
+ $o->auto_increment = $o->flags & MYSQLI_AUTO_INCREMENT_FLAG;
+ $o->binary = $o->flags & MYSQLI_BINARY_FLAG;
+ // $o->blob = $o->flags & MYSQLI_BLOB_FLAG; /* not returned by MetaColumns */
+ $o->unsigned = $o->flags & MYSQLI_UNSIGNED_FLAG;
+
+ return $o;
+ }
+
+ function GetRowAssoc($upper = ADODB_ASSOC_CASE)
+ {
+ if ($this->fetchMode == MYSQLI_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
+ return $this->fields;
+ }
+ $row = ADORecordSet::GetRowAssoc($upper);
+ return $row;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode != MYSQLI_NUM) {
+ return @$this->fields[$colname];
+ }
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i = 0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _seek($row)
+ {
+ if ($this->_numOfRows == 0 || $row < 0) {
+ return false;
+ }
+
+ mysqli_data_seek($this->_queryID, $row);
+ $this->EOF = false;
+ return true;
+ }
+
+
+ function NextRecordSet()
+ {
+ global $ADODB_COUNTRECS;
+
+ mysqli_free_result($this->_queryID);
+ $this->_queryID = -1;
+ // Move to the next recordset, or return false if there is none. In a stored proc
+ // call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
+ // returns false. I think this is because the last "recordset" is actually just the
+ // return value of the stored proc (ie the number of rows affected).
+ if(!mysqli_next_result($this->connection->_connectionID)) {
+ return false;
+ }
+ // CD: There is no $this->_connectionID variable, at least in the ADO version I'm using
+ $this->_queryID = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->connection->_connectionID )
+ : @mysqli_use_result( $this->connection->_connectionID );
+ if(!$this->_queryID) {
+ return false;
+ }
+ $this->_inited = false;
+ $this->bind = false;
+ $this->_currentRow = -1;
+ $this->Init();
+ return true;
+ }
+
+ // 10% speedup to move MoveNext to child class
+ // This is the only implementation that works now (23-10-2003).
+ // Other functions return no or the wrong results.
+ function MoveNext()
+ {
+ if ($this->EOF) return false;
+ $this->_currentRow++;
+ $this->fields = @mysqli_fetch_array($this->_queryID,$this->fetchMode);
+
+ if (is_array($this->fields)) {
+ $this->_updatefields();
+ return true;
+ }
+ $this->EOF = true;
+ return false;
+ }
+
+ function _fetch()
+ {
+ $this->fields = mysqli_fetch_array($this->_queryID,$this->fetchMode);
+ $this->_updatefields();
+ return is_array($this->fields);
+ }
+
+ function _close()
+ {
+ //if results are attached to this pointer from Stored Proceedure calls, the next standard query will die 2014
+ //only a problem with persistant connections
+
+ if(isset($this->connection->_connectionID) && $this->connection->_connectionID) {
+ while(mysqli_more_results($this->connection->_connectionID)){
+ mysqli_next_result($this->connection->_connectionID);
+ }
+ }
+
+ if($this->_queryID instanceof mysqli_result) {
+ mysqli_free_result($this->_queryID);
+ }
+ $this->_queryID = false;
+ }
+
+/*
+
+0 = MYSQLI_TYPE_DECIMAL
+1 = MYSQLI_TYPE_CHAR
+1 = MYSQLI_TYPE_TINY
+2 = MYSQLI_TYPE_SHORT
+3 = MYSQLI_TYPE_LONG
+4 = MYSQLI_TYPE_FLOAT
+5 = MYSQLI_TYPE_DOUBLE
+6 = MYSQLI_TYPE_NULL
+7 = MYSQLI_TYPE_TIMESTAMP
+8 = MYSQLI_TYPE_LONGLONG
+9 = MYSQLI_TYPE_INT24
+10 = MYSQLI_TYPE_DATE
+11 = MYSQLI_TYPE_TIME
+12 = MYSQLI_TYPE_DATETIME
+13 = MYSQLI_TYPE_YEAR
+14 = MYSQLI_TYPE_NEWDATE
+247 = MYSQLI_TYPE_ENUM
+248 = MYSQLI_TYPE_SET
+249 = MYSQLI_TYPE_TINY_BLOB
+250 = MYSQLI_TYPE_MEDIUM_BLOB
+251 = MYSQLI_TYPE_LONG_BLOB
+252 = MYSQLI_TYPE_BLOB
+253 = MYSQLI_TYPE_VAR_STRING
+254 = MYSQLI_TYPE_STRING
+255 = MYSQLI_TYPE_GEOMETRY
+*/
+
+ function MetaType($t, $len = -1, $fieldobj = false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'STRING':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'TINYBLOB':
+ case 'TINYTEXT':
+ case 'ENUM':
+ case 'SET':
+
+ case MYSQLI_TYPE_TINY_BLOB :
+ #case MYSQLI_TYPE_CHAR :
+ case MYSQLI_TYPE_STRING :
+ case MYSQLI_TYPE_ENUM :
+ case MYSQLI_TYPE_SET :
+ case 253 :
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ case 'LONGTEXT':
+ case 'MEDIUMTEXT':
+ return 'X';
+
+ // php_mysql extension always returns 'blob' even if 'text'
+ // so we have to check whether binary...
+ case 'IMAGE':
+ case 'LONGBLOB':
+ case 'BLOB':
+ case 'MEDIUMBLOB':
+
+ case MYSQLI_TYPE_BLOB :
+ case MYSQLI_TYPE_LONG_BLOB :
+ case MYSQLI_TYPE_MEDIUM_BLOB :
+ return !empty($fieldobj->binary) ? 'B' : 'X';
+
+ case 'YEAR':
+ case 'DATE':
+ case MYSQLI_TYPE_DATE :
+ case MYSQLI_TYPE_YEAR :
+ return 'D';
+
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+
+ case MYSQLI_TYPE_DATETIME :
+ case MYSQLI_TYPE_NEWDATE :
+ case MYSQLI_TYPE_TIME :
+ case MYSQLI_TYPE_TIMESTAMP :
+ return 'T';
+
+ case 'INT':
+ case 'INTEGER':
+ case 'BIGINT':
+ case 'TINYINT':
+ case 'MEDIUMINT':
+ case 'SMALLINT':
+
+ case MYSQLI_TYPE_INT24 :
+ case MYSQLI_TYPE_LONG :
+ case MYSQLI_TYPE_LONGLONG :
+ case MYSQLI_TYPE_SHORT :
+ case MYSQLI_TYPE_TINY :
+ if (!empty($fieldobj->primary_key)) return 'R';
+ return 'I';
+
+ // Added floating-point types
+ // Maybe not necessery.
+ case 'FLOAT':
+ case 'DOUBLE':
+// case 'DOUBLE PRECISION':
+ case 'DECIMAL':
+ case 'DEC':
+ case 'FIXED':
+ default:
+ //if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>";
+ return 'N';
+ }
+ } // function
+
+
+} // rs class
+
+}
+
+class ADORecordSet_array_mysqli extends ADORecordSet_array {
+
+ function __construct($id=-1,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+ function MetaType($t, $len = -1, $fieldobj = false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+
+ $len = -1; // mysql max_length is not accurate
+ switch (strtoupper($t)) {
+ case 'STRING':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'TINYBLOB':
+ case 'TINYTEXT':
+ case 'ENUM':
+ case 'SET':
+
+ case MYSQLI_TYPE_TINY_BLOB :
+ #case MYSQLI_TYPE_CHAR :
+ case MYSQLI_TYPE_STRING :
+ case MYSQLI_TYPE_ENUM :
+ case MYSQLI_TYPE_SET :
+ case 253 :
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ case 'LONGTEXT':
+ case 'MEDIUMTEXT':
+ return 'X';
+
+ // php_mysql extension always returns 'blob' even if 'text'
+ // so we have to check whether binary...
+ case 'IMAGE':
+ case 'LONGBLOB':
+ case 'BLOB':
+ case 'MEDIUMBLOB':
+
+ case MYSQLI_TYPE_BLOB :
+ case MYSQLI_TYPE_LONG_BLOB :
+ case MYSQLI_TYPE_MEDIUM_BLOB :
+
+ return !empty($fieldobj->binary) ? 'B' : 'X';
+ case 'YEAR':
+ case 'DATE':
+ case MYSQLI_TYPE_DATE :
+ case MYSQLI_TYPE_YEAR :
+
+ return 'D';
+
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+
+ case MYSQLI_TYPE_DATETIME :
+ case MYSQLI_TYPE_NEWDATE :
+ case MYSQLI_TYPE_TIME :
+ case MYSQLI_TYPE_TIMESTAMP :
+
+ return 'T';
+
+ case 'INT':
+ case 'INTEGER':
+ case 'BIGINT':
+ case 'TINYINT':
+ case 'MEDIUMINT':
+ case 'SMALLINT':
+
+ case MYSQLI_TYPE_INT24 :
+ case MYSQLI_TYPE_LONG :
+ case MYSQLI_TYPE_LONGLONG :
+ case MYSQLI_TYPE_SHORT :
+ case MYSQLI_TYPE_TINY :
+
+ if (!empty($fieldobj->primary_key)) return 'R';
+
+ return 'I';
+
+
+ // Added floating-point types
+ // Maybe not necessery.
+ case 'FLOAT':
+ case 'DOUBLE':
+// case 'DOUBLE PRECISION':
+ case 'DECIMAL':
+ case 'DEC':
+ case 'FIXED':
+ default:
+ //if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>";
+ return 'N';
+ }
+ } // function
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mysqlpo.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mysqlpo.inc.php
new file mode 100644
index 0000000..1cc7a91
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mysqlpo.inc.php
@@ -0,0 +1,128 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ MySQL code that supports transactions. For MySQL 3.23 or later.
+ Code from James Poon <jpoon88@yahoo.com>
+
+ This driver extends the deprecated mysql driver, and was originally designed to be a
+ portable driver in the same manner as oci8po and mssqlpo. Its functionality
+ is exactly duplicated in the mysqlt driver, which is itself deprecated.
+ This driver will be removed in ADOdb version 6.0.0.
+
+ Requires mysql client. Works on Windows and Unix.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-mysql.inc.php");
+
+
+class ADODB_mysqlt extends ADODB_mysql {
+ var $databaseType = 'mysqlt';
+ var $ansiOuter = true; // for Version 3.23.17 or later
+ var $hasTransactions = true;
+ var $autoRollback = true; // apparently mysql does not autorollback properly
+
+ function __construct()
+ {
+ global $ADODB_EXTENSION; if ($ADODB_EXTENSION) $this->rsPrefix .= 'ext_';
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->Execute('SET AUTOCOMMIT=0');
+ $this->Execute('BEGIN');
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('COMMIT');
+ $this->Execute('SET AUTOCOMMIT=1');
+ return true;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->Execute('ROLLBACK');
+ $this->Execute('SET AUTOCOMMIT=1');
+ return true;
+ }
+
+ function RowLock($tables,$where='',$col='1 as adodbignore')
+ {
+ if ($this->transCnt==0) $this->BeginTrans();
+ if ($where) $where = ' where '.$where;
+ $rs = $this->Execute("select $col from $tables $where for update");
+ return !empty($rs);
+ }
+
+}
+
+class ADORecordSet_mysqlt extends ADORecordSet_mysql{
+ var $databaseType = "mysqlt";
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+
+ switch ($mode)
+ {
+ case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
+ case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
+
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default: $this->fetchMode = MYSQL_BOTH; break;
+ }
+
+ $this->adodbFetchMode = $mode;
+ parent::__construct($queryID);
+ }
+
+ function MoveNext()
+ {
+ if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
+ $this->_currentRow += 1;
+ return true;
+ }
+ if (!$this->EOF) {
+ $this->_currentRow += 1;
+ $this->EOF = true;
+ }
+ return false;
+ }
+}
+
+class ADORecordSet_ext_mysqlt extends ADORecordSet_mysqlt {
+
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ function MoveNext()
+ {
+ return adodb_movenext($this);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-mysqlt.inc.php b/vendor/adodb/adodb-php/drivers/adodb-mysqlt.inc.php
new file mode 100644
index 0000000..0e31926
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-mysqlt.inc.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ This driver only supports the original MySQL driver in transactional mode. It
+ is deprected in PHP version 5.5 and removed in PHP version 7. It is deprecated
+ as of ADOdb version 5.20.0. Use the mysqli driver instead, which supports both
+ transactional and non-transactional updates
+
+ Requires mysql client. Works on Windows and Unix.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-mysql.inc.php");
+
+
+class ADODB_mysqlt extends ADODB_mysql {
+ var $databaseType = 'mysqlt';
+ var $ansiOuter = true; // for Version 3.23.17 or later
+ var $hasTransactions = true;
+ var $autoRollback = true; // apparently mysql does not autorollback properly
+
+ function __construct()
+ {
+ global $ADODB_EXTENSION; if ($ADODB_EXTENSION) $this->rsPrefix .= 'ext_';
+ }
+
+ /* set transaction mode
+
+ SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
+{ READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
+
+ */
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET SESSION TRANSACTION ".$transaction_mode);
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->Execute('SET AUTOCOMMIT=0');
+ $this->Execute('BEGIN');
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+
+ if ($this->transCnt) $this->transCnt -= 1;
+ $ok = $this->Execute('COMMIT');
+ $this->Execute('SET AUTOCOMMIT=1');
+ return $ok ? true : false;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $ok = $this->Execute('ROLLBACK');
+ $this->Execute('SET AUTOCOMMIT=1');
+ return $ok ? true : false;
+ }
+
+ function RowLock($tables,$where='',$col='1 as adodbignore')
+ {
+ if ($this->transCnt==0) $this->BeginTrans();
+ if ($where) $where = ' where '.$where;
+ $rs = $this->Execute("select $col from $tables $where for update");
+ return !empty($rs);
+ }
+
+}
+
+class ADORecordSet_mysqlt extends ADORecordSet_mysql{
+ var $databaseType = "mysqlt";
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+
+ switch ($mode)
+ {
+ case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
+ case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
+
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default: $this->fetchMode = MYSQL_BOTH; break;
+ }
+
+ $this->adodbFetchMode = $mode;
+ parent::__construct($queryID);
+ }
+
+ function MoveNext()
+ {
+ if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
+ $this->_currentRow += 1;
+ return true;
+ }
+ if (!$this->EOF) {
+ $this->_currentRow += 1;
+ $this->EOF = true;
+ }
+ return false;
+ }
+}
+
+class ADORecordSet_ext_mysqlt extends ADORecordSet_mysqlt {
+
+ function MoveNext()
+ {
+ return adodb_movenext($this);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-netezza.inc.php b/vendor/adodb/adodb-php/drivers/adodb-netezza.inc.php
new file mode 100644
index 0000000..7a1b63c
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-netezza.inc.php
@@ -0,0 +1,157 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+ First cut at the Netezza Driver by Josh Eldridge joshuae74#hotmail.com
+ Based on the previous postgres drivers.
+ http://www.netezza.com/
+ Major Additions/Changes:
+ MetaDatabasesSQL, MetaTablesSQL, MetaColumnsSQL
+ Note: You have to have admin privileges to access the system tables
+ Removed non-working keys code (Netezza has no concept of keys)
+ Fixed the way data types and lengths are returned in MetaColumns()
+ as well as added the default lengths for certain types
+ Updated public variables for Netezza
+ Still need to remove blob functions, as Netezza doesn't suppport blob
+*/
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-postgres64.inc.php');
+
+class ADODB_netezza extends ADODB_postgres64 {
+ var $databaseType = 'netezza';
+ var $dataProvider = 'netezza';
+ var $hasInsertID = false;
+ var $_resultid = false;
+ var $concat_operator='||';
+ var $random = 'random';
+ var $metaDatabasesSQL = "select objname from _v_object_data where objtype='database' order by 1";
+ var $metaTablesSQL = "select objname from _v_object_data where objtype='table' order by 1";
+ var $isoDates = true; // accepts dates in ISO format
+ var $sysDate = "CURRENT_DATE";
+ var $sysTimeStamp = "CURRENT_TIMESTAMP";
+ var $blobEncodeType = 'C';
+ var $metaColumnsSQL = "SELECT attname, atttype FROM _v_relation_column_def WHERE name = '%s' AND attnum > 0 ORDER BY attnum";
+ var $metaColumnsSQL1 = "SELECT attname, atttype FROM _v_relation_column_def WHERE name = '%s' AND attnum > 0 ORDER BY attnum";
+ // netezza doesn't have keys. it does have distributions, so maybe this is
+ // something that can be pulled from the system tables
+ var $metaKeySQL = "";
+ var $hasAffectedRows = true;
+ var $hasLimit = true;
+ var $true = 't'; // string that represents TRUE for a database
+ var $false = 'f'; // string that represents FALSE for a database
+ var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database
+ var $fmtTimeStamp = "'Y-m-d G:i:s'"; // used by DBTimeStamp as the default timestamp fmt.
+ var $ansiOuter = true;
+ var $autoRollback = true; // apparently pgsql does not autorollback properly before 4.3.4
+ // http://bugs.php.net/bug.php?id=25404
+
+
+ function __construct()
+ {
+
+ }
+
+ function MetaColumns($table,$upper=true)
+ {
+
+ // Changed this function to support Netezza which has no concept of keys
+ // could posisbly work on other things from the system table later.
+
+ global $ADODB_FETCH_MODE;
+
+ $table = strtolower($table);
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table,$table));
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rs === false) return false;
+
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+
+ // since we're returning type and length as one string,
+ // split them out here.
+
+ if ($first = strstr($rs->fields[1], "(")) {
+ $fld->max_length = trim($first, "()");
+ } else {
+ $fld->max_length = -1;
+ }
+
+ if ($first = strpos($rs->fields[1], "(")) {
+ $fld->type = substr($rs->fields[1], 0, $first);
+ } else {
+ $fld->type = $rs->fields[1];
+ }
+
+ switch ($fld->type) {
+ case "byteint":
+ case "boolean":
+ $fld->max_length = 1;
+ break;
+ case "smallint":
+ $fld->max_length = 2;
+ break;
+ case "integer":
+ case "numeric":
+ case "date":
+ $fld->max_length = 4;
+ break;
+ case "bigint":
+ case "time":
+ case "timestamp":
+ $fld->max_length = 8;
+ break;
+ case "timetz":
+ case "time with time zone":
+ $fld->max_length = 12;
+ break;
+ }
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[($upper) ? strtoupper($fld->name) : $fld->name] = $fld;
+
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ return $retarr;
+
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_netezza extends ADORecordSet_postgres64
+{
+ var $databaseType = "netezza";
+ var $canSeek = true;
+
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ // _initrs modified to disable blob handling
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS)? @pg_num_rows($this->_queryID):-1;
+ $this->_numOfFields = @pg_num_fields($this->_queryID);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-oci8.inc.php b/vendor/adodb/adodb-php/drivers/adodb-oci8.inc.php
new file mode 100644
index 0000000..66847ed
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-oci8.inc.php
@@ -0,0 +1,1826 @@
+<?php
+/*
+
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim. All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ Code contributed by George Fourlanos <fou@infomap.gr>
+
+ 13 Nov 2000 jlim - removed all ora_* references.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+NLS_Date_Format
+Allows you to use a date format other than the Oracle Lite default. When a literal
+character string appears where a date value is expected, the Oracle Lite database
+tests the string to see if it matches the formats of Oracle, SQL-92, or the value
+specified for this parameter in the POLITE.INI file. Setting this parameter also
+defines the default format used in the TO_CHAR or TO_DATE functions when no
+other format string is supplied.
+
+For Oracle the default is dd-mon-yy or dd-mon-yyyy, and for SQL-92 the default is
+yy-mm-dd or yyyy-mm-dd.
+
+Using 'RR' in the format forces two-digit years less than or equal to 49 to be
+interpreted as years in the 21st century (2000-2049), and years over 50 as years in
+the 20th century (1950-1999). Setting the RR format as the default for all two-digit
+year entries allows you to become year-2000 compliant. For example:
+NLS_DATE_FORMAT='RR-MM-DD'
+
+You can also modify the date format using the ALTER SESSION command.
+*/
+
+# define the LOB descriptor type for the given type
+# returns false if no LOB descriptor
+function oci_lob_desc($type) {
+ switch ($type) {
+ case OCI_B_BFILE: return OCI_D_FILE;
+ case OCI_B_CFILEE: return OCI_D_FILE;
+ case OCI_B_CLOB: return OCI_D_LOB;
+ case OCI_B_BLOB: return OCI_D_LOB;
+ case OCI_B_ROWID: return OCI_D_ROWID;
+ }
+ return false;
+}
+
+class ADODB_oci8 extends ADOConnection {
+ var $databaseType = 'oci8';
+ var $dataProvider = 'oci8';
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $concat_operator='||';
+ var $sysDate = "TRUNC(SYSDATE)";
+ var $sysTimeStamp = 'SYSDATE'; // requires oracle 9 or later, otherwise use SYSDATE
+ var $metaDatabasesSQL = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN ('SYS','SYSTEM','DBSNMP','OUTLN') ORDER BY 1";
+ var $_stmt;
+ var $_commit = OCI_COMMIT_ON_SUCCESS;
+ var $_initdate = true; // init date to YYYY-MM-DD
+ var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW') and table_name not like 'BIN\$%'"; // bin$ tables are recycle bin tables
+ var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net
+ var $metaColumnsSQL2 = "select column_name,data_type,data_length, data_scale, data_precision,
+ case when nullable = 'Y' then 'NULL'
+ else 'NOT NULL' end as nulls,
+ data_default from all_tab_cols
+ where owner='%s' and table_name='%s' order by column_id"; // when there is a schema
+ var $_bindInputArray = true;
+ var $hasGenID = true;
+ var $_genIDSQL = "SELECT (%s.nextval) FROM DUAL";
+ var $_genSeqSQL = "
+DECLARE
+ PRAGMA AUTONOMOUS_TRANSACTION;
+BEGIN
+ execute immediate 'CREATE SEQUENCE %s START WITH %s';
+END;
+";
+
+ var $_dropSeqSQL = "DROP SEQUENCE %s";
+ var $hasAffectedRows = true;
+ var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)";
+ var $noNullStrings = false;
+ var $connectSID = false;
+ var $_bind = false;
+ var $_nestedSQL = true;
+ var $_hasOciFetchStatement = false;
+ var $_getarray = false; // currently not working
+ var $leftOuter = ''; // oracle wierdness, $col = $value (+) for LEFT OUTER, $col (+)= $value for RIGHT OUTER
+ var $session_sharing_force_blob = false; // alter session on updateblob if set to true
+ var $firstrows = true; // enable first rows optimization on SelectLimit()
+ var $selectOffsetAlg1 = 1000; // when to use 1st algorithm of selectlimit.
+ var $NLS_DATE_FORMAT = 'YYYY-MM-DD'; // To include time, use 'RRRR-MM-DD HH24:MI:SS'
+ var $dateformat = 'YYYY-MM-DD'; // DBDate format
+ var $useDBDateFormatForTextInput=false;
+ var $datetime = false; // MetaType('DATE') returns 'D' (datetime==false) or 'T' (datetime == true)
+ var $_refLOBs = array();
+
+ // var $ansiOuter = true; // if oracle9
+
+ function __construct()
+ {
+ $this->_hasOciFetchStatement = ADODB_PHPVER >= 0x4200;
+ if (defined('ADODB_EXTENSION')) {
+ $this->rsPrefix .= 'ext_';
+ }
+ }
+
+ /* function MetaColumns($table, $normalize=true) added by smondino@users.sourceforge.net*/
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = '';
+ $this->_findschema($table, $schema);
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+
+ if ($schema){
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL2, strtoupper($schema), strtoupper($table)));
+ }
+ else {
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+ }
+
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ if (!$rs) {
+ return false;
+ }
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+ $fld->scale = $rs->fields[3];
+ if ($rs->fields[1] == 'NUMBER') {
+ if ($rs->fields[3] == 0) {
+ $fld->type = 'INT';
+ }
+ $fld->max_length = $rs->fields[4];
+ }
+ $fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0);
+ $fld->binary = (strpos($fld->type,'BLOB') !== false);
+ $fld->default_value = $rs->fields[6];
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ }
+ else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if (empty($retarr)) {
+ return false;
+ }
+ return $retarr;
+ }
+
+ function Time()
+ {
+ $rs = $this->Execute("select TO_CHAR($this->sysTimeStamp,'YYYY-MM-DD HH24:MI:SS') from dual");
+ if ($rs && !$rs->EOF) {
+ return $this->UnixTimeStamp(reset($rs->fields));
+ }
+
+ return false;
+ }
+
+ /**
+ * Multiple modes of connection are supported:
+ *
+ * a. Local Database
+ * $conn->Connect(false,'scott','tiger');
+ *
+ * b. From tnsnames.ora
+ * $conn->Connect($tnsname,'scott','tiger');
+ * $conn->Connect(false,'scott','tiger',$tnsname);
+ *
+ * c. Server + service name
+ * $conn->Connect($serveraddress,'scott,'tiger',$service_name);
+ *
+ * d. Server + SID
+ * $conn->connectSID = true;
+ * $conn->Connect($serveraddress,'scott,'tiger',$SID);
+ *
+ * @param string|false $argHostname DB server hostname or TNS name
+ * @param string $argUsername
+ * @param string $argPassword
+ * @param string $argDatabasename Service name, SID (defaults to null)
+ * @param int $mode Connection mode, defaults to 0
+ * (0 = non-persistent, 1 = persistent, 2 = force new connection)
+ *
+ * @return bool
+ */
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename=null, $mode=0)
+ {
+ if (!function_exists('oci_pconnect')) {
+ return null;
+ }
+ #adodb_backtrace();
+
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ if($argHostname) { // added by Jorma Tuomainen <jorma.tuomainen@ppoy.fi>
+ if (empty($argDatabasename)) {
+ $argDatabasename = $argHostname;
+ }
+ else {
+ if(strpos($argHostname,":")) {
+ $argHostinfo=explode(":",$argHostname);
+ $argHostname=$argHostinfo[0];
+ $argHostport=$argHostinfo[1];
+ } else {
+ $argHostport = empty($this->port)? "1521" : $this->port;
+ }
+
+ if (strncasecmp($argDatabasename,'SID=',4) == 0) {
+ $argDatabasename = substr($argDatabasename,4);
+ $this->connectSID = true;
+ }
+
+ if ($this->connectSID) {
+ $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+ .")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))";
+ } else
+ $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+ .")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))";
+ }
+ }
+
+ //if ($argHostname) print "<p>Connect: 1st argument should be left blank for $this->databaseType</p>";
+ if ($mode==1) {
+ $this->_connectionID = ($this->charSet)
+ ? oci_pconnect($argUsername,$argPassword, $argDatabasename,$this->charSet)
+ : oci_pconnect($argUsername,$argPassword, $argDatabasename);
+ if ($this->_connectionID && $this->autoRollback) {
+ oci_rollback($this->_connectionID);
+ }
+ } else if ($mode==2) {
+ $this->_connectionID = ($this->charSet)
+ ? oci_new_connect($argUsername,$argPassword, $argDatabasename,$this->charSet)
+ : oci_new_connect($argUsername,$argPassword, $argDatabasename);
+ } else {
+ $this->_connectionID = ($this->charSet)
+ ? oci_connect($argUsername,$argPassword, $argDatabasename,$this->charSet)
+ : oci_connect($argUsername,$argPassword, $argDatabasename);
+ }
+ if (!$this->_connectionID) {
+ return false;
+ }
+
+ if ($this->_initdate) {
+ $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='".$this->NLS_DATE_FORMAT."'");
+ }
+
+ // looks like:
+ // Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production With the Partitioning option JServer Release 8.1.7.0.0 - Production
+ // $vers = oci_server_version($this->_connectionID);
+ // if (strpos($vers,'8i') !== false) $this->ansiOuter = true;
+ return true;
+ }
+
+ function ServerInfo()
+ {
+ $arr['compat'] = $this->GetOne('select value from sys.database_compatible_level');
+ $arr['description'] = @oci_server_version($this->_connectionID);
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,1);
+ }
+
+ // returns true or false
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,2);
+ }
+
+ function _affectedrows()
+ {
+ if (is_resource($this->_stmt)) {
+ return @oci_num_rows($this->_stmt);
+ }
+ return 0;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " NVL($field, $ifNull) "; // if Oracle
+ }
+
+ // format and return date string in database date format
+ function DBDate($d,$isfld=false)
+ {
+ if (empty($d) && $d !== 0) {
+ return 'null';
+ }
+
+ if ($isfld) {
+ $d = _adodb_safedate($d);
+ return 'TO_DATE('.$d.",'".$this->dateformat."')";
+ }
+
+ if (is_string($d)) {
+ $d = ADORecordSet::UnixDate($d);
+ }
+
+ if (is_object($d)) {
+ $ds = $d->format($this->fmtDate);
+ }
+ else {
+ $ds = adodb_date($this->fmtDate,$d);
+ }
+
+ return "TO_DATE(".$ds.",'".$this->dateformat."')";
+ }
+
+ function BindDate($d)
+ {
+ $d = ADOConnection::DBDate($d);
+ if (strncmp($d, "'", 1)) {
+ return $d;
+ }
+
+ return substr($d, 1, strlen($d)-2);
+ }
+
+ function BindTimeStamp($ts)
+ {
+ if (empty($ts) && $ts !== 0) {
+ return 'null';
+ }
+ if (is_string($ts)) {
+ $ts = ADORecordSet::UnixTimeStamp($ts);
+ }
+
+ if (is_object($ts)) {
+ $tss = $ts->format("'Y-m-d H:i:s'");
+ }
+ else {
+ $tss = adodb_date("'Y-m-d H:i:s'",$ts);
+ }
+
+ return $tss;
+ }
+
+ // format and return date string in database timestamp format
+ function DBTimeStamp($ts,$isfld=false)
+ {
+ if (empty($ts) && $ts !== 0) {
+ return 'null';
+ }
+ if ($isfld) {
+ return 'TO_DATE(substr('.$ts.",1,19),'RRRR-MM-DD, HH24:MI:SS')";
+ }
+ if (is_string($ts)) {
+ $ts = ADORecordSet::UnixTimeStamp($ts);
+ }
+
+ if (is_object($ts)) {
+ $tss = $ts->format("'Y-m-d H:i:s'");
+ }
+ else {
+ $tss = date("'Y-m-d H:i:s'",$ts);
+ }
+
+ return 'TO_DATE('.$tss.",'RRRR-MM-DD, HH24:MI:SS')";
+ }
+
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if ($this->autoCommit) {
+ $this->BeginTrans();
+ }
+ return $this->GetOne("select $col from $tables where $where for update");
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(strtoupper($mask));
+ $this->metaTablesSQL .= " AND upper(table_name) like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+ // Mark Newnham
+ function MetaIndexes ($table, $primary = FALSE, $owner=false)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ // get index details
+ $table = strtoupper($table);
+
+ // get Primary index
+ $primary_key = '';
+
+ $rs = $this->Execute(sprintf("SELECT * FROM ALL_CONSTRAINTS WHERE UPPER(TABLE_NAME)='%s' AND CONSTRAINT_TYPE='P'",$table));
+ if (!is_object($rs)) {
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return false;
+ }
+
+ if ($row = $rs->FetchRow()) {
+ $primary_key = $row[1]; //constraint_name
+ }
+
+ if ($primary==TRUE && $primary_key=='') {
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return false; //There is no primary key
+ }
+
+ $rs = $this->Execute(sprintf("SELECT ALL_INDEXES.INDEX_NAME, ALL_INDEXES.UNIQUENESS, ALL_IND_COLUMNS.COLUMN_POSITION, ALL_IND_COLUMNS.COLUMN_NAME FROM ALL_INDEXES,ALL_IND_COLUMNS WHERE UPPER(ALL_INDEXES.TABLE_NAME)='%s' AND ALL_IND_COLUMNS.INDEX_NAME=ALL_INDEXES.INDEX_NAME",$table));
+
+
+ if (!is_object($rs)) {
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return false;
+ }
+
+ $indexes = array ();
+ // parse index data into array
+
+ while ($row = $rs->FetchRow()) {
+ if ($primary && $row[0] != $primary_key) {
+ continue;
+ }
+ if (!isset($indexes[$row[0]])) {
+ $indexes[$row[0]] = array(
+ 'unique' => ($row[1] == 'UNIQUE'),
+ 'columns' => array()
+ );
+ }
+ $indexes[$row[0]]['columns'][$row[2] - 1] = $row[3];
+ }
+
+ // sort columns by order in the index
+ foreach ( array_keys ($indexes) as $index ) {
+ ksort ($indexes[$index]['columns']);
+ }
+
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ }
+ return $indexes;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ $this->transCnt += 1;
+ $this->autoCommit = false;
+ $this->_commit = OCI_DEFAULT;
+
+ if ($this->_transmode) {
+ $ok = $this->Execute("SET TRANSACTION ".$this->_transmode);
+ }
+ else {
+ $ok = true;
+ }
+
+ return $ok ? true : false;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ if (!$ok) {
+ return $this->RollbackTrans();
+ }
+
+ if ($this->transCnt) {
+ $this->transCnt -= 1;
+ }
+ $ret = oci_commit($this->_connectionID);
+ $this->_commit = OCI_COMMIT_ON_SUCCESS;
+ $this->autoCommit = true;
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ if ($this->transCnt) {
+ $this->transCnt -= 1;
+ }
+ $ret = oci_rollback($this->_connectionID);
+ $this->_commit = OCI_COMMIT_ON_SUCCESS;
+ $this->autoCommit = true;
+ return $ret;
+ }
+
+
+ function SelectDB($dbName)
+ {
+ return false;
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_errorMsg !== false) {
+ return $this->_errorMsg;
+ }
+
+ if (is_resource($this->_stmt)) {
+ $arr = @oci_error($this->_stmt);
+ }
+ if (empty($arr)) {
+ if (is_resource($this->_connectionID)) {
+ $arr = @oci_error($this->_connectionID);
+ }
+ else {
+ $arr = @oci_error();
+ }
+ if ($arr === false) {
+ return '';
+ }
+ }
+ $this->_errorMsg = $arr['message'];
+ $this->_errorCode = $arr['code'];
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_errorCode !== false) {
+ return $this->_errorCode;
+ }
+
+ if (is_resource($this->_stmt)) {
+ $arr = @oci_error($this->_stmt);
+ }
+ if (empty($arr)) {
+ $arr = @oci_error($this->_connectionID);
+ if ($arr == false) {
+ $arr = @oci_error();
+ }
+ if ($arr == false) {
+ return '';
+ }
+ }
+
+ $this->_errorMsg = $arr['message'];
+ $this->_errorCode = $arr['code'];
+
+ return $arr['code'];
+ }
+
+ /**
+ * Format date column in sql string given an input format that understands Y M D
+ */
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) {
+ $col = $this->sysTimeStamp;
+ }
+ $s = 'TO_CHAR('.$col.",'";
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= 'YYYY';
+ break;
+ case 'Q':
+ case 'q':
+ $s .= 'Q';
+ break;
+
+ case 'M':
+ $s .= 'Mon';
+ break;
+
+ case 'm':
+ $s .= 'MM';
+ break;
+ case 'D':
+ case 'd':
+ $s .= 'DD';
+ break;
+
+ case 'H':
+ $s.= 'HH24';
+ break;
+
+ case 'h':
+ $s .= 'HH';
+ break;
+
+ case 'i':
+ $s .= 'MI';
+ break;
+
+ case 's':
+ $s .= 'SS';
+ break;
+
+ case 'a':
+ case 'A':
+ $s .= 'AM';
+ break;
+
+ case 'w':
+ $s .= 'D';
+ break;
+
+ case 'l':
+ $s .= 'DAY';
+ break;
+
+ case 'W':
+ $s .= 'WW';
+ break;
+
+ default:
+ // handle escape characters...
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ if (strpos('-/.:;, ',$ch) !== false) {
+ $s .= $ch;
+ }
+ else {
+ $s .= '"'.$ch.'"';
+ }
+
+ }
+ }
+ return $s. "')";
+ }
+
+ function GetRandRow($sql, $arr = false)
+ {
+ $sql = "SELECT * FROM ($sql ORDER BY dbms_random.value) WHERE rownum = 1";
+
+ return $this->GetRow($sql,$arr);
+ }
+
+ /**
+ * This algorithm makes use of
+ *
+ * a. FIRST_ROWS hint
+ * The FIRST_ROWS hint explicitly chooses the approach to optimize response
+ * time, that is, minimum resource usage to return the first row. Results
+ * will be returned as soon as they are identified.
+ *
+ * b. Uses rownum tricks to obtain only the required rows from a given offset.
+ * As this uses complicated sql statements, we only use this if $offset >= 100.
+ * This idea by Tomas V V Cox.
+ *
+ * This implementation does not appear to work with oracle 8.0.5 or earlier.
+ * Comment out this function then, and the slower SelectLimit() in the base
+ * class will be used.
+ *
+ * Note: FIRST_ROWS hinting is only used if $sql is a string; when
+ * processing a prepared statement's handle, no hinting is performed.
+ */
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ // Since the methods used to limit the number of returned rows rely
+ // on modifying the provided SQL query, we can't work with prepared
+ // statements so we just extract the SQL string.
+ if(is_array($sql)) {
+ $sql = $sql[0];
+ }
+
+ // seems that oracle only supports 1 hint comment in 8i
+ if ($this->firstrows) {
+ if ($nrows > 500 && $nrows < 1000) {
+ $hint = "FIRST_ROWS($nrows)";
+ }
+ else {
+ $hint = 'FIRST_ROWS';
+ }
+
+ if (strpos($sql,'/*+') !== false) {
+ $sql = str_replace('/*+ ',"/*+$hint ",$sql);
+ }
+ else {
+ $sql = preg_replace('/^[ \t\n]*select/i',"SELECT /*+$hint*/",$sql);
+ }
+ $hint = "/*+ $hint */";
+ } else {
+ $hint = '';
+ }
+
+ if ($offset == -1 || ($offset < $this->selectOffsetAlg1 && 0 < $nrows && $nrows < 1000)) {
+ if ($nrows > 0) {
+ if ($offset > 0) {
+ $nrows += $offset;
+ }
+ $sql = "select * from (".$sql.") where rownum <= :adodb_offset";
+ $inputarr['adodb_offset'] = $nrows;
+ $nrows = -1;
+ }
+ // note that $nrows = 0 still has to work ==> no rows returned
+
+ return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+ } else {
+ // Algorithm by Tomas V V Cox, from PEAR DB oci8.php
+
+ // Let Oracle return the name of the columns
+ $q_fields = "SELECT * FROM (".$sql.") WHERE NULL = NULL";
+
+ if (! $stmt_arr = $this->Prepare($q_fields)) {
+ return false;
+ }
+ $stmt = $stmt_arr[1];
+
+ if (is_array($inputarr)) {
+ foreach($inputarr as $k => $v) {
+ $i=0;
+ if ($this->databaseType == 'oci8po') {
+ $bv_name = ":".$i++;
+ } else {
+ $bv_name = ":".$k;
+ }
+ if (is_array($v)) {
+ // suggested by g.giunta@libero.
+ if (sizeof($v) == 2) {
+ oci_bind_by_name($stmt,$bv_name,$inputarr[$k][0],$v[1]);
+ }
+ else {
+ oci_bind_by_name($stmt,$bv_name,$inputarr[$k][0],$v[1],$v[2]);
+ }
+ } else {
+ $len = -1;
+ if ($v === ' ') {
+ $len = 1;
+ }
+ if (isset($bindarr)) { // is prepared sql, so no need to oci_bind_by_name again
+ $bindarr[$k] = $v;
+ } else { // dynamic sql, so rebind every time
+ oci_bind_by_name($stmt,$bv_name,$inputarr[$k],$len);
+ }
+ }
+ }
+ }
+
+ if (!oci_execute($stmt, OCI_DEFAULT)) {
+ oci_free_statement($stmt);
+ return false;
+ }
+
+ $ncols = oci_num_fields($stmt);
+ for ( $i = 1; $i <= $ncols; $i++ ) {
+ $cols[] = '"'.oci_field_name($stmt, $i).'"';
+ }
+ $result = false;
+
+ oci_free_statement($stmt);
+ $fields = implode(',', $cols);
+ if ($nrows <= 0) {
+ $nrows = 999999999999;
+ }
+ else {
+ $nrows += $offset;
+ }
+ $offset += 1; // in Oracle rownum starts at 1
+
+ $sql = "SELECT $hint $fields FROM".
+ "(SELECT rownum as adodb_rownum, $fields FROM".
+ " ($sql) WHERE rownum <= :adodb_nrows".
+ ") WHERE adodb_rownum >= :adodb_offset";
+ $inputarr['adodb_nrows'] = $nrows;
+ $inputarr['adodb_offset'] = $offset;
+
+ if ($secs2cache > 0) {
+ $rs = $this->CacheExecute($secs2cache, $sql,$inputarr);
+ }
+ else {
+ $rs = $this->Execute($sql, $inputarr);
+ }
+ return $rs;
+ }
+ }
+
+ /**
+ * Usage:
+ * Store BLOBs and CLOBs
+ *
+ * Example: to store $var in a blob
+ * $conn->Execute('insert into TABLE (id,ablob) values(12,empty_blob())');
+ * $conn->UpdateBlob('TABLE', 'ablob', $varHoldingBlob, 'ID=12', 'BLOB');
+ *
+ * $blobtype supports 'BLOB' and 'CLOB', but you need to change to 'empty_clob()'.
+ *
+ * to get length of LOB:
+ * select DBMS_LOB.GETLENGTH(ablob) from TABLE
+ *
+ * If you are using CURSOR_SHARING = force, it appears this will case a segfault
+ * under oracle 8.1.7.0. Run:
+ * $db->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT');
+ * before UpdateBlob() then...
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+
+ //if (strlen($val) < 4000) return $this->Execute("UPDATE $table SET $column=:blob WHERE $where",array('blob'=>$val)) != false;
+
+ switch(strtoupper($blobtype)) {
+ default: ADOConnection::outp("<b>UpdateBlob</b>: Unknown blobtype=$blobtype"); return false;
+ case 'BLOB': $type = OCI_B_BLOB; break;
+ case 'CLOB': $type = OCI_B_CLOB; break;
+ }
+
+ if ($this->databaseType == 'oci8po')
+ $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?";
+ else
+ $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob";
+
+ $desc = oci_new_descriptor($this->_connectionID, OCI_D_LOB);
+ $arr['blob'] = array($desc,-1,$type);
+ if ($this->session_sharing_force_blob) {
+ $this->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT');
+ }
+ $commit = $this->autoCommit;
+ if ($commit) {
+ $this->BeginTrans();
+ }
+ $rs = $this->_Execute($sql,$arr);
+ if ($rez = !empty($rs)) {
+ $desc->save($val);
+ }
+ $desc->free();
+ if ($commit) {
+ $this->CommitTrans();
+ }
+ if ($this->session_sharing_force_blob) {
+ $this->Execute('ALTER SESSION SET CURSOR_SHARING=FORCE');
+ }
+
+ if ($rez) {
+ $rs->Close();
+ }
+ return $rez;
+ }
+
+ /**
+ * Usage: store file pointed to by $val in a blob
+ */
+ function UpdateBlobFile($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ switch(strtoupper($blobtype)) {
+ default: ADOConnection::outp( "<b>UpdateBlob</b>: Unknown blobtype=$blobtype"); return false;
+ case 'BLOB': $type = OCI_B_BLOB; break;
+ case 'CLOB': $type = OCI_B_CLOB; break;
+ }
+
+ if ($this->databaseType == 'oci8po')
+ $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?";
+ else
+ $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob";
+
+ $desc = oci_new_descriptor($this->_connectionID, OCI_D_LOB);
+ $arr['blob'] = array($desc,-1,$type);
+
+ $this->BeginTrans();
+ $rs = ADODB_oci8::Execute($sql,$arr);
+ if ($rez = !empty($rs)) {
+ $desc->savefile($val);
+ }
+ $desc->free();
+ $this->CommitTrans();
+
+ if ($rez) {
+ $rs->Close();
+ }
+ return $rez;
+ }
+
+ /**
+ * Execute SQL
+ *
+ * @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
+ * @param [inputarr] holds the input data to bind to. Null elements will be set to null.
+ * @return RecordSet or false
+ */
+ function Execute($sql,$inputarr=false)
+ {
+ if ($this->fnExecute) {
+ $fn = $this->fnExecute;
+ $ret = $fn($this,$sql,$inputarr);
+ if (isset($ret)) {
+ return $ret;
+ }
+ }
+ if ($inputarr !== false) {
+ if (!is_array($inputarr)) {
+ $inputarr = array($inputarr);
+ }
+
+ $element0 = reset($inputarr);
+ $array2d = $this->bulkBind && is_array($element0) && !is_object(reset($element0));
+
+ # see http://phplens.com/lens/lensforum/msgs.php?id=18786
+ if ($array2d || !$this->_bindInputArray) {
+
+ # is_object check because oci8 descriptors can be passed in
+ if ($array2d && $this->_bindInputArray) {
+ if (is_string($sql)) {
+ $stmt = $this->Prepare($sql);
+ } else {
+ $stmt = $sql;
+ }
+
+ foreach($inputarr as $arr) {
+ $ret = $this->_Execute($stmt,$arr);
+ if (!$ret) {
+ return $ret;
+ }
+ }
+ return $ret;
+ } else {
+ $sqlarr = explode(':', $sql);
+ $sql = '';
+ $lastnomatch = -2;
+ #var_dump($sqlarr);echo "<hr>";var_dump($inputarr);echo"<hr>";
+ foreach($sqlarr as $k => $str) {
+ if ($k == 0) {
+ $sql = $str;
+ continue;
+ }
+ // we need $lastnomatch because of the following datetime,
+ // eg. '10:10:01', which causes code to think that there is bind param :10 and :1
+ $ok = preg_match('/^([0-9]*)/', $str, $arr);
+
+ if (!$ok) {
+ $sql .= $str;
+ } else {
+ $at = $arr[1];
+ if (isset($inputarr[$at]) || is_null($inputarr[$at])) {
+ if ((strlen($at) == strlen($str) && $k < sizeof($arr)-1)) {
+ $sql .= ':'.$str;
+ $lastnomatch = $k;
+ } else if ($lastnomatch == $k-1) {
+ $sql .= ':'.$str;
+ } else {
+ if (is_null($inputarr[$at])) {
+ $sql .= 'null';
+ }
+ else {
+ $sql .= $this->qstr($inputarr[$at]);
+ }
+ $sql .= substr($str, strlen($at));
+ }
+ } else {
+ $sql .= ':'.$str;
+ }
+ }
+ }
+ $inputarr = false;
+ }
+ }
+ $ret = $this->_Execute($sql,$inputarr);
+
+ } else {
+ $ret = $this->_Execute($sql,false);
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Example of usage:
+ * $stmt = $this->Prepare('insert into emp (empno, ename) values (:empno, :ename)');
+ */
+ function Prepare($sql,$cursor=false)
+ {
+ static $BINDNUM = 0;
+
+ $stmt = oci_parse($this->_connectionID,$sql);
+
+ if (!$stmt) {
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+ $arr = @oci_error($this->_connectionID);
+ if ($arr === false) {
+ return false;
+ }
+
+ $this->_errorMsg = $arr['message'];
+ $this->_errorCode = $arr['code'];
+ return false;
+ }
+
+ $BINDNUM += 1;
+
+ $sttype = @oci_statement_type($stmt);
+ if ($sttype == 'BEGIN' || $sttype == 'DECLARE') {
+ return array($sql,$stmt,0,$BINDNUM, ($cursor) ? oci_new_cursor($this->_connectionID) : false);
+ }
+ return array($sql,$stmt,0,$BINDNUM);
+ }
+
+ /*
+ Call an oracle stored procedure and returns a cursor variable as a recordset.
+ Concept by Robert Tuttle robert@ud.com
+
+ Example:
+ Note: we return a cursor variable in :RS2
+ $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:RS2); END;",'RS2');
+
+ $rs = $db->ExecuteCursor(
+ "BEGIN :RS2 = adodb.getdata(:VAR1); END;",
+ 'RS2',
+ array('VAR1' => 'Mr Bean'));
+
+ */
+ function ExecuteCursor($sql,$cursorName='rs',$params=false)
+ {
+ if (is_array($sql)) {
+ $stmt = $sql;
+ }
+ else $stmt = ADODB_oci8::Prepare($sql,true); # true to allocate oci_new_cursor
+
+ if (is_array($stmt) && sizeof($stmt) >= 5) {
+ $hasref = true;
+ $ignoreCur = false;
+ $this->Parameter($stmt, $ignoreCur, $cursorName, false, -1, OCI_B_CURSOR);
+ if ($params) {
+ foreach($params as $k => $v) {
+ $this->Parameter($stmt,$params[$k], $k);
+ }
+ }
+ } else
+ $hasref = false;
+
+ $rs = $this->Execute($stmt);
+ if ($rs) {
+ if ($rs->databaseType == 'array') {
+ oci_free_cursor($stmt[4]);
+ }
+ elseif ($hasref) {
+ $rs->_refcursor = $stmt[4];
+ }
+ }
+ return $rs;
+ }
+
+ /**
+ * Bind a variable -- very, very fast for executing repeated statements in oracle.
+ *
+ * Better than using
+ * for ($i = 0; $i < $max; $i++) {
+ * $p1 = ?; $p2 = ?; $p3 = ?;
+ * $this->Execute("insert into table (col0, col1, col2) values (:0, :1, :2)", array($p1,$p2,$p3));
+ * }
+ *
+ * Usage:
+ * $stmt = $DB->Prepare("insert into table (col0, col1, col2) values (:0, :1, :2)");
+ * $DB->Bind($stmt, $p1);
+ * $DB->Bind($stmt, $p2);
+ * $DB->Bind($stmt, $p3);
+ * for ($i = 0; $i < $max; $i++) {
+ * $p1 = ?; $p2 = ?; $p3 = ?;
+ * $DB->Execute($stmt);
+ * }
+ *
+ * Some timings to insert 1000 records, test table has 3 cols, and 1 index.
+ * - Time 0.6081s (1644.60 inserts/sec) with direct oci_parse/oci_execute
+ * - Time 0.6341s (1577.16 inserts/sec) with ADOdb Prepare/Bind/Execute
+ * - Time 1.5533s ( 643.77 inserts/sec) with pure SQL using Execute
+ *
+ * Now if PHP only had batch/bulk updating like Java or PL/SQL...
+ *
+ * Note that the order of parameters differs from oci_bind_by_name,
+ * because we default the names to :0, :1, :2
+ */
+ function Bind(&$stmt,&$var,$size=4000,$type=false,$name=false,$isOutput=false)
+ {
+
+ if (!is_array($stmt)) {
+ return false;
+ }
+
+ if (($type == OCI_B_CURSOR) && sizeof($stmt) >= 5) {
+ return oci_bind_by_name($stmt[1],":".$name,$stmt[4],$size,$type);
+ }
+
+ if ($name == false) {
+ if ($type !== false) {
+ $rez = oci_bind_by_name($stmt[1],":".$stmt[2],$var,$size,$type);
+ }
+ else {
+ $rez = oci_bind_by_name($stmt[1],":".$stmt[2],$var,$size); // +1 byte for null terminator
+ }
+ $stmt[2] += 1;
+ } else if (oci_lob_desc($type)) {
+ if ($this->debug) {
+ ADOConnection::outp("<b>Bind</b>: name = $name");
+ }
+ //we have to create a new Descriptor here
+ $numlob = count($this->_refLOBs);
+ $this->_refLOBs[$numlob]['LOB'] = oci_new_descriptor($this->_connectionID, oci_lob_desc($type));
+ $this->_refLOBs[$numlob]['TYPE'] = $isOutput;
+
+ $tmp = $this->_refLOBs[$numlob]['LOB'];
+ $rez = oci_bind_by_name($stmt[1], ":".$name, $tmp, -1, $type);
+ if ($this->debug) {
+ ADOConnection::outp("<b>Bind</b>: descriptor has been allocated, var (".$name.") binded");
+ }
+
+ // if type is input then write data to lob now
+ if ($isOutput == false) {
+ $var = $this->BlobEncode($var);
+ $tmp->WriteTemporary($var);
+ $this->_refLOBs[$numlob]['VAR'] = &$var;
+ if ($this->debug) {
+ ADOConnection::outp("<b>Bind</b>: LOB has been written to temp");
+ }
+ } else {
+ $this->_refLOBs[$numlob]['VAR'] = &$var;
+ }
+ $rez = $tmp;
+ } else {
+ if ($this->debug)
+ ADOConnection::outp("<b>Bind</b>: name = $name");
+
+ if ($type !== false) {
+ $rez = oci_bind_by_name($stmt[1],":".$name,$var,$size,$type);
+ }
+ else {
+ $rez = oci_bind_by_name($stmt[1],":".$name,$var,$size); // +1 byte for null terminator
+ }
+ }
+
+ return $rez;
+ }
+
+ function Param($name,$type='C')
+ {
+ return ':'.$name;
+ }
+
+ /**
+ * Usage:
+ * $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
+ * $db->Parameter($stmt,$id,'myid');
+ * $db->Parameter($stmt,$group,'group');
+ * $db->Execute($stmt);
+ *
+ * @param $stmt Statement returned by Prepare() or PrepareSP().
+ * @param $var PHP variable to bind to
+ * @param $name Name of stored procedure variable name to bind to.
+ * @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8.
+ * @param [$maxLen] Holds an maximum length of the variable.
+ * @param [$type] The data type of $var. Legal values depend on driver.
+ *
+ * @link http://php.net/oci_bind_by_name
+ */
+ function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false)
+ {
+ if ($this->debug) {
+ $prefix = ($isOutput) ? 'Out' : 'In';
+ $ztype = (empty($type)) ? 'false' : $type;
+ ADOConnection::outp( "{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);");
+ }
+ return $this->Bind($stmt,$var,$maxLen,$type,$name,$isOutput);
+ }
+
+ /**
+ * returns query ID if successful, otherwise false
+ * this version supports:
+ *
+ * 1. $db->execute('select * from table');
+ *
+ * 2. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)');
+ * $db->execute($prepared_statement, array(1,2,3));
+ *
+ * 3. $db->execute('insert into table (a,b,c) values (:a,:b,:c)',array('a'=>1,'b'=>2,'c'=>3));
+ *
+ * 4. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)');
+ * $db->bind($stmt,1); $db->bind($stmt,2); $db->bind($stmt,3);
+ * $db->execute($stmt);
+ */
+ function _query($sql,$inputarr=false)
+ {
+ if (is_array($sql)) { // is prepared sql
+ $stmt = $sql[1];
+
+ // we try to bind to permanent array, so that oci_bind_by_name is persistent
+ // and carried out once only - note that max array element size is 4000 chars
+ if (is_array($inputarr)) {
+ $bindpos = $sql[3];
+ if (isset($this->_bind[$bindpos])) {
+ // all tied up already
+ $bindarr = $this->_bind[$bindpos];
+ } else {
+ // one statement to bind them all
+ $bindarr = array();
+ foreach($inputarr as $k => $v) {
+ $bindarr[$k] = $v;
+ oci_bind_by_name($stmt,":$k",$bindarr[$k],is_string($v) && strlen($v)>4000 ? -1 : 4000);
+ }
+ $this->_bind[$bindpos] = $bindarr;
+ }
+ }
+ } else {
+ $stmt=oci_parse($this->_connectionID,$sql);
+ }
+
+ $this->_stmt = $stmt;
+ if (!$stmt) {
+ return false;
+ }
+
+ if (defined('ADODB_PREFETCH_ROWS')) {
+ @oci_set_prefetch($stmt,ADODB_PREFETCH_ROWS);
+ }
+
+ if (is_array($inputarr)) {
+ foreach($inputarr as $k => $v) {
+ if (is_array($v)) {
+ // suggested by g.giunta@libero.
+ if (sizeof($v) == 2) {
+ oci_bind_by_name($stmt,":$k",$inputarr[$k][0],$v[1]);
+ }
+ else {
+ oci_bind_by_name($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]);
+ }
+
+ if ($this->debug==99) {
+ if (is_object($v[0])) {
+ echo "name=:$k",' len='.$v[1],' type='.$v[2],'<br>';
+ }
+ else {
+ echo "name=:$k",' var='.$inputarr[$k][0],' len='.$v[1],' type='.$v[2],'<br>';
+ }
+
+ }
+ } else {
+ $len = -1;
+ if ($v === ' ') {
+ $len = 1;
+ }
+ if (isset($bindarr)) { // is prepared sql, so no need to oci_bind_by_name again
+ $bindarr[$k] = $v;
+ } else { // dynamic sql, so rebind every time
+ oci_bind_by_name($stmt,":$k",$inputarr[$k],$len);
+ }
+ }
+ }
+ }
+
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+ if (oci_execute($stmt,$this->_commit)) {
+
+ if (count($this -> _refLOBs) > 0) {
+
+ foreach ($this -> _refLOBs as $key => $value) {
+ if ($this -> _refLOBs[$key]['TYPE'] == true) {
+ $tmp = $this -> _refLOBs[$key]['LOB'] -> load();
+ if ($this -> debug) {
+ ADOConnection::outp("<b>OUT LOB</b>: LOB has been loaded. <br>");
+ }
+ //$_GLOBALS[$this -> _refLOBs[$key]['VAR']] = $tmp;
+ $this -> _refLOBs[$key]['VAR'] = $tmp;
+ } else {
+ $this->_refLOBs[$key]['LOB']->save($this->_refLOBs[$key]['VAR']);
+ $this -> _refLOBs[$key]['LOB']->free();
+ unset($this -> _refLOBs[$key]);
+ if ($this->debug) {
+ ADOConnection::outp("<b>IN LOB</b>: LOB has been saved. <br>");
+ }
+ }
+ }
+ }
+
+ switch (@oci_statement_type($stmt)) {
+ case "SELECT":
+ return $stmt;
+
+ case 'DECLARE':
+ case "BEGIN":
+ if (is_array($sql) && !empty($sql[4])) {
+ $cursor = $sql[4];
+ if (is_resource($cursor)) {
+ $ok = oci_execute($cursor);
+ return $cursor;
+ }
+ return $stmt;
+ } else {
+ if (is_resource($stmt)) {
+ oci_free_statement($stmt);
+ return true;
+ }
+ return $stmt;
+ }
+ break;
+ default :
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // From Oracle Whitepaper: PHP Scalability and High Availability
+ function IsConnectionError($err)
+ {
+ switch($err) {
+ case 378: /* buffer pool param incorrect */
+ case 602: /* core dump */
+ case 603: /* fatal error */
+ case 609: /* attach failed */
+ case 1012: /* not logged in */
+ case 1033: /* init or shutdown in progress */
+ case 1043: /* Oracle not available */
+ case 1089: /* immediate shutdown in progress */
+ case 1090: /* shutdown in progress */
+ case 1092: /* instance terminated */
+ case 3113: /* disconnect */
+ case 3114: /* not connected */
+ case 3122: /* closing window */
+ case 3135: /* lost contact */
+ case 12153: /* TNS: not connected */
+ case 27146: /* fatal or instance terminated */
+ case 28511: /* Lost RPC */
+ return true;
+ }
+ return false;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if (!$this->_connectionID) {
+ return;
+ }
+
+
+ if (!$this->autoCommit) {
+ oci_rollback($this->_connectionID);
+ }
+ if (count($this->_refLOBs) > 0) {
+ foreach ($this ->_refLOBs as $key => $value) {
+ $this->_refLOBs[$key]['LOB']->free();
+ unset($this->_refLOBs[$key]);
+ }
+ }
+ oci_close($this->_connectionID);
+
+ $this->_stmt = false;
+ $this->_connectionID = false;
+ }
+
+ function MetaPrimaryKeys($table, $owner=false,$internalKey=false)
+ {
+ if ($internalKey) {
+ return array('ROWID');
+ }
+
+ // tested with oracle 8.1.7
+ $table = strtoupper($table);
+ if ($owner) {
+ $owner_clause = "AND ((a.OWNER = b.OWNER) AND (a.OWNER = UPPER('$owner')))";
+ $ptab = 'ALL_';
+ } else {
+ $owner_clause = '';
+ $ptab = 'USER_';
+ }
+ $sql = "
+SELECT /*+ RULE */ distinct b.column_name
+ FROM {$ptab}CONSTRAINTS a
+ , {$ptab}CONS_COLUMNS b
+ WHERE ( UPPER(b.table_name) = ('$table'))
+ AND (UPPER(a.table_name) = ('$table') and a.constraint_type = 'P')
+ $owner_clause
+ AND (a.constraint_name = b.constraint_name)";
+
+ $rs = $this->Execute($sql);
+ if ($rs && !$rs->EOF) {
+ $arr = $rs->GetArray();
+ $a = array();
+ foreach($arr as $v) {
+ $a[] = reset($v);
+ }
+ return $a;
+ }
+ else return false;
+ }
+
+ /**
+ * returns assoc array where keys are tables, and values are foreign keys
+ *
+ * @param str $table
+ * @param str $owner [optional][default=NULL]
+ * @param bool $upper [optional][discarded]
+ * @return mixed[] Array of foreign key information
+ *
+ * @link http://gis.mit.edu/classes/11.521/sqlnotes/referential_integrity.html
+ */
+ function MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $table = $this->qstr(strtoupper($table));
+ if (!$owner) {
+ $owner = $this->user;
+ $tabp = 'user_';
+ } else
+ $tabp = 'all_';
+
+ $owner = ' and owner='.$this->qstr(strtoupper($owner));
+
+ $sql =
+"select constraint_name,r_owner,r_constraint_name
+ from {$tabp}constraints
+ where constraint_type = 'R' and table_name = $table $owner";
+
+ $constraints = $this->GetArray($sql);
+ $arr = false;
+ foreach($constraints as $constr) {
+ $cons = $this->qstr($constr[0]);
+ $rowner = $this->qstr($constr[1]);
+ $rcons = $this->qstr($constr[2]);
+ $cols = $this->GetArray("select column_name from {$tabp}cons_columns where constraint_name=$cons $owner order by position");
+ $tabcol = $this->GetArray("select table_name,column_name from {$tabp}cons_columns where owner=$rowner and constraint_name=$rcons order by position");
+
+ if ($cols && $tabcol)
+ for ($i=0, $max=sizeof($cols); $i < $max; $i++) {
+ $arr[$tabcol[$i][0]] = $cols[$i][0].'='.$tabcol[$i][1];
+ }
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $arr;
+ }
+
+
+ function CharMax()
+ {
+ return 4000;
+ }
+
+ function TextMax()
+ {
+ return 4000;
+ }
+
+ /**
+ * Quotes a string.
+ * An example is $db->qstr("Don't bother",magic_quotes_runtime());
+ *
+ * @param string $s the string to quote
+ * @param bool $magic_quotes if $s is GET/POST var, set to get_magic_quotes_gpc().
+ * This undoes the stupidity of magic quotes for GPC.
+ *
+ * @return string quoted string to be sent back to database
+ */
+ function qstr($s,$magic_quotes=false)
+ {
+ //$nofixquotes=false;
+
+ if ($this->noNullStrings && strlen($s)==0) {
+ $s = ' ';
+ }
+ if (!$magic_quotes) {
+ if ($this->replaceQuote[0] == '\\'){
+ $s = str_replace('\\','\\\\',$s);
+ }
+ return "'".str_replace("'",$this->replaceQuote,$s)."'";
+ }
+
+ // undo magic quotes for " unless sybase is on
+ if (!ini_get('magic_quotes_sybase')) {
+ $s = str_replace('\\"','"',$s);
+ $s = str_replace('\\\\','\\',$s);
+ return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
+ } else {
+ return "'".$s."'";
+ }
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_oci8 extends ADORecordSet {
+
+ var $databaseType = 'oci8';
+ var $bind=false;
+ var $_fieldobjs;
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch ($mode) {
+ case ADODB_FETCH_ASSOC:
+ $this->fetchMode = OCI_ASSOC;
+ break;
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ $this->fetchMode = OCI_NUM + OCI_ASSOC;
+ break;
+ case ADODB_FETCH_NUM:
+ default:
+ $this->fetchMode = OCI_NUM;
+ break;
+ }
+ $this->fetchMode += OCI_RETURN_NULLS + OCI_RETURN_LOBS;
+ $this->adodbFetchMode = $mode;
+ $this->_queryID = $queryID;
+ }
+
+ /**
+ * Overrides the core destructor method as that causes problems here
+ *
+ * @return void
+ */
+ function __destruct() {}
+
+ function Init()
+ {
+ if ($this->_inited) {
+ return;
+ }
+
+ $this->_inited = true;
+ if ($this->_queryID) {
+
+ $this->_currentRow = 0;
+ @$this->_initrs();
+ if ($this->_numOfFields) {
+ $this->EOF = !$this->_fetch();
+ }
+ else $this->EOF = true;
+
+ /*
+ // based on idea by Gaetano Giunta to detect unusual oracle errors
+ // see http://phplens.com/lens/lensforum/msgs.php?id=6771
+ $err = oci_error($this->_queryID);
+ if ($err && $this->connection->debug) {
+ ADOConnection::outp($err);
+ }
+ */
+
+ if (!is_array($this->fields)) {
+ $this->_numOfRows = 0;
+ $this->fields = array();
+ }
+ } else {
+ $this->fields = array();
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ $this->EOF = true;
+ }
+ }
+
+ function _initrs()
+ {
+ $this->_numOfRows = -1;
+ $this->_numOfFields = oci_num_fields($this->_queryID);
+ if ($this->_numOfFields>0) {
+ $this->_fieldobjs = array();
+ $max = $this->_numOfFields;
+ for ($i=0;$i<$max; $i++) $this->_fieldobjs[] = $this->_FetchField($i);
+ }
+ }
+
+ /**
+ * Get column information in the Recordset object.
+ * fetchField() can be used in order to obtain information about fields
+ * in a certain query result. If the field offset isn't specified, the next
+ * field that wasn't yet retrieved by fetchField() is retrieved
+ *
+ * @return object containing field information
+ */
+ function _FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $fieldOffset += 1;
+ $fld->name =oci_field_name($this->_queryID, $fieldOffset);
+ if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) {
+ $fld->name = strtolower($fld->name);
+ }
+ $fld->type = oci_field_type($this->_queryID, $fieldOffset);
+ $fld->max_length = oci_field_size($this->_queryID, $fieldOffset);
+
+ switch($fld->type) {
+ case 'NUMBER':
+ $p = oci_field_precision($this->_queryID, $fieldOffset);
+ $sc = oci_field_scale($this->_queryID, $fieldOffset);
+ if ($p != 0 && $sc == 0) {
+ $fld->type = 'INT';
+ }
+ $fld->scale = $p;
+ break;
+
+ case 'CLOB':
+ case 'NCLOB':
+ case 'BLOB':
+ $fld->max_length = -1;
+ break;
+ }
+ return $fld;
+ }
+
+ /* For some reason, oci_field_name fails when called after _initrs() so we cache it */
+ function FetchField($fieldOffset = -1)
+ {
+ return $this->_fieldobjs[$fieldOffset];
+ }
+
+
+ function MoveNext()
+ {
+ if ($this->fields = @oci_fetch_array($this->_queryID,$this->fetchMode)) {
+ $this->_currentRow += 1;
+ $this->_updatefields();
+ return true;
+ }
+ if (!$this->EOF) {
+ $this->_currentRow += 1;
+ $this->EOF = true;
+ }
+ return false;
+ }
+
+ // Optimize SelectLimit() by using oci_fetch()
+ function GetArrayLimit($nrows,$offset=-1)
+ {
+ if ($offset <= 0) {
+ $arr = $this->GetArray($nrows);
+ return $arr;
+ }
+ $arr = array();
+ for ($i=1; $i < $offset; $i++) {
+ if (!@oci_fetch($this->_queryID)) {
+ return $arr;
+ }
+ }
+
+ if (!$this->fields = @oci_fetch_array($this->_queryID,$this->fetchMode)) {
+ return $arr;
+ }
+ $this->_updatefields();
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+
+ // Use associative array to get fields array
+ function Fields($colname)
+ {
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ function _fetch()
+ {
+ $this->fields = @oci_fetch_array($this->_queryID,$this->fetchMode);
+ $this->_updatefields();
+
+ return $this->fields;
+ }
+
+ /**
+ * close() only needs to be called if you are worried about using too much
+ * memory while your script is running. All associated result memory for the
+ * specified result identifier will automatically be freed.
+ */
+ function _close()
+ {
+ if ($this->connection->_stmt === $this->_queryID) {
+ $this->connection->_stmt = false;
+ }
+ if (!empty($this->_refcursor)) {
+ oci_free_cursor($this->_refcursor);
+ $this->_refcursor = false;
+ }
+ @oci_free_statement($this->_queryID);
+ $this->_queryID = false;
+ }
+
+ /**
+ * not the fastest implementation - quick and dirty - jlim
+ * for best performance, use the actual $rs->MetaType().
+ *
+ * @param mixed $t
+ * @param int $len [optional] Length of blobsize
+ * @param bool $fieldobj [optional][discarded]
+ * @return str The metatype of the field
+ */
+ function MetaType($t, $len=-1, $fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ switch (strtoupper($t)) {
+ case 'VARCHAR':
+ case 'VARCHAR2':
+ case 'CHAR':
+ case 'VARBINARY':
+ case 'BINARY':
+ case 'NCHAR':
+ case 'NVARCHAR':
+ case 'NVARCHAR2':
+ if ($len <= $this->blobSize) {
+ return 'C';
+ }
+
+ case 'NCLOB':
+ case 'LONG':
+ case 'LONG VARCHAR':
+ case 'CLOB':
+ return 'X';
+
+ case 'LONG RAW':
+ case 'LONG VARBINARY':
+ case 'BLOB':
+ return 'B';
+
+ case 'DATE':
+ return ($this->connection->datetime) ? 'T' : 'D';
+
+
+ case 'TIMESTAMP': return 'T';
+
+ case 'INT':
+ case 'SMALLINT':
+ case 'INTEGER':
+ return 'I';
+
+ default:
+ return 'N';
+ }
+ }
+}
+
+class ADORecordSet_ext_oci8 extends ADORecordSet_oci8 {
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID, $mode);
+ }
+
+ function MoveNext()
+ {
+ return adodb_movenext($this);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-oci805.inc.php b/vendor/adodb/adodb-php/drivers/adodb-oci805.inc.php
new file mode 100644
index 0000000..b729ab8
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-oci805.inc.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * Oracle 8.0.5 driver
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php');
+
+class ADODB_oci805 extends ADODB_oci8 {
+ var $databaseType = "oci805";
+ var $connectSID = true;
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ // seems that oracle only supports 1 hint comment in 8i
+ if (strpos($sql,'/*+') !== false)
+ $sql = str_replace('/*+ ','/*+FIRST_ROWS ',$sql);
+ else
+ $sql = preg_replace('/^[ \t\n]*select/i','SELECT /*+FIRST_ROWS*/',$sql);
+
+ /*
+ The following is only available from 8.1.5 because order by in inline views not
+ available before then...
+ http://www.jlcomp.demon.co.uk/faq/top_sql.html
+ if ($nrows > 0) {
+ if ($offset > 0) $nrows += $offset;
+ $sql = "select * from ($sql) where rownum <= $nrows";
+ $nrows = -1;
+ }
+ */
+
+ return ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ }
+}
+
+class ADORecordset_oci805 extends ADORecordset_oci8 {
+ var $databaseType = "oci805";
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-oci8po.inc.php b/vendor/adodb/adodb-php/drivers/adodb-oci8po.inc.php
new file mode 100644
index 0000000..7687760
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-oci8po.inc.php
@@ -0,0 +1,286 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim. All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ Portable version of oci8 driver, to make it more similar to other database drivers.
+ The main differences are
+
+ 1. that the OCI_ASSOC names are in lowercase instead of uppercase.
+ 2. bind variables are mapped using ? instead of :<bindvar>
+
+ Should some emulation of RecordCount() be implemented?
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php');
+
+class ADODB_oci8po extends ADODB_oci8 {
+ var $databaseType = 'oci8po';
+ var $dataProvider = 'oci8';
+ var $metaColumnsSQL = "select lower(cname),coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net
+ var $metaTablesSQL = "select lower(table_name),table_type from cat where table_type in ('TABLE','VIEW')";
+
+ function __construct()
+ {
+ $this->_hasOCIFetchStatement = ADODB_PHPVER >= 0x4200;
+ # oci8po does not support adodb extension: adodb_movenext()
+ }
+
+ function Param($name,$type='C')
+ {
+ return '?';
+ }
+
+ function Prepare($sql,$cursor=false)
+ {
+ $sqlarr = explode('?',$sql);
+ $sql = $sqlarr[0];
+ for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) {
+ $sql .= ':'.($i-1) . $sqlarr[$i];
+ }
+ return ADODB_oci8::Prepare($sql,$cursor);
+ }
+
+ function Execute($sql,$inputarr=false)
+ {
+ return ADOConnection::Execute($sql,$inputarr);
+ }
+
+ /**
+ * The optimizations performed by ADODB_oci8::SelectLimit() are not
+ * compatible with the oci8po driver, so we rely on the slower method
+ * from the base class.
+ * We can't properly handle prepared statements either due to preprocessing
+ * of query parameters, so we treat them as regular SQL statements.
+ */
+ function SelectLimit($sql, $nrows=-1, $offset=-1, $inputarr=false, $secs2cache=0)
+ {
+ if(is_array($sql)) {
+// $sql = $sql[0];
+ }
+ return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+ }
+
+ // emulate handling of parameters ? ?, replacing with :bind0 :bind1
+ function _query($sql,$inputarr=false)
+ {
+ if (is_array($inputarr)) {
+ $i = 0;
+ if (is_array($sql)) {
+ foreach($inputarr as $v) {
+ $arr['bind'.$i++] = $v;
+ }
+ } else {
+ $sql = $this->extractBinds($sql,$inputarr);
+ }
+ }
+ return ADODB_oci8::_query($sql,$inputarr);
+ }
+ /**
+ * Replaces compatibility bind markers with oracle ones and returns a
+ * valid sql statement
+ *
+ * This replaces a regexp based section of code that has been subject
+ * to numerous tweaks, as more extreme test cases have appeared. This
+ * is now done this like this to help maintainability and avoid the
+ * need to rely on regexp experienced maintainers
+ *
+ * @param string $sql The sql statement
+ * @param string[] $inputarr The bind array
+ *
+ * @return string The modified statement
+ */
+ final private function extractBinds($sql,$inputarr)
+ {
+ $inString = false;
+ $escaped = 0;
+ $sqlLength = strlen($sql) - 1;
+ $newSql = '';
+ $bindCount = 0;
+
+ /*
+ * inputarr is the passed in bind list, which is associative, but
+ * we only want the keys here
+ */
+ $inputKeys = array_keys($inputarr);
+
+
+ for ($i=0;$i<=$sqlLength;$i++)
+ {
+ /*
+ * find the next character of the string
+ */
+ $c = $sql{$i};
+
+ if ($c == "'" && !$inString && $escaped==0)
+ /*
+ * Found the start of a string inside the statement
+ */
+ $inString = true;
+ elseif ($c == "\\" && $escaped==0)
+ /*
+ * The next character will be escaped
+ */
+ $escaped = 1;
+ elseif ($c == "'" && $inString && $escaped==0)
+ /*
+ * We found the end of the string
+ */
+ $inString = false;
+
+ if ($escaped == 2)
+ $escaped = 0;
+
+ if ($escaped==0 && !$inString && $c == '?')
+ /*
+ * We found a bind symbol, replace it with the oracle equivalent
+ */
+ $newSql .= ':' . $inputKeys[$bindCount++];
+ else
+ /*
+ * Add the current character the pile
+ */
+ $newSql .= $c;
+
+ if ($escaped == 1)
+ /*
+ * We have just found an escape character, make sure we ignore the
+ * next one that comes along, it might be a ' character
+ */
+ $escaped = 2;
+ }
+
+ return $newSql;
+
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_oci8po extends ADORecordset_oci8 {
+
+ var $databaseType = 'oci8po';
+
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ function Fields($colname)
+ {
+ if ($this->fetchMode & OCI_ASSOC) return $this->fields[$colname];
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ // lowercase field names...
+ function _FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $fieldOffset += 1;
+ $fld->name = OCIcolumnname($this->_queryID, $fieldOffset);
+ if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) {
+ $fld->name = strtolower($fld->name);
+ }
+ $fld->type = OCIcolumntype($this->_queryID, $fieldOffset);
+ $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset);
+ if ($fld->type == 'NUMBER') {
+ $sc = OCIColumnScale($this->_queryID, $fieldOffset);
+ if ($sc == 0) {
+ $fld->type = 'INT';
+ }
+ }
+ return $fld;
+ }
+
+ // 10% speedup to move MoveNext to child class
+ function MoveNext()
+ {
+ $ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
+ if($ret !== false) {
+ global $ADODB_ANSI_PADDING_OFF;
+ $this->fields = $ret;
+ $this->_currentRow++;
+ $this->_updatefields();
+
+ if (!empty($ADODB_ANSI_PADDING_OFF)) {
+ foreach($this->fields as $k => $v) {
+ if (is_string($v)) $this->fields[$k] = rtrim($v);
+ }
+ }
+ return true;
+ }
+ if (!$this->EOF) {
+ $this->EOF = true;
+ $this->_currentRow++;
+ }
+ return false;
+ }
+
+ /* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */
+ function GetArrayLimit($nrows,$offset=-1)
+ {
+ if ($offset <= 0) {
+ $arr = $this->GetArray($nrows);
+ return $arr;
+ }
+ for ($i=1; $i < $offset; $i++)
+ if (!@OCIFetch($this->_queryID)) {
+ $arr = array();
+ return $arr;
+ }
+ $ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
+ if ($ret === false) {
+ $arr = array();
+ return $arr;
+ }
+ $this->fields = $ret;
+ $this->_updatefields();
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+ function _fetch()
+ {
+ global $ADODB_ANSI_PADDING_OFF;
+
+ $ret = @oci_fetch_array($this->_queryID,$this->fetchMode);
+ if ($ret) {
+ $this->fields = $ret;
+ $this->_updatefields();
+
+ if (!empty($ADODB_ANSI_PADDING_OFF)) {
+ foreach($this->fields as $k => $v) {
+ if (is_string($v)) $this->fields[$k] = rtrim($v);
+ }
+ }
+ }
+ return $ret !== false;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-oci8quercus.inc.php b/vendor/adodb/adodb-php/drivers/adodb-oci8quercus.inc.php
new file mode 100644
index 0000000..62601b7
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-oci8quercus.inc.php
@@ -0,0 +1,89 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim. All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ Portable version of oci8 driver, to make it more similar to other database drivers.
+ The main differences are
+
+ 1. that the OCI_ASSOC names are in lowercase instead of uppercase.
+ 2. bind variables are mapped using ? instead of :<bindvar>
+
+ Should some emulation of RecordCount() be implemented?
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php');
+
+class ADODB_oci8quercus extends ADODB_oci8 {
+ var $databaseType = 'oci8quercus';
+ var $dataProvider = 'oci8';
+
+ function __construct()
+ {
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_oci8quercus extends ADORecordset_oci8 {
+
+ var $databaseType = 'oci8quercus';
+
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ function _FetchField($fieldOffset = -1)
+ {
+ global $QUERCUS;
+ $fld = new ADOFieldObject;
+
+ if (!empty($QUERCUS)) {
+ $fld->name = oci_field_name($this->_queryID, $fieldOffset);
+ $fld->type = oci_field_type($this->_queryID, $fieldOffset);
+ $fld->max_length = oci_field_size($this->_queryID, $fieldOffset);
+
+ //if ($fld->name == 'VAL6_NUM_12_4') $fld->type = 'NUMBER';
+ switch($fld->type) {
+ case 'string': $fld->type = 'VARCHAR'; break;
+ case 'real': $fld->type = 'NUMBER'; break;
+ }
+ } else {
+ $fieldOffset += 1;
+ $fld->name = oci_field_name($this->_queryID, $fieldOffset);
+ $fld->type = oci_field_type($this->_queryID, $fieldOffset);
+ $fld->max_length = oci_field_size($this->_queryID, $fieldOffset);
+ }
+ switch($fld->type) {
+ case 'NUMBER':
+ $p = oci_field_precision($this->_queryID, $fieldOffset);
+ $sc = oci_field_scale($this->_queryID, $fieldOffset);
+ if ($p != 0 && $sc == 0) $fld->type = 'INT';
+ $fld->scale = $p;
+ break;
+
+ case 'CLOB':
+ case 'NCLOB':
+ case 'BLOB':
+ $fld->max_length = -1;
+ break;
+ }
+
+ return $fld;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbc.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbc.inc.php
new file mode 100644
index 0000000..8a7e370
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbc.inc.php
@@ -0,0 +1,735 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Requires ODBC. Works on Windows and Unix.
+*/
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+ define("_ADODB_ODBC_LAYER", 2 );
+
+/*--------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------*/
+
+
+class ADODB_odbc extends ADOConnection {
+ var $databaseType = "odbc";
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $dataProvider = "odbc";
+ var $hasAffectedRows = true;
+ var $binmode = ODBC_BINMODE_RETURN;
+ var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
+ // breaking backward-compat
+ //var $longreadlen = 8000; // default number of chars to return for a Blob/Long field
+ var $_bindInputArray = false;
+ var $curmode = SQL_CUR_USE_DRIVER; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L
+ var $_genSeqSQL = "create table %s (id integer)";
+ var $_autocommit = true;
+ var $_haserrorfunctions = true;
+ var $_has_stupid_odbc_fetch_api_change = true;
+ var $_lastAffectedRows = 0;
+ var $uCaseTables = true; // for meta* functions, uppercase table names
+
+ function __construct()
+ {
+ $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050;
+ $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200;
+ }
+
+ // returns true or false
+ function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('odbc_connect')) return null;
+
+ if (!empty($argDatabasename) && stristr($argDSN, 'Database=') === false) {
+ $argDSN = trim($argDSN);
+ $endDSN = substr($argDSN, strlen($argDSN) - 1);
+ if ($endDSN != ';') $argDSN .= ';';
+ $argDSN .= 'Database='.$argDatabasename;
+ }
+
+ $last_php_error = $this->resetLastError();
+ if ($this->curmode === false) $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword);
+ else $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword,$this->curmode);
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ return $this->_connectionID != false;
+ }
+
+ // returns true or false
+ function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('odbc_connect')) return null;
+
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = '';
+ if ($this->debug && $argDatabasename) {
+ ADOConnection::outp("For odbc PConnect(), $argDatabasename is not used. Place dsn in 1st parameter.");
+ }
+ // print "dsn=$argDSN u=$argUsername p=$argPassword<br>"; flush();
+ if ($this->curmode === false) $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword);
+ else $this->_connectionID = odbc_pconnect($argDSN,$argUsername,$argPassword,$this->curmode);
+
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ if ($this->_connectionID && $this->autoRollback) @odbc_rollback($this->_connectionID);
+ if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+
+ return $this->_connectionID != false;
+ }
+
+
+ function ServerInfo()
+ {
+
+ if (!empty($this->host) && ADODB_PHPVER >= 0x4300) {
+ $dsn = strtoupper($this->host);
+ $first = true;
+ $found = false;
+
+ if (!function_exists('odbc_data_source')) return false;
+
+ while(true) {
+
+ $rez = @odbc_data_source($this->_connectionID,
+ $first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT);
+ $first = false;
+ if (!is_array($rez)) break;
+ if (strtoupper($rez['server']) == $dsn) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) return ADOConnection::ServerInfo();
+ if (!isset($rez['version'])) $rez['version'] = '';
+ return $rez;
+ } else {
+ return ADOConnection::ServerInfo();
+ }
+ }
+
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ if (empty($this->_genSeqSQL)) return false;
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) return false;
+ $start -= 1;
+ return $this->Execute("insert into $seqname values($start)");
+ }
+
+ var $_dropSeqSQL = 'drop table %s';
+ function DropSequence($seqname = 'adodbseq')
+ {
+ if (empty($this->_dropSeqSQL)) return false;
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ /*
+ This algorithm is not very efficient, but works even if table locking
+ is not available.
+
+ Will return false if unable to generate an ID after $MAXLOOPS attempts.
+ */
+ function GenID($seq='adodbseq',$start=1)
+ {
+ // if you have to modify the parameter below, your database is overloaded,
+ // or you need to implement generation of id's yourself!
+ $MAXLOOPS = 100;
+ //$this->debug=1;
+ while (--$MAXLOOPS>=0) {
+ $num = $this->GetOne("select id from $seq");
+ if ($num === false) {
+ $this->Execute(sprintf($this->_genSeqSQL ,$seq));
+ $start -= 1;
+ $num = '0';
+ $ok = $this->Execute("insert into $seq values($start)");
+ if (!$ok) return false;
+ }
+ $this->Execute("update $seq set id=id+1 where id=$num");
+
+ if ($this->affected_rows() > 0) {
+ $num += 1;
+ $this->genID = $num;
+ return $num;
+ } elseif ($this->affected_rows() == 0) {
+ // some drivers do not return a valid value => try with another method
+ $value = $this->GetOne("select id from $seq");
+ if ($value == $num + 1) {
+ return $value;
+ }
+ }
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
+ }
+ return false;
+ }
+
+
+ function ErrorMsg()
+ {
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+ if (empty($this->_connectionID)) return @odbc_errormsg();
+ return @odbc_errormsg($this->_connectionID);
+ } else return ADOConnection::ErrorMsg();
+ }
+
+ function ErrorNo()
+ {
+
+ if ($this->_haserrorfunctions) {
+ if ($this->_errorCode !== false) {
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode;
+ }
+
+ if (empty($this->_connectionID)) $e = @odbc_error();
+ else $e = @odbc_error($this->_connectionID);
+
+ // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
+ // so we check and patch
+ if (strlen($e)<=2) return 0;
+ return $e;
+ } else return ADOConnection::ErrorNo();
+ }
+
+
+
+ function BeginTrans()
+ {
+ if (!$this->hasTransactions) return false;
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->_autocommit = false;
+ return odbc_autocommit($this->_connectionID,false);
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = odbc_commit($this->_connectionID);
+ odbc_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->_autocommit = true;
+ $ret = odbc_rollback($this->_connectionID);
+ odbc_autocommit($this->_connectionID,true);
+ return $ret;
+ }
+
+ function MetaPrimaryKeys($table,$owner=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = @odbc_primarykeys($this->_connectionID,'',$schema,$table);
+
+ if (!$qid) {
+ $ADODB_FETCH_MODE = $savem;
+ return false;
+ }
+ $rs = new ADORecordSet_odbc($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return false;
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+
+ $arr = $rs->GetArray();
+ $rs->Close();
+ //print_r($arr);
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($arr[$i][3]) $arr2[] = $arr[$i][3];
+ }
+ return $arr2;
+ }
+
+
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = odbc_tables($this->_connectionID);
+
+ $rs = new ADORecordSet_odbc($qid);
+
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) {
+ $false = false;
+ return $false;
+ }
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+
+ $arr = $rs->GetArray();
+ //print_r($arr);
+
+ $rs->Close();
+ $arr2 = array();
+
+ if ($ttype) {
+ $isview = strncmp($ttype,'V',1) === 0;
+ }
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if (!$arr[$i][2]) continue;
+ $type = $arr[$i][3];
+ if ($ttype) {
+ if ($isview) {
+ if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2];
+ } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2];
+ } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2];
+ }
+ return $arr2;
+ }
+
+/*
+See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcdatetime_data_type_changes.asp
+/ SQL data type codes /
+#define SQL_UNKNOWN_TYPE 0
+#define SQL_CHAR 1
+#define SQL_NUMERIC 2
+#define SQL_DECIMAL 3
+#define SQL_INTEGER 4
+#define SQL_SMALLINT 5
+#define SQL_FLOAT 6
+#define SQL_REAL 7
+#define SQL_DOUBLE 8
+#if (ODBCVER >= 0x0300)
+#define SQL_DATETIME 9
+#endif
+#define SQL_VARCHAR 12
+
+
+/ One-parameter shortcuts for date/time data types /
+#if (ODBCVER >= 0x0300)
+#define SQL_TYPE_DATE 91
+#define SQL_TYPE_TIME 92
+#define SQL_TYPE_TIMESTAMP 93
+
+#define SQL_UNICODE (-95)
+#define SQL_UNICODE_VARCHAR (-96)
+#define SQL_UNICODE_LONGVARCHAR (-97)
+*/
+ function ODBCTypes($t)
+ {
+ switch ((integer)$t) {
+ case 1:
+ case 12:
+ case 0:
+ case -95:
+ case -96:
+ return 'C';
+ case -97:
+ case -1: //text
+ return 'X';
+ case -4: //image
+ return 'B';
+
+ case 9:
+ case 91:
+ return 'D';
+
+ case 10:
+ case 11:
+ case 92:
+ case 93:
+ return 'T';
+
+ case 4:
+ case 5:
+ case -6:
+ return 'I';
+
+ case -11: // uniqidentifier
+ return 'R';
+ case -7: //bit
+ return 'L';
+
+ default:
+ return 'N';
+ }
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ if ($this->uCaseTables) $table = strtoupper($table);
+ $schema = '';
+ $this->_findschema($table,$schema);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ /*if (false) { // after testing, confirmed that the following does not work becoz of a bug
+ $qid2 = odbc_tables($this->_connectionID);
+ $rs = new ADORecordSet_odbc($qid2);
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) return false;
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+ $rs->_fetch();
+
+ while (!$rs->EOF) {
+ if ($table == strtoupper($rs->fields[2])) {
+ $q = $rs->fields[0];
+ $o = $rs->fields[1];
+ break;
+ }
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $qid = odbc_columns($this->_connectionID,$q,$o,strtoupper($table),'%');
+ } */
+
+ switch ($this->databaseType) {
+ case 'access':
+ case 'vfp':
+ $qid = odbc_columns($this->_connectionID);#,'%','',strtoupper($table),'%');
+ break;
+
+
+ case 'db2':
+ $colname = "%";
+ $qid = odbc_columns($this->_connectionID, "", $schema, $table, $colname);
+ break;
+
+ default:
+ $qid = @odbc_columns($this->_connectionID,'%','%',strtoupper($table),'%');
+ if (empty($qid)) $qid = odbc_columns($this->_connectionID);
+ break;
+ }
+ if (empty($qid)) return $false;
+
+ $rs = new ADORecordSet_odbc($qid);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs) return $false;
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+ $rs->_fetch();
+
+ $retarr = array();
+
+ /*
+ $rs->fields indices
+ 0 TABLE_QUALIFIER
+ 1 TABLE_SCHEM
+ 2 TABLE_NAME
+ 3 COLUMN_NAME
+ 4 DATA_TYPE
+ 5 TYPE_NAME
+ 6 PRECISION
+ 7 LENGTH
+ 8 SCALE
+ 9 RADIX
+ 10 NULLABLE
+ 11 REMARKS
+ */
+ while (!$rs->EOF) {
+ // adodb_pr($rs->fields);
+ if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[3];
+ $fld->type = $this->ODBCTypes($rs->fields[4]);
+
+ // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
+ // access uses precision to store length for char/varchar
+ if ($fld->type == 'C' or $fld->type == 'X') {
+ if ($this->databaseType == 'access')
+ $fld->max_length = $rs->fields[6];
+ else if ($rs->fields[4] <= -95) // UNICODE
+ $fld->max_length = $rs->fields[7]/2;
+ else
+ $fld->max_length = $rs->fields[7];
+ } else
+ $fld->max_length = $rs->fields[7];
+ $fld->not_null = !empty($rs->fields[10]);
+ $fld->scale = $rs->fields[8];
+ $retarr[strtoupper($fld->name)] = $fld;
+ } else if (sizeof($retarr)>0)
+ break;
+ $rs->MoveNext();
+ }
+ $rs->Close(); //-- crashes 4.03pl1 -- why?
+
+ if (empty($retarr)) $retarr = false;
+ return $retarr;
+ }
+
+ function Prepare($sql)
+ {
+ if (! $this->_bindInputArray) return $sql; // no binding
+ $stmt = odbc_prepare($this->_connectionID,$sql);
+ if (!$stmt) {
+ // we don't know whether odbc driver is parsing prepared stmts, so just return sql
+ return $sql;
+ }
+ return array($sql,$stmt,false);
+ }
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = '';
+
+ if ($inputarr) {
+ if (is_array($sql)) {
+ $stmtid = $sql[1];
+ } else {
+ $stmtid = odbc_prepare($this->_connectionID,$sql);
+
+ if ($stmtid == false) {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ return false;
+ }
+ }
+
+ if (! odbc_execute($stmtid,$inputarr)) {
+ //@odbc_free_result($stmtid);
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = odbc_errormsg();
+ $this->_errorCode = odbc_error();
+ }
+ return false;
+ }
+
+ } else if (is_array($sql)) {
+ $stmtid = $sql[1];
+ if (!odbc_execute($stmtid)) {
+ //@odbc_free_result($stmtid);
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = odbc_errormsg();
+ $this->_errorCode = odbc_error();
+ }
+ return false;
+ }
+ } else
+ $stmtid = odbc_exec($this->_connectionID,$sql);
+
+ $this->_lastAffectedRows = 0;
+ if ($stmtid) {
+ if (@odbc_num_fields($stmtid) == 0) {
+ $this->_lastAffectedRows = odbc_num_rows($stmtid);
+ $stmtid = true;
+ } else {
+ $this->_lastAffectedRows = 0;
+ odbc_binmode($stmtid,$this->binmode);
+ odbc_longreadlen($stmtid,$this->maxblobsize);
+ }
+
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = '';
+ $this->_errorCode = 0;
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ } else {
+ if ($this->_haserrorfunctions) {
+ $this->_errorMsg = odbc_errormsg();
+ $this->_errorCode = odbc_error();
+ } else {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ }
+ }
+ return $stmtid;
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ $ret = @odbc_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $ret;
+ }
+
+ function _affectedrows()
+ {
+ return $this->_lastAffectedRows;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_odbc extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "odbc";
+ var $dataProvider = "odbc";
+ var $useFetchArray;
+ var $_has_stupid_odbc_fetch_api_change;
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+
+ $this->_queryID = $id;
+
+ // the following is required for mysql odbc driver in 4.3.1 -- why?
+ $this->EOF = false;
+ $this->_currentRow = -1;
+ //parent::__construct($id);
+ }
+
+
+ // returns the field object
+ function FetchField($fieldOffset = -1)
+ {
+
+ $off=$fieldOffset+1; // offsets begin at 1
+
+ $o= new ADOFieldObject();
+ $o->name = @odbc_field_name($this->_queryID,$off);
+ $o->type = @odbc_field_type($this->_queryID,$off);
+ $o->max_length = @odbc_field_len($this->_queryID,$off);
+ if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name);
+ else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name);
+ return $o;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS) ? @odbc_num_rows($this->_queryID) : -1;
+ $this->_numOfFields = @odbc_num_fields($this->_queryID);
+ // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
+ if ($this->_numOfRows == 0) $this->_numOfRows = -1;
+ //$this->useFetchArray = $this->connection->useFetchArray;
+ $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200;
+ }
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
+ function GetArrayLimit($nrows,$offset=-1)
+ {
+ if ($offset <= 0) {
+ $rs = $this->GetArray($nrows);
+ return $rs;
+ }
+ $savem = $this->fetchMode;
+ $this->fetchMode = ADODB_FETCH_NUM;
+ $this->Move($offset);
+ $this->fetchMode = $savem;
+
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+
+ $results = array();
+ $cnt = 0;
+ while (!$this->EOF && $nrows != $cnt) {
+ $results[$cnt++] = $this->fields;
+ $this->MoveNext();
+ }
+
+ return $results;
+ }
+
+
+ function MoveNext()
+ {
+ if ($this->_numOfRows != 0 && !$this->EOF) {
+ $this->_currentRow++;
+ if( $this->_fetch() ) {
+ return true;
+ }
+ }
+ $this->fields = false;
+ $this->EOF = true;
+ return false;
+ }
+
+ function _fetch()
+ {
+ $this->fields = false;
+ if ($this->_has_stupid_odbc_fetch_api_change)
+ $rez = @odbc_fetch_into($this->_queryID,$this->fields);
+ else {
+ $row = 0;
+ $rez = @odbc_fetch_into($this->_queryID,$row,$this->fields);
+ }
+ if ($rez) {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+ $this->fields = $this->GetRowAssoc();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ function _close()
+ {
+ return @odbc_free_result($this->_queryID);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbc_db2.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbc_db2.inc.php
new file mode 100644
index 0000000..20fcccd
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbc_db2.inc.php
@@ -0,0 +1,369 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ DB2 data driver. Requires ODBC.
+
+From phpdb list:
+
+Hi Andrew,
+
+thanks a lot for your help. Today we discovered what
+our real problem was:
+
+After "playing" a little bit with the php-scripts that try
+to connect to the IBM DB2, we set the optional parameter
+Cursortype when calling odbc_pconnect(....).
+
+And the exciting thing: When we set the cursor type
+to SQL_CUR_USE_ODBC Cursor Type, then
+the whole query speed up from 1 till 10 seconds
+to 0.2 till 0.3 seconds for 100 records. Amazing!!!
+
+Therfore, PHP is just almost fast as calling the DB2
+from Servlets using JDBC (don't take too much care
+about the speed at whole: the database was on a
+completely other location, so the whole connection
+was made over a slow network connection).
+
+I hope this helps when other encounter the same
+problem when trying to connect to DB2 from
+PHP.
+
+Kind regards,
+Christian Szardenings
+
+2 Oct 2001
+Mark Newnham has discovered that the SQL_CUR_USE_ODBC is not supported by
+IBM's DB2 ODBC driver, so this must be a 3rd party ODBC driver.
+
+From the IBM CLI Reference:
+
+SQL_ATTR_ODBC_CURSORS (DB2 CLI v5)
+This connection attribute is defined by ODBC, but is not supported by DB2
+CLI. Any attempt to set or get this attribute will result in an SQLSTATE of
+HYC00 (Driver not capable).
+
+A 32-bit option specifying how the Driver Manager uses the ODBC cursor
+library.
+
+So I guess this means the message [above] was related to using a 3rd party
+odbc driver.
+
+Setting SQL_CUR_USE_ODBC
+========================
+To set SQL_CUR_USE_ODBC for drivers that require it, do this:
+
+$db = NewADOConnection('odbc_db2');
+$db->curMode = SQL_CUR_USE_ODBC;
+$db->Connect($dsn, $userid, $pwd);
+
+
+
+USING CLI INTERFACE
+===================
+
+I have had reports that the $host and $database params have to be reversed in
+Connect() when using the CLI interface. From Halmai Csongor csongor.halmai#nexum.hu:
+
+> The symptom is that if I change the database engine from postgres or any other to DB2 then the following
+> connection command becomes wrong despite being described this version to be correct in the docs.
+>
+> $connection_object->Connect( $DATABASE_HOST, $DATABASE_AUTH_USER_NAME, $DATABASE_AUTH_PASSWORD, $DATABASE_NAME )
+>
+> In case of DB2 I had to swap the first and last arguments in order to connect properly.
+
+
+System Error 5
+==============
+IF you get a System Error 5 when trying to Connect/Load, it could be a permission problem. Give the user connecting
+to DB2 full rights to the DB2 SQLLIB directory, and place the user in the DBUSERS group.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+if (!defined('ADODB_ODBC_DB2')){
+define('ADODB_ODBC_DB2',1);
+
+class ADODB_ODBC_DB2 extends ADODB_odbc {
+ var $databaseType = "db2";
+ var $concat_operator = '||';
+ var $sysTime = 'CURRENT TIME';
+ var $sysDate = 'CURRENT DATE';
+ var $sysTimeStamp = 'CURRENT TIMESTAMP';
+ // The complete string representation of a timestamp has the form
+ // yyyy-mm-dd-hh.mm.ss.nnnnnn.
+ var $fmtTimeStamp = "'Y-m-d-H.i.s'";
+ var $ansiOuter = true;
+ var $identitySQL = 'values IDENTITY_VAL_LOCAL()';
+ var $_bindInputArray = true;
+ var $hasInsertID = true;
+ var $rsPrefix = 'ADORecordset_odbc_';
+
+ function __construct()
+ {
+ if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC;
+ parent::__construct();
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " COALESCE($field, $ifNull) "; // if DB2 UDB
+ }
+
+ function ServerInfo()
+ {
+ //odbc_setoption($this->_connectionID,1,101 /*SQL_ATTR_ACCESS_MODE*/, 1 /*SQL_MODE_READ_ONLY*/);
+ $vers = $this->GetOne('select versionnumber from sysibm.sysversions');
+ //odbc_setoption($this->_connectionID,1,101, 0 /*SQL_MODE_READ_WRITE*/);
+ return array('description'=>'DB2 ODBC driver', 'version'=>$vers);
+ }
+
+ function _insertid()
+ {
+ return $this->GetOne($this->identitySQL);
+ }
+
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if ($this->_autocommit) $this->BeginTrans();
+ return $this->GetOne("select $col from $tables where $where for update");
+ }
+
+ function MetaTables($ttype=false,$showSchema=false, $qtable="%", $qschema="%")
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $qid = odbc_tables($this->_connectionID, "", $qschema, $qtable, "");
+
+ $rs = new ADORecordSet_odbc($qid);
+
+ $ADODB_FETCH_MODE = $savem;
+ if (!$rs) {
+ $false = false;
+ return $false;
+ }
+ $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change;
+
+ $arr = $rs->GetArray();
+ //print_r($arr);
+
+ $rs->Close();
+ $arr2 = array();
+
+ if ($ttype) {
+ $isview = strncmp($ttype,'V',1) === 0;
+ }
+ for ($i=0; $i < sizeof($arr); $i++) {
+
+ if (!$arr[$i][2]) continue;
+ if (strncmp($arr[$i][1],'SYS',3) === 0) continue;
+
+ $type = $arr[$i][3];
+
+ if ($showSchema) $arr[$i][2] = $arr[$i][1].'.'.$arr[$i][2];
+
+ if ($ttype) {
+ if ($isview) {
+ if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2];
+ } else if (strncmp($type,'T',1) === 0) $arr2[] = $arr[$i][2];
+ } else if (strncmp($type,'S',1) !== 0) $arr2[] = $arr[$i][2];
+ }
+ return $arr2;
+ }
+
+ function MetaIndexes ($table, $primary = FALSE, $owner=false)
+ {
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+ $false = false;
+ // get index details
+ $table = strtoupper($table);
+ $SQL="SELECT NAME, UNIQUERULE, COLNAMES FROM SYSIBM.SYSINDEXES WHERE TBNAME='$table'";
+ if ($primary)
+ $SQL.= " AND UNIQUERULE='P'";
+ $rs = $this->Execute($SQL);
+ if (!is_object($rs)) {
+ if (isset($savem))
+ $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+ $indexes = array ();
+ // parse index data into array
+ while ($row = $rs->FetchRow()) {
+ $indexes[$row[0]] = array(
+ 'unique' => ($row[1] == 'U' || $row[1] == 'P'),
+ 'columns' => array()
+ );
+ $cols = ltrim($row[2],'+');
+ $indexes[$row[0]]['columns'] = explode('+', $cols);
+ }
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ }
+ return $indexes;
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ // use right() and replace() ?
+ if (!$col) $col = $this->sysDate;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '||';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "char(year($col))";
+ break;
+ case 'M':
+ $s .= "substr(monthname($col),1,3)";
+ break;
+ case 'm':
+ $s .= "right(digits(month($col)),2)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "right(digits(day($col)),2)";
+ break;
+ case 'H':
+ case 'h':
+ if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)";
+ else $s .= "''";
+ break;
+ case 'i':
+ case 'I':
+ if ($col != $this->sysDate)
+ $s .= "right(digits(minute($col)),2)";
+ else $s .= "''";
+ break;
+ case 'S':
+ case 's':
+ if ($col != $this->sysDate)
+ $s .= "right(digits(second($col)),2)";
+ else $s .= "''";
+ break;
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ }
+ }
+ return $s;
+ }
+
+
+ function SelectLimit($sql, $nrows = -1, $offset = -1, $inputArr = false, $secs2cache = 0)
+ {
+ $nrows = (integer) $nrows;
+ if ($offset <= 0) {
+ // could also use " OPTIMIZE FOR $nrows ROWS "
+ if ($nrows >= 0) $sql .= " FETCH FIRST $nrows ROWS ONLY ";
+ $rs = $this->Execute($sql,$inputArr);
+ } else {
+ if ($offset > 0 && $nrows < 0);
+ else {
+ $nrows += $offset;
+ $sql .= " FETCH FIRST $nrows ROWS ONLY ";
+ }
+ $rs = ADOConnection::SelectLimit($sql,-1,$offset,$inputArr);
+ }
+
+ return $rs;
+ }
+
+};
+
+
+class ADORecordSet_odbc_db2 extends ADORecordSet_odbc {
+
+ var $databaseType = "db2";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ switch (strtoupper($t)) {
+ case 'VARCHAR':
+ case 'CHAR':
+ case 'CHARACTER':
+ case 'C':
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'LONGCHAR':
+ case 'TEXT':
+ case 'CLOB':
+ case 'DBCLOB': // double-byte
+ case 'X':
+ return 'X';
+
+ case 'BLOB':
+ case 'GRAPHIC':
+ case 'VARGRAPHIC':
+ return 'B';
+
+ case 'DATE':
+ case 'D':
+ return 'D';
+
+ case 'TIME':
+ case 'TIMESTAMP':
+ case 'T':
+ return 'T';
+
+ //case 'BOOLEAN':
+ //case 'BIT':
+ // return 'L';
+
+ //case 'COUNTER':
+ // return 'R';
+
+ case 'INT':
+ case 'INTEGER':
+ case 'BIGINT':
+ case 'SMALLINT':
+ case 'I':
+ return 'I';
+
+ default: return 'N';
+ }
+ }
+}
+
+} //define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbc_mssql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbc_mssql.inc.php
new file mode 100644
index 0000000..367964c
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbc_mssql.inc.php
@@ -0,0 +1,365 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ MSSQL support via ODBC. Requires ODBC. Works on Windows and Unix.
+ For Unix configuration, see http://phpbuilder.com/columns/alberto20000919.php3
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+
+
+class ADODB_odbc_mssql extends ADODB_odbc {
+ var $databaseType = 'odbc_mssql';
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d\TH:i:s'";
+ var $_bindInputArray = true;
+ var $metaDatabasesSQL = "select name from sysdatabases where name <> 'master'";
+ var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE'))";
+ var $metaColumnsSQL = # xtype==61 is datetime
+ "select c.name,t.name,c.length,c.isnullable, c.status,
+ (case when c.xusertype=61 then 0 else c.xprec end),
+ (case when c.xusertype=61 then 0 else c.xscale end)
+ from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'";
+ var $hasTop = 'top'; // support mssql/interbase SELECT TOP 10 * FROM TABLE
+ var $sysDate = 'GetDate()';
+ var $sysTimeStamp = 'GetDate()';
+ var $leftOuter = '*=';
+ var $rightOuter = '=*';
+ var $substr = 'substring';
+ var $length = 'len';
+ var $ansiOuter = true; // for mssql7 or later
+ var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
+ var $hasInsertID = true;
+ var $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL OFF'; # When SET CONCAT_NULL_YIELDS_NULL is ON,
+ # concatenating a null value with a string yields a NULL result
+
+ function __construct()
+ {
+ parent::__construct();
+ //$this->curmode = SQL_CUR_USE_ODBC;
+ }
+
+ // crashes php...
+ function ServerInfo()
+ {
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $row = $this->GetRow("execute sp_server_info 2");
+ $ADODB_FETCH_MODE = $save;
+ if (!is_array($row)) return false;
+ $arr['description'] = $row[2];
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " ISNULL($field, $ifNull) "; // if MS SQL Server
+ }
+
+ function _insertid()
+ {
+ // SCOPE_IDENTITY()
+ // Returns the last IDENTITY value inserted into an IDENTITY column in
+ // the same scope. A scope is a module -- a stored procedure, trigger,
+ // function, or batch. Thus, two statements are in the same scope if
+ // they are in the same stored procedure, function, or batch.
+ return $this->GetOne($this->identitySQL);
+ }
+
+
+ function MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $table = $this->qstr(strtoupper($table));
+
+ $sql =
+"select object_name(constid) as constraint_name,
+ col_name(fkeyid, fkey) as column_name,
+ object_name(rkeyid) as referenced_table_name,
+ col_name(rkeyid, rkey) as referenced_column_name
+from sysforeignkeys
+where upper(object_name(fkeyid)) = $table
+order by constraint_name, referenced_table_name, keyno";
+
+ $constraints = $this->GetArray($sql);
+
+ $ADODB_FETCH_MODE = $save;
+
+ $arr = false;
+ foreach($constraints as $constr) {
+ //print_r($constr);
+ $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3];
+ }
+ if (!$arr) return false;
+
+ $arr2 = false;
+
+ foreach($arr as $k => $v) {
+ foreach($v as $a => $b) {
+ if ($upper) $a = strtoupper($a);
+ $arr2[$a] = $b;
+ }
+ }
+ return $arr2;
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ if ($mask) {//$this->debug=1;
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr($mask);
+ $this->metaTablesSQL .= " AND name like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+
+ $this->_findschema($table,$schema);
+ if ($schema) {
+ $dbName = $this->database;
+ $this->SelectDB($schema);
+ }
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+
+ if ($schema) {
+ $this->SelectDB($dbName);
+ }
+
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF){
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+
+ $fld->not_null = (!$rs->fields[3]);
+ $fld->auto_increment = ($rs->fields[4] == 128); // sys.syscolumns status field. 0x80 = 128 ref: http://msdn.microsoft.com/en-us/library/ms186816.aspx
+
+
+ if (isset($rs->fields[5]) && $rs->fields[5]) {
+ if ($rs->fields[5]>0) $fld->max_length = $rs->fields[5];
+ $fld->scale = $rs->fields[6];
+ if ($fld->scale>0) $fld->max_length += 1;
+ } else
+ $fld->max_length = $rs->fields[2];
+
+
+ if ($save == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $retarr;
+
+ }
+
+
+ function MetaIndexes($table,$primary=false, $owner=false)
+ {
+ $table = $this->qstr($table);
+
+ $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
+ CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK,
+ CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
+ FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
+ INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
+ INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
+ WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table
+ ORDER BY O.name, I.Name, K.keyno";
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute($sql);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return FALSE;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ if (!$primary && $row[5]) continue;
+
+ $indexes[$row[0]]['unique'] = $row[6];
+ $indexes[$row[0]]['columns'][] = $row[1];
+ }
+ return $indexes;
+ }
+
+ function _query($sql,$inputarr=false)
+ {
+ if (is_string($sql)) $sql = str_replace('||','+',$sql);
+ return ADODB_odbc::_query($sql,$inputarr);
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET TRANSACTION ".$transaction_mode);
+ }
+
+ // "Stein-Aksel Basma" <basma@accelero.no>
+ // tested with MSSQL 2000
+ function MetaPrimaryKeys($table, $owner = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = '';
+ $this->_findschema($table,$schema);
+ //if (!$schema) $schema = $this->database;
+ if ($schema) $schema = "and k.table_catalog like '$schema%'";
+
+ $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k,
+ information_schema.table_constraints tc
+ where tc.constraint_name = k.constraint_name and tc.constraint_type =
+ 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position ";
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $a = $this->GetCol($sql);
+ $ADODB_FETCH_MODE = $savem;
+
+ if ($a && sizeof($a)>0) return $a;
+ $false = false;
+ return $false;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ if ($nrows > 0 && $offset <= 0) {
+ $sql = preg_replace(
+ '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop." $nrows ",$sql);
+ $rs = $this->Execute($sql,$inputarr);
+ } else
+ $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+
+ return $rs;
+ }
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '+';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "datename(yyyy,$col)";
+ break;
+ case 'M':
+ $s .= "convert(char(3),$col,0)";
+ break;
+ case 'm':
+ $s .= "replace(str(month($col),2),' ','0')";
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "datename(quarter,$col)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "replace(str(day($col),2),' ','0')";
+ break;
+ case 'h':
+ $s .= "substring(convert(char(14),$col,0),13,2)";
+ break;
+
+ case 'H':
+ $s .= "replace(str(datepart(hh,$col),2),' ','0')";
+ break;
+
+ case 'i':
+ $s .= "replace(str(datepart(mi,$col),2),' ','0')";
+ break;
+ case 's':
+ $s .= "replace(str(datepart(ss,$col),2),' ','0')";
+ break;
+ case 'a':
+ case 'A':
+ $s .= "substring(convert(char(19),$col,0),18,2)";
+ break;
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ break;
+ }
+ }
+ return $s;
+ }
+
+}
+
+class ADORecordSet_odbc_mssql extends ADORecordSet_odbc {
+
+ var $databaseType = 'odbc_mssql';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbc_oracle.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbc_oracle.inc.php
new file mode 100644
index 0000000..d1badca
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbc_oracle.inc.php
@@ -0,0 +1,108 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Oracle support via ODBC. Requires ODBC. Works on Windows.
+*/
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+
+
+class ADODB_odbc_oracle extends ADODB_odbc {
+ var $databaseType = 'odbc_oracle';
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $concat_operator='||';
+ var $fmtDate = "'Y-m-d 00:00:00'";
+ var $fmtTimeStamp = "'Y-m-d h:i:sA'";
+ var $metaTablesSQL = 'select table_name from cat';
+ var $metaColumnsSQL = "select cname,coltype,width from col where tname='%s' order by colno";
+ var $sysDate = "TRUNC(SYSDATE)";
+ var $sysTimeStamp = 'SYSDATE';
+
+ //var $_bindInputArray = false;
+
+ function MetaTables($ttype = false, $showSchema = false, $mask = false)
+ {
+ $false = false;
+ $rs = $this->Execute($this->metaTablesSQL);
+ if ($rs === false) return $false;
+ $arr = $rs->GetArray();
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ $arr2[] = $arr[$i][0];
+ }
+ $rs->Close();
+ return $arr2;
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+ if ($rs === false) {
+ $false = false;
+ return $false;
+ }
+ $retarr = array();
+ while (!$rs->EOF) { //print_r($rs->fields);
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[strtoupper($fld->name)] = $fld;
+
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ return $retarr;
+ }
+
+ // returns true or false
+ function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword,SQL_CUR_USE_ODBC );
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+
+ $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+ //if ($this->_connectionID) odbc_autocommit($this->_connectionID,true);
+ return $this->_connectionID != false;
+ }
+ // returns true or false
+ function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_connectionID = odbc_pconnect($argDSN,$argUsername,$argPassword,SQL_CUR_USE_ODBC );
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+
+ $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+ //if ($this->_connectionID) odbc_autocommit($this->_connectionID,true);
+ return $this->_connectionID != false;
+ }
+}
+
+class ADORecordSet_odbc_oracle extends ADORecordSet_odbc {
+
+ var $databaseType = 'odbc_oracle';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbtp.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbtp.inc.php
new file mode 100644
index 0000000..6dfb5d0
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbtp.inc.php
@@ -0,0 +1,839 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+ Latest version is available at http://adodb.org/
+*/
+// Code contributed by "stefan bogdan" <sbogdan#rsb.ro>
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+define("_ADODB_ODBTP_LAYER", 2 );
+
+class ADODB_odbtp extends ADOConnection{
+ var $databaseType = "odbtp";
+ var $dataProvider = "odbtp";
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $odbc_driver = 0;
+ var $hasAffectedRows = true;
+ var $hasInsertID = false;
+ var $hasGenID = true;
+ var $hasMoveFirst = true;
+
+ var $_genSeqSQL = "create table %s (seq_name char(30) not null unique , seq_value integer not null)";
+ var $_dropSeqSQL = "delete from adodb_seq where seq_name = '%s'";
+ var $_bindInputArray = false;
+ var $_useUnicodeSQL = false;
+ var $_canPrepareSP = false;
+ var $_dontPoolDBC = true;
+
+ function __construct()
+ {
+ }
+
+ function ServerInfo()
+ {
+ return array('description' => @odbtp_get_attr( ODB_ATTR_DBMSNAME, $this->_connectionID),
+ 'version' => @odbtp_get_attr( ODB_ATTR_DBMSVER, $this->_connectionID));
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+ if (empty($this->_connectionID)) return @odbtp_last_error();
+ return @odbtp_last_error($this->_connectionID);
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_errorCode !== false) return $this->_errorCode;
+ if (empty($this->_connectionID)) return @odbtp_last_error_state();
+ return @odbtp_last_error_state($this->_connectionID);
+ }
+/*
+ function DBDate($d,$isfld=false)
+ {
+ if (empty($d) && $d !== 0) return 'null';
+ if ($isfld) return "convert(date, $d, 120)";
+
+ if (is_string($d)) $d = ADORecordSet::UnixDate($d);
+ $d = adodb_date($this->fmtDate,$d);
+ return "convert(date, $d, 120)";
+ }
+
+ function DBTimeStamp($d,$isfld=false)
+ {
+ if (empty($d) && $d !== 0) return 'null';
+ if ($isfld) return "convert(datetime, $d, 120)";
+
+ if (is_string($d)) $d = ADORecordSet::UnixDate($d);
+ $d = adodb_date($this->fmtDate,$d);
+ return "convert(datetime, $d, 120)";
+ }
+*/
+
+ function _insertid()
+ {
+ // SCOPE_IDENTITY()
+ // Returns the last IDENTITY value inserted into an IDENTITY column in
+ // the same scope. A scope is a module -- a stored procedure, trigger,
+ // function, or batch. Thus, two statements are in the same scope if
+ // they are in the same stored procedure, function, or batch.
+ return $this->GetOne($this->identitySQL);
+ }
+
+ function _affectedrows()
+ {
+ if ($this->_queryID) {
+ return @odbtp_affected_rows ($this->_queryID);
+ } else
+ return 0;
+ }
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ //verify existence
+ $num = $this->GetOne("select seq_value from adodb_seq");
+ $seqtab='adodb_seq';
+ if( $this->odbc_driver == ODB_DRIVER_FOXPRO ) {
+ $path = @odbtp_get_attr( ODB_ATTR_DATABASENAME, $this->_connectionID );
+ //if using vfp dbc file
+ if( !strcasecmp(strrchr($path, '.'), '.dbc') )
+ $path = substr($path,0,strrpos($path,'\/'));
+ $seqtab = $path . '/' . $seqtab;
+ }
+ if($num == false) {
+ if (empty($this->_genSeqSQL)) return false;
+ $ok = $this->Execute(sprintf($this->_genSeqSQL ,$seqtab));
+ }
+ $num = $this->GetOne("select seq_value from adodb_seq where seq_name='$seqname'");
+ if ($num) {
+ return false;
+ }
+ $start -= 1;
+ return $this->Execute("insert into adodb_seq values('$seqname',$start)");
+ }
+
+ function DropSequence($seqname = 'adodbseq')
+ {
+ if (empty($this->_dropSeqSQL)) return false;
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ $seqtab='adodb_seq';
+ if( $this->odbc_driver == ODB_DRIVER_FOXPRO) {
+ $path = @odbtp_get_attr( ODB_ATTR_DATABASENAME, $this->_connectionID );
+ //if using vfp dbc file
+ if( !strcasecmp(strrchr($path, '.'), '.dbc') )
+ $path = substr($path,0,strrpos($path,'\/'));
+ $seqtab = $path . '/' . $seqtab;
+ }
+ $MAXLOOPS = 100;
+ while (--$MAXLOOPS>=0) {
+ $num = $this->GetOne("select seq_value from adodb_seq where seq_name='$seq'");
+ if ($num === false) {
+ //verify if abodb_seq table exist
+ $ok = $this->GetOne("select seq_value from adodb_seq ");
+ if(!$ok) {
+ //creating the sequence table adodb_seq
+ $this->Execute(sprintf($this->_genSeqSQL ,$seqtab));
+ }
+ $start -= 1;
+ $num = '0';
+ $ok = $this->Execute("insert into adodb_seq values('$seq',$start)");
+ if (!$ok) return false;
+ }
+ $ok = $this->Execute("update adodb_seq set seq_value=seq_value+1 where seq_name='$seq'");
+ if($ok) {
+ $num += 1;
+ $this->genID = $num;
+ return $num;
+ }
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
+ }
+ return false;
+ }
+
+ //example for $UserOrDSN
+ //for visual fox : DRIVER={Microsoft Visual FoxPro Driver};SOURCETYPE=DBF;SOURCEDB=c:\YourDbfFileDir;EXCLUSIVE=NO;
+ //for visual fox dbc: DRIVER={Microsoft Visual FoxPro Driver};SOURCETYPE=DBC;SOURCEDB=c:\YourDbcFileDir\mydb.dbc;EXCLUSIVE=NO;
+ //for access : DRIVER={Microsoft Access Driver (*.mdb)};DBQ=c:\path_to_access_db\base_test.mdb;UID=root;PWD=;
+ //for mssql : DRIVER={SQL Server};SERVER=myserver;UID=myuid;PWD=mypwd;DATABASE=OdbtpTest;
+ //if uid & pwd can be separate
+ function _connect($HostOrInterface, $UserOrDSN='', $argPassword='', $argDatabase='')
+ {
+ if ($argPassword && stripos($UserOrDSN,'DRIVER=') !== false) {
+ $this->_connectionID = odbtp_connect($HostOrInterface,$UserOrDSN.';PWD='.$argPassword);
+ } else
+ $this->_connectionID = odbtp_connect($HostOrInterface,$UserOrDSN,$argPassword,$argDatabase);
+ if ($this->_connectionID === false) {
+ $this->_errorMsg = $this->ErrorMsg() ;
+ return false;
+ }
+
+ odbtp_convert_datetime($this->_connectionID,true);
+
+ if ($this->_dontPoolDBC) {
+ if (function_exists('odbtp_dont_pool_dbc'))
+ @odbtp_dont_pool_dbc($this->_connectionID);
+ }
+ else {
+ $this->_dontPoolDBC = true;
+ }
+ $this->odbc_driver = @odbtp_get_attr(ODB_ATTR_DRIVER, $this->_connectionID);
+ $dbms = strtolower(@odbtp_get_attr(ODB_ATTR_DBMSNAME, $this->_connectionID));
+ $this->odbc_name = $dbms;
+
+ // Account for inconsistent DBMS names
+ if( $this->odbc_driver == ODB_DRIVER_ORACLE )
+ $dbms = 'oracle';
+ else if( $this->odbc_driver == ODB_DRIVER_SYBASE )
+ $dbms = 'sybase';
+
+ // Set DBMS specific attributes
+ switch( $dbms ) {
+ case 'microsoft sql server':
+ $this->databaseType = 'odbtp_mssql';
+ $this->fmtDate = "'Y-m-d'";
+ $this->fmtTimeStamp = "'Y-m-d h:i:sA'";
+ $this->sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ $this->sysTimeStamp = 'GetDate()';
+ $this->ansiOuter = true;
+ $this->leftOuter = '*=';
+ $this->rightOuter = '=*';
+ $this->hasTop = 'top';
+ $this->hasInsertID = true;
+ $this->hasTransactions = true;
+ $this->_bindInputArray = true;
+ $this->_canSelectDb = true;
+ $this->substr = "substring";
+ $this->length = 'len';
+ $this->identitySQL = 'select SCOPE_IDENTITY()';
+ $this->metaDatabasesSQL = "select name from master..sysdatabases where name <> 'master'";
+ $this->_canPrepareSP = true;
+ break;
+ case 'access':
+ $this->databaseType = 'odbtp_access';
+ $this->fmtDate = "#Y-m-d#";
+ $this->fmtTimeStamp = "#Y-m-d h:i:sA#";
+ $this->sysDate = "FORMAT(NOW,'yyyy-mm-dd')";
+ $this->sysTimeStamp = 'NOW';
+ $this->hasTop = 'top';
+ $this->hasTransactions = false;
+ $this->_canPrepareSP = true; // For MS Access only.
+ break;
+ case 'visual foxpro':
+ $this->databaseType = 'odbtp_vfp';
+ $this->fmtDate = "{^Y-m-d}";
+ $this->fmtTimeStamp = "{^Y-m-d, h:i:sA}";
+ $this->sysDate = 'date()';
+ $this->sysTimeStamp = 'datetime()';
+ $this->ansiOuter = true;
+ $this->hasTop = 'top';
+ $this->hasTransactions = false;
+ $this->replaceQuote = "'+chr(39)+'";
+ $this->true = '.T.';
+ $this->false = '.F.';
+
+ break;
+ case 'oracle':
+ $this->databaseType = 'odbtp_oci8';
+ $this->fmtDate = "'Y-m-d 00:00:00'";
+ $this->fmtTimeStamp = "'Y-m-d h:i:sA'";
+ $this->sysDate = 'TRUNC(SYSDATE)';
+ $this->sysTimeStamp = 'SYSDATE';
+ $this->hasTransactions = true;
+ $this->_bindInputArray = true;
+ $this->concat_operator = '||';
+ break;
+ case 'sybase':
+ $this->databaseType = 'odbtp_sybase';
+ $this->fmtDate = "'Y-m-d'";
+ $this->fmtTimeStamp = "'Y-m-d H:i:s'";
+ $this->sysDate = 'GetDate()';
+ $this->sysTimeStamp = 'GetDate()';
+ $this->leftOuter = '*=';
+ $this->rightOuter = '=*';
+ $this->hasInsertID = true;
+ $this->hasTransactions = true;
+ $this->identitySQL = 'select SCOPE_IDENTITY()';
+ break;
+ default:
+ $this->databaseType = 'odbtp';
+ if( @odbtp_get_attr(ODB_ATTR_TXNCAPABLE, $this->_connectionID) )
+ $this->hasTransactions = true;
+ else
+ $this->hasTransactions = false;
+ }
+ @odbtp_set_attr(ODB_ATTR_FULLCOLINFO, TRUE, $this->_connectionID );
+
+ if ($this->_useUnicodeSQL )
+ @odbtp_set_attr(ODB_ATTR_UNICODESQL, TRUE, $this->_connectionID);
+
+ return true;
+ }
+
+ function _pconnect($HostOrInterface, $UserOrDSN='', $argPassword='', $argDatabase='')
+ {
+ $this->_dontPoolDBC = false;
+ return $this->_connect($HostOrInterface, $UserOrDSN, $argPassword, $argDatabase);
+ }
+
+ function SelectDB($dbName)
+ {
+ if (!@odbtp_select_db($dbName, $this->_connectionID)) {
+ return false;
+ }
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ return true;
+ }
+
+ function MetaTables($ttype='',$showSchema=false,$mask=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savefm = $this->SetFetchMode(false);
+
+ $arr = $this->GetArray("||SQLTables||||$ttype");
+
+ if (isset($savefm)) $this->SetFetchMode($savefm);
+ $ADODB_FETCH_MODE = $savem;
+
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($arr[$i][3] == 'SYSTEM TABLE' ) continue;
+ if ($arr[$i][2])
+ $arr2[] = $showSchema && $arr[$i][1]? $arr[$i][1].'.'.$arr[$i][2] : $arr[$i][2];
+ }
+ return $arr2;
+ }
+
+ function MetaColumns($table,$upper=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = false;
+ $this->_findschema($table,$schema);
+ if ($upper) $table = strtoupper($table);
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savefm = $this->SetFetchMode(false);
+
+ $rs = $this->Execute( "||SQLColumns||$schema|$table" );
+
+ if (isset($savefm)) $this->SetFetchMode($savefm);
+ $ADODB_FETCH_MODE = $savem;
+
+ if (!$rs || $rs->EOF) {
+ $false = false;
+ return $false;
+ }
+ $retarr = array();
+ while (!$rs->EOF) {
+ //print_r($rs->fields);
+ if (strtoupper($rs->fields[2]) == $table) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[3];
+ $fld->type = $rs->fields[5];
+ $fld->max_length = $rs->fields[6];
+ $fld->not_null = !empty($rs->fields[9]);
+ $fld->scale = $rs->fields[7];
+ if (isset($rs->fields[12])) // vfp does not have field 12
+ if (!is_null($rs->fields[12])) {
+ $fld->has_default = true;
+ $fld->default_value = $rs->fields[12];
+ }
+ $retarr[strtoupper($fld->name)] = $fld;
+ } else if (!empty($retarr))
+ break;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ return $retarr;
+ }
+
+ function MetaPrimaryKeys($table, $owner='')
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $arr = $this->GetArray("||SQLPrimaryKeys||$owner|$table");
+ $ADODB_FETCH_MODE = $savem;
+
+ //print_r($arr);
+ $arr2 = array();
+ for ($i=0; $i < sizeof($arr); $i++) {
+ if ($arr[$i][3]) $arr2[] = $arr[$i][3];
+ }
+ return $arr2;
+ }
+
+ function MetaForeignKeys($table, $owner='', $upper=false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $savem = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $constraints = $this->GetArray("||SQLForeignKeys|||||$owner|$table");
+ $ADODB_FETCH_MODE = $savem;
+
+ $arr = false;
+ foreach($constraints as $constr) {
+ //print_r($constr);
+ $arr[$constr[11]][$constr[2]][] = $constr[7].'='.$constr[3];
+ }
+ if (!$arr) {
+ $false = false;
+ return $false;
+ }
+
+ $arr2 = array();
+
+ foreach($arr as $k => $v) {
+ foreach($v as $a => $b) {
+ if ($upper) $a = strtoupper($a);
+ $arr2[$a] = $b;
+ }
+ }
+ return $arr2;
+ }
+
+ function BeginTrans()
+ {
+ if (!$this->hasTransactions) return false;
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ $this->autoCommit = false;
+ if (defined('ODB_TXN_DEFAULT'))
+ $txn = ODB_TXN_DEFAULT;
+ else
+ $txn = ODB_TXN_READUNCOMMITTED;
+ $rs = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS,$txn,$this->_connectionID);
+ if(!$rs) return false;
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->autoCommit = true;
+ if( ($ret = @odbtp_commit($this->_connectionID)) )
+ $ret = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS, ODB_TXN_NONE, $this->_connectionID);//set transaction off
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ if ($this->transCnt) $this->transCnt -= 1;
+ $this->autoCommit = true;
+ if( ($ret = @odbtp_rollback($this->_connectionID)) )
+ $ret = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS, ODB_TXN_NONE, $this->_connectionID);//set transaction off
+ return $ret;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ // TOP requires ORDER BY for Visual FoxPro
+ if( $this->odbc_driver == ODB_DRIVER_FOXPRO ) {
+ if (!preg_match('/ORDER[ \t\r\n]+BY/is',$sql)) $sql .= ' ORDER BY 1';
+ }
+ $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ return $ret;
+ }
+
+ function Prepare($sql)
+ {
+ if (! $this->_bindInputArray) return $sql; // no binding
+
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ $stmt = @odbtp_prepare($sql,$this->_connectionID);
+ if (!$stmt) {
+ // print "Prepare Error for ($sql) ".$this->ErrorMsg()."<br>";
+ return $sql;
+ }
+ return array($sql,$stmt,false);
+ }
+
+ function PrepareSP($sql, $param = true)
+ {
+ if (!$this->_canPrepareSP) return $sql; // Can't prepare procedures
+
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ $stmt = @odbtp_prepare_proc($sql,$this->_connectionID);
+ if (!$stmt) return false;
+ return array($sql,$stmt);
+ }
+
+ /*
+ Usage:
+ $stmt = $db->PrepareSP('SP_RUNSOMETHING'); -- takes 2 params, @myid and @group
+
+ # note that the parameter does not have @ in front!
+ $db->Parameter($stmt,$id,'myid');
+ $db->Parameter($stmt,$group,'group',false,64);
+ $db->Parameter($stmt,$group,'photo',false,100000,ODB_BINARY);
+ $db->Execute($stmt);
+
+ @param $stmt Statement returned by Prepare() or PrepareSP().
+ @param $var PHP variable to bind to. Can set to null (for isNull support).
+ @param $name Name of stored procedure variable name to bind to.
+ @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in odbtp.
+ @param [$maxLen] Holds an maximum length of the variable.
+ @param [$type] The data type of $var. Legal values depend on driver.
+
+ See odbtp_attach_param documentation at http://odbtp.sourceforge.net.
+ */
+ function Parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=0, $type=0)
+ {
+ if ( $this->odbc_driver == ODB_DRIVER_JET ) {
+ $name = '['.$name.']';
+ if( !$type && $this->_useUnicodeSQL
+ && @odbtp_param_bindtype($stmt[1], $name) == ODB_CHAR )
+ {
+ $type = ODB_WCHAR;
+ }
+ }
+ else {
+ $name = '@'.$name;
+ }
+ return @odbtp_attach_param($stmt[1], $name, $var, $type, $maxLen);
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+
+ function UpdateBlob($table,$column,$val,$where,$blobtype='image')
+ {
+ $sql = "UPDATE $table SET $column = ? WHERE $where";
+ if( !($stmt = @odbtp_prepare($sql, $this->_connectionID)) )
+ return false;
+ if( !@odbtp_input( $stmt, 1, ODB_BINARY, 1000000, $blobtype ) )
+ return false;
+ if( !@odbtp_set( $stmt, 1, $val ) )
+ return false;
+ return @odbtp_execute( $stmt ) != false;
+ }
+
+ function MetaIndexes($table,$primary=false, $owner=false)
+ {
+ switch ( $this->odbc_driver) {
+ case ODB_DRIVER_MSSQL:
+ return $this->MetaIndexes_mssql($table, $primary);
+ default:
+ return array();
+ }
+ }
+
+ function MetaIndexes_mssql($table,$primary=false, $owner = false)
+ {
+ $table = strtolower($this->qstr($table));
+
+ $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
+ CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK,
+ CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
+ FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
+ INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
+ INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
+ WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND lower(O.Name) = $table
+ ORDER BY O.name, I.Name, K.keyno";
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute($sql);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return FALSE;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ if ($primary && !$row[5]) continue;
+
+ $indexes[$row[0]]['unique'] = $row[6];
+ $indexes[$row[0]]['columns'][] = $row[1];
+ }
+ return $indexes;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ switch( $this->odbc_driver ) {
+ case ODB_DRIVER_MSSQL:
+ return " ISNULL($field, $ifNull) ";
+ case ODB_DRIVER_JET:
+ return " IIF(IsNull($field), $ifNull, $field) ";
+ }
+ return " CASE WHEN $field is null THEN $ifNull ELSE $field END ";
+ }
+
+ function _query($sql,$inputarr=false)
+ {
+ $last_php_error = $this->resetLastError();
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ if ($inputarr) {
+ if (is_array($sql)) {
+ $stmtid = $sql[1];
+ } else {
+ $stmtid = @odbtp_prepare($sql,$this->_connectionID);
+ if ($stmtid == false) {
+ $this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
+ return false;
+ }
+ }
+ $num_params = @odbtp_num_params( $stmtid );
+ /*
+ for( $param = 1; $param <= $num_params; $param++ ) {
+ @odbtp_input( $stmtid, $param );
+ @odbtp_set( $stmtid, $param, $inputarr[$param-1] );
+ }*/
+
+ $param = 1;
+ foreach($inputarr as $v) {
+ @odbtp_input( $stmtid, $param );
+ @odbtp_set( $stmtid, $param, $v );
+ $param += 1;
+ if ($param > $num_params) break;
+ }
+
+ if (!@odbtp_execute($stmtid) ) {
+ return false;
+ }
+ } else if (is_array($sql)) {
+ $stmtid = $sql[1];
+ if (!@odbtp_execute($stmtid)) {
+ return false;
+ }
+ } else {
+ $stmtid = odbtp_query($sql,$this->_connectionID);
+ }
+ $this->_lastAffectedRows = 0;
+ if ($stmtid) {
+ $this->_lastAffectedRows = @odbtp_affected_rows($stmtid);
+ }
+ return $stmtid;
+ }
+
+ function _close()
+ {
+ $ret = @odbtp_close($this->_connectionID);
+ $this->_connectionID = false;
+ return $ret;
+ }
+}
+
+class ADORecordSet_odbtp extends ADORecordSet {
+
+ var $databaseType = 'odbtp';
+ var $canSeek = true;
+
+ function __construct($queryID,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+ parent::__construct($queryID);
+ }
+
+ function _initrs()
+ {
+ $this->_numOfFields = @odbtp_num_fields($this->_queryID);
+ if (!($this->_numOfRows = @odbtp_num_rows($this->_queryID)))
+ $this->_numOfRows = -1;
+
+ if (!$this->connection->_useUnicodeSQL) return;
+
+ if ($this->connection->odbc_driver == ODB_DRIVER_JET) {
+ if (!@odbtp_get_attr(ODB_ATTR_MAPCHARTOWCHAR,
+ $this->connection->_connectionID))
+ {
+ for ($f = 0; $f < $this->_numOfFields; $f++) {
+ if (@odbtp_field_bindtype($this->_queryID, $f) == ODB_CHAR)
+ @odbtp_bind_field($this->_queryID, $f, ODB_WCHAR);
+ }
+ }
+ }
+ }
+
+ function FetchField($fieldOffset = 0)
+ {
+ $off=$fieldOffset; // offsets begin at 0
+ $o= new ADOFieldObject();
+ $o->name = @odbtp_field_name($this->_queryID,$off);
+ $o->type = @odbtp_field_type($this->_queryID,$off);
+ $o->max_length = @odbtp_field_length($this->_queryID,$off);
+ if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name);
+ else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name);
+ return $o;
+ }
+
+ function _seek($row)
+ {
+ return @odbtp_data_seek($this->_queryID, $row);
+ }
+
+ function fields($colname)
+ {
+ if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $name = @odbtp_field_name( $this->_queryID, $i );
+ $this->bind[strtoupper($name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _fetch_odbtp($type=0)
+ {
+ switch ($this->fetchMode) {
+ case ADODB_FETCH_NUM:
+ $this->fields = @odbtp_fetch_row($this->_queryID, $type);
+ break;
+ case ADODB_FETCH_ASSOC:
+ $this->fields = @odbtp_fetch_assoc($this->_queryID, $type);
+ break;
+ default:
+ $this->fields = @odbtp_fetch_array($this->_queryID, $type);
+ }
+ if ($this->databaseType = 'odbtp_vfp') {
+ if ($this->fields)
+ foreach($this->fields as $k => $v) {
+ if (strncmp($v,'1899-12-30',10) == 0) $this->fields[$k] = '';
+ }
+ }
+ return is_array($this->fields);
+ }
+
+ function _fetch()
+ {
+ return $this->_fetch_odbtp();
+ }
+
+ function MoveFirst()
+ {
+ if (!$this->_fetch_odbtp(ODB_FETCH_FIRST)) return false;
+ $this->EOF = false;
+ $this->_currentRow = 0;
+ return true;
+ }
+
+ function MoveLast()
+ {
+ if (!$this->_fetch_odbtp(ODB_FETCH_LAST)) return false;
+ $this->EOF = false;
+ $this->_currentRow = $this->_numOfRows - 1;
+ return true;
+ }
+
+ function NextRecordSet()
+ {
+ if (!@odbtp_next_result($this->_queryID)) return false;
+ $this->_inited = false;
+ $this->bind = false;
+ $this->_currentRow = -1;
+ $this->Init();
+ return true;
+ }
+
+ function _close()
+ {
+ return @odbtp_free_query($this->_queryID);
+ }
+}
+
+class ADORecordSet_odbtp_mssql extends ADORecordSet_odbtp {
+
+ var $databaseType = 'odbtp_mssql';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+class ADORecordSet_odbtp_access extends ADORecordSet_odbtp {
+
+ var $databaseType = 'odbtp_access';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+class ADORecordSet_odbtp_vfp extends ADORecordSet_odbtp {
+
+ var $databaseType = 'odbtp_vfp';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+class ADORecordSet_odbtp_oci8 extends ADORecordSet_odbtp {
+
+ var $databaseType = 'odbtp_oci8';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
+
+class ADORecordSet_odbtp_sybase extends ADORecordSet_odbtp {
+
+ var $databaseType = 'odbtp_sybase';
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-odbtp_unicode.inc.php b/vendor/adodb/adodb-php/drivers/adodb-odbtp_unicode.inc.php
new file mode 100644
index 0000000..b41b1fd
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-odbtp_unicode.inc.php
@@ -0,0 +1,35 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+ Latest version is available at http://adodb.org/
+*/
+
+// Code contributed by "Robert Twitty" <rtwitty#neutron.ushmm.org>
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+ Because the ODBTP server sends and reads UNICODE text data using UTF-8
+ encoding, the following HTML meta tag must be included within the HTML
+ head section of every HTML form and script page:
+
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+ Also, all SQL query strings must be submitted as UTF-8 encoded text.
+*/
+
+if (!defined('_ADODB_ODBTP_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbtp.inc.php");
+}
+
+class ADODB_odbtp_unicode extends ADODB_odbtp {
+ var $databaseType = 'odbtp';
+ var $_useUnicodeSQL = true;
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-oracle.inc.php b/vendor/adodb/adodb-php/drivers/adodb-oracle.inc.php
new file mode 100644
index 0000000..65e1050
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-oracle.inc.php
@@ -0,0 +1,343 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ Oracle data driver. Requires Oracle client. Works on Windows and Unix and Oracle 7.
+
+ If you are using Oracle 8 or later, use the oci8 driver which is much better and more reliable.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB_oracle extends ADOConnection {
+ var $databaseType = "oracle";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $concat_operator='||';
+ var $_curs;
+ var $_initdate = true; // init date to YYYY-MM-DD
+ var $metaTablesSQL = 'select table_name from cat';
+ var $metaColumnsSQL = "select cname,coltype,width from col where tname='%s' order by colno";
+ var $sysDate = "TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD')";
+ var $sysTimeStamp = 'SYSDATE';
+ var $connectSID = true;
+
+ function __construct()
+ {
+ }
+
+ // format and return date string in database date format
+ function DBDate($d, $isfld = false)
+ {
+ if (is_string($d)) $d = ADORecordSet::UnixDate($d);
+ if (is_object($d)) $ds = $d->format($this->fmtDate);
+ else $ds = adodb_date($this->fmtDate,$d);
+ return 'TO_DATE('.$ds.",'YYYY-MM-DD')";
+ }
+
+ // format and return date string in database timestamp format
+ function DBTimeStamp($ts, $isfld = false)
+ {
+
+ if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts);
+ if (is_object($ts)) $ds = $ts->format($this->fmtDate);
+ else $ds = adodb_date($this->fmtTimeStamp,$ts);
+ return 'TO_DATE('.$ds.",'RRRR-MM-DD, HH:MI:SS AM')";
+ }
+
+
+ function BindDate($d)
+ {
+ $d = ADOConnection::DBDate($d);
+ if (strncmp($d,"'",1)) return $d;
+
+ return substr($d,1,strlen($d)-2);
+ }
+
+ function BindTimeStamp($d)
+ {
+ $d = ADOConnection::DBTimeStamp($d);
+ if (strncmp($d,"'",1)) return $d;
+
+ return substr($d,1,strlen($d)-2);
+ }
+
+
+
+ function BeginTrans()
+ {
+ $this->autoCommit = false;
+ ora_commitoff($this->_connectionID);
+ return true;
+ }
+
+
+ function CommitTrans($ok=true)
+ {
+ if (!$ok) return $this->RollbackTrans();
+ $ret = ora_commit($this->_connectionID);
+ ora_commiton($this->_connectionID);
+ return $ret;
+ }
+
+
+ function RollbackTrans()
+ {
+ $ret = ora_rollback($this->_connectionID);
+ ora_commiton($this->_connectionID);
+ return $ret;
+ }
+
+
+ /* there seems to be a bug in the oracle extension -- always returns ORA-00000 - no error */
+ function ErrorMsg()
+ {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+
+ if (is_resource($this->_curs)) $this->_errorMsg = @ora_error($this->_curs);
+ if (empty($this->_errorMsg)) $this->_errorMsg = @ora_error($this->_connectionID);
+ return $this->_errorMsg;
+ }
+
+
+ function ErrorNo()
+ {
+ if ($this->_errorCode !== false) return $this->_errorCode;
+
+ if (is_resource($this->_curs)) $this->_errorCode = @ora_errorcode($this->_curs);
+ if (empty($this->_errorCode)) $this->_errorCode = @ora_errorcode($this->_connectionID);
+ return $this->_errorCode;
+ }
+
+
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename, $mode=0)
+ {
+ if (!function_exists('ora_plogon')) return null;
+
+ // <G. Giunta 2003/03/03/> Reset error messages before connecting
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ // G. Giunta 2003/08/13 - This looks danegrously suspicious: why should we want to set
+ // the oracle home to the host name of remote DB?
+// if ($argHostname) putenv("ORACLE_HOME=$argHostname");
+
+ if($argHostname) { // code copied from version submitted for oci8 by Jorma Tuomainen <jorma.tuomainen@ppoy.fi>
+ if (empty($argDatabasename)) $argDatabasename = $argHostname;
+ else {
+ if(strpos($argHostname,":")) {
+ $argHostinfo=explode(":",$argHostname);
+ $argHostname=$argHostinfo[0];
+ $argHostport=$argHostinfo[1];
+ } else {
+ $argHostport="1521";
+ }
+
+
+ if ($this->connectSID) {
+ $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+ .")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))";
+ } else
+ $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+ .")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))";
+ }
+
+ }
+
+ if ($argDatabasename) $argUsername .= "@$argDatabasename";
+
+ //if ($argHostname) print "<p>Connect: 1st argument should be left blank for $this->databaseType</p>";
+ if ($mode == 1)
+ $this->_connectionID = ora_plogon($argUsername,$argPassword);
+ else
+ $this->_connectionID = ora_logon($argUsername,$argPassword);
+ if ($this->_connectionID === false) return false;
+ if ($this->autoCommit) ora_commiton($this->_connectionID);
+ if ($this->_initdate) {
+ $rs = $this->_query("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD'");
+ if ($rs) ora_close($rs);
+ }
+
+ return true;
+ }
+
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, 1);
+ }
+
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ // <G. Giunta 2003/03/03/> Reset error messages before executing
+ $this->_errorMsg = false;
+ $this->_errorCode = false;
+
+ $curs = ora_open($this->_connectionID);
+
+ if ($curs === false) return false;
+ $this->_curs = $curs;
+ if (!ora_parse($curs,$sql)) return false;
+ if (ora_exec($curs)) return $curs;
+ // <G. Giunta 2004/03/03> before we close the cursor, we have to store the error message
+ // that we can obtain ONLY from the cursor (and not from the connection)
+ $this->_errorCode = @ora_errorcode($curs);
+ $this->_errorMsg = @ora_error($curs);
+ // </G. Giunta 2004/03/03>
+ @ora_close($curs);
+ return false;
+ }
+
+
+ // returns true or false
+ function _close()
+ {
+ return @ora_logoff($this->_connectionID);
+ }
+
+
+
+}
+
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_oracle extends ADORecordSet {
+
+ var $databaseType = "oracle";
+ var $bind = false;
+
+ function __construct($queryID,$mode=false)
+ {
+
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->fetchMode = $mode;
+
+ $this->_queryID = $queryID;
+
+ $this->_inited = true;
+ $this->fields = array();
+ if ($queryID) {
+ $this->_currentRow = 0;
+ $this->EOF = !$this->_fetch();
+ @$this->_initrs();
+ } else {
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ $this->EOF = true;
+ }
+
+ return $this->_queryID;
+ }
+
+
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved. */
+
+ function FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $fld->name = ora_columnname($this->_queryID, $fieldOffset);
+ $fld->type = ora_columntype($this->_queryID, $fieldOffset);
+ $fld->max_length = ora_columnsize($this->_queryID, $fieldOffset);
+ return $fld;
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _initrs()
+ {
+ $this->_numOfRows = -1;
+ $this->_numOfFields = @ora_numcols($this->_queryID);
+ }
+
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ function _fetch($ignore_fields=false) {
+// should remove call by reference, but ora_fetch_into requires it in 4.0.3pl1
+ if ($this->fetchMode & ADODB_FETCH_ASSOC)
+ return @ora_fetch_into($this->_queryID,$this->fields,ORA_FETCHINTO_NULLS|ORA_FETCHINTO_ASSOC);
+ else
+ return @ora_fetch_into($this->_queryID,$this->fields,ORA_FETCHINTO_NULLS);
+ }
+
+ /* close() only needs to be called if you are worried about using too much memory while your script
+ is running. All associated result memory for the specified result identifier will automatically be freed. */
+
+ function _close()
+{
+ return @ora_close($this->_queryID);
+ }
+
+ function MetaType($t, $len = -1, $fieldobj = false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+
+ switch (strtoupper($t)) {
+ case 'VARCHAR':
+ case 'VARCHAR2':
+ case 'CHAR':
+ case 'VARBINARY':
+ case 'BINARY':
+ if ($len <= $this->blobSize) return 'C';
+ case 'LONG':
+ case 'LONG VARCHAR':
+ case 'CLOB':
+ return 'X';
+ case 'LONG RAW':
+ case 'LONG VARBINARY':
+ case 'BLOB':
+ return 'B';
+
+ case 'DATE': return 'D';
+
+ //case 'T': return 'T';
+
+ case 'BIT': return 'L';
+ case 'INT':
+ case 'SMALLINT':
+ case 'INTEGER': return 'I';
+ default: return 'N';
+ }
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo.inc.php
new file mode 100644
index 0000000..cb4fbc4
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo.inc.php
@@ -0,0 +1,815 @@
+<?php
+/**
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Requires ODBC. Works on Windows and Unix.
+
+ Problems:
+ Where is float/decimal type in pdo_param_type
+ LOB handling for CLOB/BLOB differs significantly
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+
+/*
+enum pdo_param_type {
+PDO::PARAM_NULL, 0
+
+/* int as in long (the php native int type).
+ * If you mark a column as an int, PDO expects get_col to return
+ * a pointer to a long
+PDO::PARAM_INT, 1
+
+/* get_col ptr should point to start of the string buffer
+PDO::PARAM_STR, 2
+
+/* get_col: when len is 0 ptr should point to a php_stream *,
+ * otherwise it should behave like a string. Indicate a NULL field
+ * value by setting the ptr to NULL
+PDO::PARAM_LOB, 3
+
+/* get_col: will expect the ptr to point to a new PDOStatement object handle,
+ * but this isn't wired up yet
+PDO::PARAM_STMT, 4 /* hierarchical result set
+
+/* get_col ptr should point to a zend_bool
+PDO::PARAM_BOOL, 5
+
+
+/* magic flag to denote a parameter as being input/output
+PDO::PARAM_INPUT_OUTPUT = 0x80000000
+};
+*/
+
+function adodb_pdo_type($t)
+{
+ switch($t) {
+ case 2: return 'VARCHAR';
+ case 3: return 'BLOB';
+ default: return 'NUMERIC';
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+
+
+class ADODB_pdo extends ADOConnection {
+ var $databaseType = "pdo";
+ var $dataProvider = "pdo";
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $hasAffectedRows = true;
+ var $_bindInputArray = true;
+ var $_genIDSQL;
+ var $_genSeqSQL = "create table %s (id integer)";
+ var $_dropSeqSQL;
+ var $_autocommit = true;
+ var $_haserrorfunctions = true;
+ var $_lastAffectedRows = 0;
+
+ var $_errormsg = false;
+ var $_errorno = false;
+
+ var $dsnType = '';
+ var $stmt = false;
+ var $_driver;
+
+ function __construct()
+ {
+ }
+
+ function _UpdatePDO()
+ {
+ $d = $this->_driver;
+ $this->fmtDate = $d->fmtDate;
+ $this->fmtTimeStamp = $d->fmtTimeStamp;
+ $this->replaceQuote = $d->replaceQuote;
+ $this->sysDate = $d->sysDate;
+ $this->sysTimeStamp = $d->sysTimeStamp;
+ $this->random = $d->random;
+ $this->concat_operator = $d->concat_operator;
+ $this->nameQuote = $d->nameQuote;
+
+ $this->hasGenID = $d->hasGenID;
+ $this->_genIDSQL = $d->_genIDSQL;
+ $this->_genSeqSQL = $d->_genSeqSQL;
+ $this->_dropSeqSQL = $d->_dropSeqSQL;
+
+ $d->_init($this);
+ }
+
+ function Time()
+ {
+ if (!empty($this->_driver->_hasdual)) {
+ $sql = "select $this->sysTimeStamp from dual";
+ }
+ else {
+ $sql = "select $this->sysTimeStamp";
+ }
+
+ $rs = $this->_Execute($sql);
+ if ($rs && !$rs->EOF) {
+ return $this->UnixTimeStamp(reset($rs->fields));
+ }
+
+ return false;
+ }
+
+ // returns true or false
+ function _connect($argDSN, $argUsername, $argPassword, $argDatabasename, $persist=false)
+ {
+ $at = strpos($argDSN,':');
+ $this->dsnType = substr($argDSN,0,$at);
+
+ if ($argDatabasename) {
+ switch($this->dsnType){
+ case 'sqlsrv':
+ $argDSN .= ';database='.$argDatabasename;
+ break;
+ case 'mssql':
+ case 'mysql':
+ case 'oci':
+ case 'pgsql':
+ case 'sqlite':
+ default:
+ $argDSN .= ';dbname='.$argDatabasename;
+ }
+ }
+ try {
+ $this->_connectionID = new PDO($argDSN, $argUsername, $argPassword);
+ } catch (Exception $e) {
+ $this->_connectionID = false;
+ $this->_errorno = -1;
+ //var_dump($e);
+ $this->_errormsg = 'Connection attempt failed: '.$e->getMessage();
+ return false;
+ }
+
+ if ($this->_connectionID) {
+ switch(ADODB_ASSOC_CASE){
+ case ADODB_ASSOC_CASE_LOWER:
+ $m = PDO::CASE_LOWER;
+ break;
+ case ADODB_ASSOC_CASE_UPPER:
+ $m = PDO::CASE_UPPER;
+ break;
+ default:
+ case ADODB_ASSOC_CASE_NATIVE:
+ $m = PDO::CASE_NATURAL;
+ break;
+ }
+
+ //$this->_connectionID->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT );
+ $this->_connectionID->setAttribute(PDO::ATTR_CASE,$m);
+
+ $class = 'ADODB_pdo_'.$this->dsnType;
+ //$this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT,true);
+ switch($this->dsnType) {
+ case 'mssql':
+ case 'mysql':
+ case 'oci':
+ case 'pgsql':
+ case 'sqlite':
+ case 'sqlsrv':
+ include_once(ADODB_DIR.'/drivers/adodb-pdo_'.$this->dsnType.'.inc.php');
+ break;
+ }
+ if (class_exists($class)) {
+ $this->_driver = new $class();
+ }
+ else {
+ $this->_driver = new ADODB_pdo_base();
+ }
+
+ $this->_driver->_connectionID = $this->_connectionID;
+ $this->_UpdatePDO();
+ $this->_driver->database = $this->database;
+ return true;
+ }
+ $this->_driver = new ADODB_pdo_base();
+ return false;
+ }
+
+ function Concat()
+ {
+ $args = func_get_args();
+ if(method_exists($this->_driver, 'Concat')) {
+ return call_user_func_array(array($this->_driver, 'Concat'), $args);
+ }
+
+ if (PHP_VERSION >= 5.3) {
+ return call_user_func_array('parent::Concat', $args);
+ }
+ return call_user_func_array(array($this,'parent::Concat'), $args);
+ }
+
+ // returns true or false
+ function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
+ {
+ return $this->_connect($argDSN, $argUsername, $argPassword, $argDatabasename, true);
+ }
+
+ /*------------------------------------------------------------------------------*/
+
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $save = $this->_driver->fetchMode;
+ $this->_driver->fetchMode = $this->fetchMode;
+ $this->_driver->debug = $this->debug;
+ $ret = $this->_driver->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ $this->_driver->fetchMode = $save;
+ return $ret;
+ }
+
+
+ function ServerInfo()
+ {
+ return $this->_driver->ServerInfo();
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ return $this->_driver->MetaTables($ttype,$showSchema,$mask);
+ }
+
+ function MetaColumns($table,$normalize=true)
+ {
+ return $this->_driver->MetaColumns($table,$normalize);
+ }
+
+ function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false)
+ {
+ $obj = $stmt[1];
+ if ($type) {
+ $obj->bindParam($name, $var, $type, $maxLen);
+ }
+ else {
+ $obj->bindParam($name, $var);
+ }
+ }
+
+ function OffsetDate($dayFraction,$date=false)
+ {
+ return $this->_driver->OffsetDate($dayFraction,$date);
+ }
+
+ function SelectDB($dbName)
+ {
+ return $this->_driver->SelectDB($dbName);
+ }
+
+ function SQLDate($fmt, $col=false)
+ {
+ return $this->_driver->SQLDate($fmt, $col);
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_errormsg !== false) {
+ return $this->_errormsg;
+ }
+ if (!empty($this->_stmt)) {
+ $arr = $this->_stmt->errorInfo();
+ }
+ else if (!empty($this->_connectionID)) {
+ $arr = $this->_connectionID->errorInfo();
+ }
+ else {
+ return 'No Connection Established';
+ }
+
+ if ($arr) {
+ if (sizeof($arr)<2) {
+ return '';
+ }
+ if ((integer)$arr[0]) {
+ return $arr[2];
+ }
+ else {
+ return '';
+ }
+ }
+ else {
+ return '-1';
+ }
+ }
+
+
+ function ErrorNo()
+ {
+ if ($this->_errorno !== false) {
+ return $this->_errorno;
+ }
+ if (!empty($this->_stmt)) {
+ $err = $this->_stmt->errorCode();
+ }
+ else if (!empty($this->_connectionID)) {
+ $arr = $this->_connectionID->errorInfo();
+ if (isset($arr[0])) {
+ $err = $arr[0];
+ }
+ else {
+ $err = -1;
+ }
+ } else {
+ return 0;
+ }
+
+ if ($err == '00000') {
+ return 0; // allows empty check
+ }
+ return $err;
+ }
+
+ /**
+ * @param bool $auto_commit
+ * @return void
+ */
+ function SetAutoCommit($auto_commit)
+ {
+ if(method_exists($this->_driver, 'SetAutoCommit')) {
+ $this->_driver->SetAutoCommit($auto_commit);
+ }
+ }
+
+ function SetTransactionMode($transaction_mode)
+ {
+ if(method_exists($this->_driver, 'SetTransactionMode')) {
+ return $this->_driver->SetTransactionMode($transaction_mode);
+ }
+
+ return parent::SetTransactionMode($seqname);
+ }
+
+ function BeginTrans()
+ {
+ if(method_exists($this->_driver, 'BeginTrans')) {
+ return $this->_driver->BeginTrans();
+ }
+
+ if (!$this->hasTransactions) {
+ return false;
+ }
+ if ($this->transOff) {
+ return true;
+ }
+ $this->transCnt += 1;
+ $this->_autocommit = false;
+ $this->SetAutoCommit(false);
+
+ return $this->_connectionID->beginTransaction();
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if(method_exists($this->_driver, 'CommitTrans')) {
+ return $this->_driver->CommitTrans($ok);
+ }
+
+ if (!$this->hasTransactions) {
+ return false;
+ }
+ if ($this->transOff) {
+ return true;
+ }
+ if (!$ok) {
+ return $this->RollbackTrans();
+ }
+ if ($this->transCnt) {
+ $this->transCnt -= 1;
+ }
+ $this->_autocommit = true;
+
+ $ret = $this->_connectionID->commit();
+ $this->SetAutoCommit(true);
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ if(method_exists($this->_driver, 'RollbackTrans')) {
+ return $this->_driver->RollbackTrans();
+ }
+
+ if (!$this->hasTransactions) {
+ return false;
+ }
+ if ($this->transOff) {
+ return true;
+ }
+ if ($this->transCnt) {
+ $this->transCnt -= 1;
+ }
+ $this->_autocommit = true;
+
+ $ret = $this->_connectionID->rollback();
+ $this->SetAutoCommit(true);
+ return $ret;
+ }
+
+ function Prepare($sql)
+ {
+ $this->_stmt = $this->_connectionID->prepare($sql);
+ if ($this->_stmt) {
+ return array($sql,$this->_stmt);
+ }
+
+ return false;
+ }
+
+ function PrepareStmt($sql)
+ {
+ $stmt = $this->_connectionID->prepare($sql);
+ if (!$stmt) {
+ return false;
+ }
+ $obj = new ADOPDOStatement($stmt,$this);
+ return $obj;
+ }
+
+ function CreateSequence($seqname='adodbseq',$startID=1)
+ {
+ if(method_exists($this->_driver, 'CreateSequence')) {
+ return $this->_driver->CreateSequence($seqname, $startID);
+ }
+
+ return parent::CreateSequence($seqname, $startID);
+ }
+
+ function DropSequence($seqname='adodbseq')
+ {
+ if(method_exists($this->_driver, 'DropSequence')) {
+ return $this->_driver->DropSequence($seqname);
+ }
+
+ return parent::DropSequence($seqname);
+ }
+
+ function GenID($seqname='adodbseq',$startID=1)
+ {
+ if(method_exists($this->_driver, 'GenID')) {
+ return $this->_driver->GenID($seqname, $startID);
+ }
+
+ return parent::GenID($seqname, $startID);
+ }
+
+
+ /* returns queryID or false */
+ function _query($sql,$inputarr=false)
+ {
+ $ok = false;
+ if (is_array($sql)) {
+ $stmt = $sql[1];
+ } else {
+ $stmt = $this->_connectionID->prepare($sql);
+ }
+ #adodb_backtrace();
+ #var_dump($this->_bindInputArray);
+ if ($stmt) {
+ $this->_driver->debug = $this->debug;
+ if ($inputarr) {
+ $ok = $stmt->execute($inputarr);
+ }
+ else {
+ $ok = $stmt->execute();
+ }
+ }
+
+
+ $this->_errormsg = false;
+ $this->_errorno = false;
+
+ if ($ok) {
+ $this->_stmt = $stmt;
+ return $stmt;
+ }
+
+ if ($stmt) {
+
+ $arr = $stmt->errorinfo();
+ if ((integer)$arr[1]) {
+ $this->_errormsg = $arr[2];
+ $this->_errorno = $arr[1];
+ }
+
+ } else {
+ $this->_errormsg = false;
+ $this->_errorno = false;
+ }
+ return false;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ $this->_stmt = false;
+ return true;
+ }
+
+ function _affectedrows()
+ {
+ return ($this->_stmt) ? $this->_stmt->rowCount() : 0;
+ }
+
+ function _insertid()
+ {
+ return ($this->_connectionID) ? $this->_connectionID->lastInsertId() : 0;
+ }
+
+ /**
+ * Quotes a string to be sent to the database.
+ * If we have an active connection, delegates quoting to the underlying
+ * PDO object. Otherwise, replace "'" by the value of $replaceQuote (same
+ * behavior as mysqli driver)
+ * @param string $s The string to quote
+ * @param boolean $magic_quotes If false, use PDO::quote().
+ * @return string Quoted string
+ */
+ function qstr($s, $magic_quotes = false)
+ {
+ if (!$magic_quotes) {
+ if ($this->_connectionID) {
+ return $this->_connectionID->quote($s);
+ }
+ return "'" . str_replace("'", $this->replaceQuote, $s) . "'";
+ }
+
+ // undo magic quotes for "
+ $s = str_replace('\\"', '"', $s);
+ return "'$s'";
+ }
+
+}
+
+class ADODB_pdo_base extends ADODB_pdo {
+
+ var $sysDate = "'?'";
+ var $sysTimeStamp = "'?'";
+
+
+ function _init($parentDriver)
+ {
+ $parentDriver->_bindInputArray = true;
+ #$parentDriver->_connectionID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
+ }
+
+ function ServerInfo()
+ {
+ return ADOConnection::ServerInfo();
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ return $ret;
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ return false;
+ }
+
+ function MetaColumns($table,$normalize=true)
+ {
+ return false;
+ }
+}
+
+class ADOPDOStatement {
+
+ var $databaseType = "pdo";
+ var $dataProvider = "pdo";
+ var $_stmt;
+ var $_connectionID;
+
+ function __construct($stmt,$connection)
+ {
+ $this->_stmt = $stmt;
+ $this->_connectionID = $connection;
+ }
+
+ function Execute($inputArr=false)
+ {
+ $savestmt = $this->_connectionID->_stmt;
+ $rs = $this->_connectionID->Execute(array(false,$this->_stmt),$inputArr);
+ $this->_connectionID->_stmt = $savestmt;
+ return $rs;
+ }
+
+ function InParameter(&$var,$name,$maxLen=4000,$type=false)
+ {
+
+ if ($type) {
+ $this->_stmt->bindParam($name,$var,$type,$maxLen);
+ }
+ else {
+ $this->_stmt->bindParam($name, $var);
+ }
+ }
+
+ function Affected_Rows()
+ {
+ return ($this->_stmt) ? $this->_stmt->rowCount() : 0;
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_stmt) {
+ $arr = $this->_stmt->errorInfo();
+ }
+ else {
+ $arr = $this->_connectionID->errorInfo();
+ }
+
+ if (is_array($arr)) {
+ if ((integer) $arr[0] && isset($arr[2])) {
+ return $arr[2];
+ }
+ else {
+ return '';
+ }
+ } else {
+ return '-1';
+ }
+ }
+
+ function NumCols()
+ {
+ return ($this->_stmt) ? $this->_stmt->columnCount() : 0;
+ }
+
+ function ErrorNo()
+ {
+ if ($this->_stmt) {
+ return $this->_stmt->errorCode();
+ }
+ else {
+ return $this->_connectionID->errorInfo();
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_pdo extends ADORecordSet {
+
+ var $bind = false;
+ var $databaseType = "pdo";
+ var $dataProvider = "pdo";
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ $this->adodbFetchMode = $mode;
+ switch($mode) {
+ case ADODB_FETCH_NUM: $mode = PDO::FETCH_NUM; break;
+ case ADODB_FETCH_ASSOC: $mode = PDO::FETCH_ASSOC; break;
+
+ case ADODB_FETCH_BOTH:
+ default: $mode = PDO::FETCH_BOTH; break;
+ }
+ $this->fetchMode = $mode;
+
+ $this->_queryID = $id;
+ parent::__construct($id);
+ }
+
+
+ function Init()
+ {
+ if ($this->_inited) {
+ return;
+ }
+ $this->_inited = true;
+ if ($this->_queryID) {
+ @$this->_initrs();
+ }
+ else {
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ }
+ if ($this->_numOfRows != 0 && $this->_currentRow == -1) {
+ $this->_currentRow = 0;
+ if ($this->EOF = ($this->_fetch() === false)) {
+ $this->_numOfRows = 0; // _numOfRows could be -1
+ }
+ } else {
+ $this->EOF = true;
+ }
+ }
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+
+ $this->_numOfRows = ($ADODB_COUNTRECS) ? @$this->_queryID->rowCount() : -1;
+ if (!$this->_numOfRows) {
+ $this->_numOfRows = -1;
+ }
+ $this->_numOfFields = $this->_queryID->columnCount();
+ }
+
+ // returns the field object
+ function FetchField($fieldOffset = -1)
+ {
+ $off=$fieldOffset+1; // offsets begin at 1
+
+ $o= new ADOFieldObject();
+ $arr = @$this->_queryID->getColumnMeta($fieldOffset);
+ if (!$arr) {
+ $o->name = 'bad getColumnMeta()';
+ $o->max_length = -1;
+ $o->type = 'VARCHAR';
+ $o->precision = 0;
+ # $false = false;
+ return $o;
+ }
+ //adodb_pr($arr);
+ $o->name = $arr['name'];
+ if (isset($arr['sqlsrv:decl_type']) && $arr['sqlsrv:decl_type'] <> "null")
+ {
+ /*
+ * If the database is SQL server, use the native built-ins
+ */
+ $o->type = $arr['sqlsrv:decl_type'];
+ }
+ elseif (isset($arr['native_type']) && $arr['native_type'] <> "null")
+ {
+ $o->type = $arr['native_type'];
+ }
+ else
+ {
+ $o->type = adodb_pdo_type($arr['pdo_type']);
+ }
+
+ $o->max_length = $arr['len'];
+ $o->precision = $arr['precision'];
+
+ switch(ADODB_ASSOC_CASE) {
+ case ADODB_ASSOC_CASE_LOWER:
+ $o->name = strtolower($o->name);
+ break;
+ case ADODB_ASSOC_CASE_UPPER:
+ $o->name = strtoupper($o->name);
+ break;
+ }
+ return $o;
+ }
+
+ function _seek($row)
+ {
+ return false;
+ }
+
+ function _fetch()
+ {
+ if (!$this->_queryID) {
+ return false;
+ }
+
+ $this->fields = $this->_queryID->fetch($this->fetchMode);
+ return !empty($this->fields);
+ }
+
+ function _close()
+ {
+ $this->_queryID = false;
+ }
+
+ function Fields($colname)
+ {
+ if ($this->adodbFetchMode != ADODB_FETCH_NUM) {
+ return @$this->fields[$colname];
+ }
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_mssql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_mssql.inc.php
new file mode 100644
index 0000000..3f5e1d5
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_mssql.inc.php
@@ -0,0 +1,62 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+*/
+
+class ADODB_pdo_mssql extends ADODB_pdo {
+
+ var $hasTop = 'top';
+ var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ var $sysTimeStamp = 'GetDate()';
+
+
+ function _init($parentDriver)
+ {
+
+ $parentDriver->hasTransactions = false; ## <<< BUG IN PDO mssql driver
+ $parentDriver->_bindInputArray = false;
+ $parentDriver->hasInsertID = true;
+ }
+
+ function ServerInfo()
+ {
+ return ADOConnection::ServerInfo();
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ return $ret;
+ }
+
+ function SetTransactionMode( $transaction_mode )
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ return;
+ }
+ if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+ $this->Execute("SET TRANSACTION ".$transaction_mode);
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ return false;
+ }
+
+ function MetaColumns($table,$normalize=true)
+ {
+ return false;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_mysql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_mysql.inc.php
new file mode 100644
index 0000000..f873e14
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_mysql.inc.php
@@ -0,0 +1,313 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+*/
+
+class ADODB_pdo_mysql extends ADODB_pdo {
+
+ var $metaTablesSQL = "SELECT
+ TABLE_NAME,
+ CASE WHEN TABLE_TYPE = 'VIEW' THEN 'V' ELSE 'T' END
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA=";
+ var $metaColumnsSQL = "SHOW COLUMNS FROM `%s`";
+ var $sysDate = 'CURDATE()';
+ var $sysTimeStamp = 'NOW()';
+ var $hasGenID = true;
+ var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
+ var $_dropSeqSQL = "drop table %s";
+ var $fmtTimeStamp = "'Y-m-d, H:i:s'";
+ var $nameQuote = '`';
+
+ function _init($parentDriver)
+ {
+ $parentDriver->hasTransactions = false;
+ #$parentDriver->_bindInputArray = false;
+ $parentDriver->hasInsertID = true;
+ $parentDriver->_connectionID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+ }
+
+ // dayFraction is a day in floating point
+ function OffsetDate($dayFraction, $date=false)
+ {
+ if (!$date) {
+ $date = $this->sysDate;
+ }
+
+ $fraction = $dayFraction * 24 * 3600;
+ return $date . ' + INTERVAL ' . $fraction . ' SECOND';
+// return "from_unixtime(unix_timestamp($date)+$fraction)";
+ }
+
+ function Concat()
+ {
+ $s = '';
+ $arr = func_get_args();
+
+ // suggestion by andrew005#mnogo.ru
+ $s = implode(',', $arr);
+ if (strlen($s) > 0) {
+ return "CONCAT($s)";
+ }
+ return '';
+ }
+
+ function ServerInfo()
+ {
+ $arr['description'] = ADOConnection::GetOne('select version()');
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function MetaTables($ttype=false, $showSchema=false, $mask=false)
+ {
+ $save = $this->metaTablesSQL;
+ if ($showSchema && is_string($showSchema)) {
+ $this->metaTablesSQL .= $this->qstr($showSchema);
+ } else {
+ $this->metaTablesSQL .= 'schema()';
+ }
+
+ if ($mask) {
+ $mask = $this->qstr($mask);
+ $this->metaTablesSQL .= " like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype, $showSchema);
+
+ $this->metaTablesSQL = $save;
+ return $ret;
+ }
+
+ /**
+ * @param bool $auto_commit
+ * @return void
+ */
+ function SetAutoCommit($auto_commit)
+ {
+ $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT, $auto_commit);
+ }
+
+ function SetTransactionMode($transaction_mode)
+ {
+ $this->_transmode = $transaction_mode;
+ if (empty($transaction_mode)) {
+ $this->Execute('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
+ return;
+ }
+ if (!stristr($transaction_mode, 'isolation')) {
+ $transaction_mode = 'ISOLATION LEVEL ' . $transaction_mode;
+ }
+ $this->Execute('SET SESSION TRANSACTION ' . $transaction_mode);
+ }
+
+ function MetaColumns($table, $normalize=true)
+ {
+ $this->_findschema($table, $schema);
+ if ($schema) {
+ $dbName = $this->database;
+ $this->SelectDB($schema);
+ }
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL, $table));
+
+ if ($schema) {
+ $this->SelectDB($dbName);
+ }
+
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $retarr = array();
+ while (!$rs->EOF){
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $type = $rs->fields[1];
+
+ // split type into type(length):
+ $fld->scale = null;
+ if (preg_match('/^(.+)\((\d+),(\d+)/', $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1;
+ } elseif (preg_match('/^(.+)\((\d+)/', $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
+ } elseif (preg_match('/^(enum)\((.*)\)$/i', $type, $query_array)) {
+ $fld->type = $query_array[1];
+ $arr = explode(',', $query_array[2]);
+ $fld->enums = $arr;
+ $zlen = max(array_map('strlen', $arr)) - 2; // PHP >= 4.0.6
+ $fld->max_length = ($zlen > 0) ? $zlen : 1;
+ } else {
+ $fld->type = $type;
+ $fld->max_length = -1;
+ }
+ $fld->not_null = ($rs->fields[2] != 'YES');
+ $fld->primary_key = ($rs->fields[3] == 'PRI');
+ $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
+ $fld->binary = (strpos($type, 'blob') !== false);
+ $fld->unsigned = (strpos($type, 'unsigned') !== false);
+
+ if (!$fld->binary) {
+ $d = $rs->fields[4];
+ if ($d != '' && $d != 'NULL') {
+ $fld->has_default = true;
+ $fld->default_value = $d;
+ } else {
+ $fld->has_default = false;
+ }
+ }
+
+ if ($save == ADODB_FETCH_NUM) {
+ $retarr[] = $fld;
+ } else {
+ $retarr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+ return $retarr;
+ }
+
+ // returns true or false
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ $try = $this->Execute('use ' . $dbName);
+ return ($try !== false);
+ }
+
+ // parameters use PostgreSQL convention, not MySQL
+ function SelectLimit($sql, $nrows=-1, $offset=-1, $inputarr=false, $secs=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr =($offset>=0) ? "$offset," : '';
+ // jason judge, see http://phplens.com/lens/lensforum/msgs.php?id=9220
+ if ($nrows < 0) {
+ $nrows = '18446744073709551615';
+ }
+
+ if ($secs) {
+ $rs = $this->CacheExecute($secs, $sql . " LIMIT $offsetStr$nrows", $inputarr);
+ } else {
+ $rs = $this->Execute($sql . " LIMIT $offsetStr$nrows", $inputarr);
+ }
+ return $rs;
+ }
+
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) {
+ $col = $this->sysTimeStamp;
+ }
+ $s = 'DATE_FORMAT(' . $col . ",'";
+ $concat = false;
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ $ch = $fmt[$i];
+ switch($ch) {
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt, $i, 1);
+ }
+ // FALL THROUGH
+ case '-':
+ case '/':
+ $s .= $ch;
+ break;
+
+ case 'Y':
+ case 'y':
+ $s .= '%Y';
+ break;
+
+ case 'M':
+ $s .= '%b';
+ break;
+
+ case 'm':
+ $s .= '%m';
+ break;
+
+ case 'D':
+ case 'd':
+ $s .= '%d';
+ break;
+
+ case 'Q':
+ case 'q':
+ $s .= "'),Quarter($col)";
+
+ if ($len > $i+1) {
+ $s .= ",DATE_FORMAT($col,'";
+ } else {
+ $s .= ",('";
+ }
+ $concat = true;
+ break;
+
+ case 'H':
+ $s .= '%H';
+ break;
+
+ case 'h':
+ $s .= '%I';
+ break;
+
+ case 'i':
+ $s .= '%i';
+ break;
+
+ case 's':
+ $s .= '%s';
+ break;
+
+ case 'a':
+ case 'A':
+ $s .= '%p';
+ break;
+
+ case 'w':
+ $s .= '%w';
+ break;
+
+ case 'W':
+ $s .= '%U';
+ break;
+
+ case 'l':
+ $s .= '%W';
+ break;
+ }
+ }
+ $s .= "')";
+ if ($concat) {
+ $s = "CONCAT($s)";
+ }
+ return $s;
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_oci.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_oci.inc.php
new file mode 100644
index 0000000..ae6a1f1
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_oci.inc.php
@@ -0,0 +1,102 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+*/
+
+class ADODB_pdo_oci extends ADODB_pdo_base {
+
+ var $concat_operator='||';
+ var $sysDate = "TRUNC(SYSDATE)";
+ var $sysTimeStamp = 'SYSDATE';
+ var $NLS_DATE_FORMAT = 'YYYY-MM-DD'; // To include time, use 'RRRR-MM-DD HH24:MI:SS'
+ var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)";
+ var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW')";
+ var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno";
+
+ var $_initdate = true;
+ var $_hasdual = true;
+
+ function _init($parentDriver)
+ {
+ $parentDriver->_bindInputArray = true;
+ $parentDriver->_nestedSQL = true;
+ if ($this->_initdate) {
+ $parentDriver->Execute("ALTER SESSION SET NLS_DATE_FORMAT='".$this->NLS_DATE_FORMAT."'");
+ }
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(strtoupper($mask));
+ $this->metaTablesSQL .= " AND table_name like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+ function MetaColumns($table,$normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ if (!$rs) {
+ return $false;
+ }
+ $retarr = array();
+ while (!$rs->EOF) { //print_r($rs->fields);
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+ $fld->scale = $rs->fields[3];
+ if ($rs->fields[1] == 'NUMBER' && $rs->fields[3] == 0) {
+ $fld->type ='INT';
+ $fld->max_length = $rs->fields[4];
+ }
+ $fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0);
+ $fld->binary = (strpos($fld->type,'BLOB') !== false);
+ $fld->default_value = $rs->fields[6];
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[strtoupper($fld->name)] = $fld;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if (empty($retarr))
+ return $false;
+ else
+ return $retarr;
+ }
+
+ /**
+ * @param bool $auto_commit
+ * @return void
+ */
+ function SetAutoCommit($auto_commit)
+ {
+ $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT, $auto_commit);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_pgsql.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_pgsql.inc.php
new file mode 100644
index 0000000..1146b43
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_pgsql.inc.php
@@ -0,0 +1,232 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+*/
+
+class ADODB_pdo_pgsql extends ADODB_pdo {
+ var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1";
+ var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
+ and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages',
+ 'sql_packages', 'sql_sizing', 'sql_sizing_profiles')
+ union
+ select viewname,'V' from pg_views where viewname not like 'pg\_%'";
+ //"select tablename from pg_tables where tablename not like 'pg_%' order by 1";
+ var $isoDates = true; // accepts dates in ISO format
+ var $sysDate = "CURRENT_DATE";
+ var $sysTimeStamp = "CURRENT_TIMESTAMP";
+ var $blobEncodeType = 'C';
+ var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum
+ FROM pg_class c, pg_attribute a,pg_type t
+ WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%'
+AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+
+ // used when schema defined
+ var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
+FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n
+WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s'))
+ and c.relnamespace=n.oid and n.nspname='%s'
+ and a.attname not like '....%%' AND a.attnum > 0
+ AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+
+ // get primary key etc -- from Freek Dijkstra
+ var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key
+ FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) AND a.attrelid = bc.oid AND bc.relname = '%s'";
+
+ var $hasAffectedRows = true;
+ var $hasLimit = false; // set to true for pgsql 7 only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10
+ // below suggested by Freek Dijkstra
+ var $true = 't'; // string that represents TRUE for a database
+ var $false = 'f'; // string that represents FALSE for a database
+ var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database
+ var $fmtTimeStamp = "'Y-m-d G:i:s'"; // used by DBTimeStamp as the default timestamp fmt.
+ var $hasMoveFirst = true;
+ var $hasGenID = true;
+ var $_genIDSQL = "SELECT NEXTVAL('%s')";
+ var $_genSeqSQL = "CREATE SEQUENCE %s START %s";
+ var $_dropSeqSQL = "DROP SEQUENCE %s";
+ var $metaDefaultsSQL = "SELECT d.adnum as num, d.adsrc as def from pg_attrdef d, pg_class c where d.adrelid=c.oid and c.relname='%s' order by d.adnum";
+ var $random = 'random()'; /// random function
+ var $concat_operator='||';
+
+ function _init($parentDriver)
+ {
+
+ $parentDriver->hasTransactions = false; ## <<< BUG IN PDO pgsql driver
+ $parentDriver->hasInsertID = true;
+ $parentDriver->_nestedSQL = true;
+ }
+
+ function ServerInfo()
+ {
+ $arr['description'] = ADOConnection::GetOne("select version()");
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ return $arr;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr = ($offset >= 0) ? " OFFSET $offset" : '';
+ $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : '';
+ if ($secs2cache)
+ $rs = $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr);
+ else
+ $rs = $this->Execute($sql."$limitStr$offsetStr",$inputarr);
+
+ return $rs;
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $info = $this->ServerInfo();
+ if ($info['version'] >= 7.3) {
+ $this->metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
+ and schemaname not in ( 'pg_catalog','information_schema')
+ union
+ select viewname,'V' from pg_views where viewname not like 'pg\_%' and schemaname not in ( 'pg_catalog','information_schema') ";
+ }
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(strtolower($mask));
+ if ($info['version']>=7.3)
+ $this->metaTablesSQL = "
+select tablename,'T' from pg_tables where tablename like $mask and schemaname not in ( 'pg_catalog','information_schema')
+ union
+select viewname,'V' from pg_views where viewname like $mask and schemaname not in ( 'pg_catalog','information_schema') ";
+ else
+ $this->metaTablesSQL = "
+select tablename,'T' from pg_tables where tablename like $mask
+ union
+select viewname,'V' from pg_views where viewname like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+ function MetaColumns($table,$normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = false;
+ $this->_findschema($table,$schema);
+
+ if ($normalize) $table = strtolower($table);
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+
+ if ($schema) $rs = $this->Execute(sprintf($this->metaColumnsSQL1,$table,$table,$schema));
+ else $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table,$table));
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rs === false) {
+ $false = false;
+ return $false;
+ }
+ if (!empty($this->metaKeySQL)) {
+ // If we want the primary keys, we have to issue a separate query
+ // Of course, a modified version of the metaColumnsSQL query using a
+ // LEFT JOIN would have been much more elegant, but postgres does
+ // not support OUTER JOINS. So here is the clumsy way.
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+ $rskey = $this->Execute(sprintf($this->metaKeySQL,($table)));
+ // fetch all result in once for performance.
+ $keys = $rskey->GetArray();
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ $rskey->Close();
+ unset($rskey);
+ }
+
+ $rsdefa = array();
+ if (!empty($this->metaDefaultsSQL)) {
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $sql = sprintf($this->metaDefaultsSQL, ($table));
+ $rsdef = $this->Execute($sql);
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rsdef) {
+ while (!$rsdef->EOF) {
+ $num = $rsdef->fields['num'];
+ $s = $rsdef->fields['def'];
+ if (strpos($s,'::')===false && substr($s, 0, 1) == "'") { /* quoted strings hack... for now... fixme */
+ $s = substr($s, 1);
+ $s = substr($s, 0, strlen($s) - 1);
+ }
+
+ $rsdefa[$num] = $s;
+ $rsdef->MoveNext();
+ }
+ } else {
+ ADOConnection::outp( "==> SQL => " . $sql);
+ }
+ unset($rsdef);
+ }
+
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+ if ($fld->max_length <= 0) $fld->max_length = $rs->fields[3]-4;
+ if ($fld->max_length <= 0) $fld->max_length = -1;
+ if ($fld->type == 'numeric') {
+ $fld->scale = $fld->max_length & 0xFFFF;
+ $fld->max_length >>= 16;
+ }
+ // dannym
+ // 5 hasdefault; 6 num-of-column
+ $fld->has_default = ($rs->fields[5] == 't');
+ if ($fld->has_default) {
+ $fld->default_value = $rsdefa[$rs->fields[6]];
+ }
+
+ //Freek
+ if ($rs->fields[4] == $this->true) {
+ $fld->not_null = true;
+ }
+
+ // Freek
+ if (is_array($keys)) {
+ foreach($keys as $key) {
+ if ($fld->name == $key['column_name'] AND $key['primary_key'] == $this->true)
+ $fld->primary_key = true;
+ if ($fld->name == $key['column_name'] AND $key['unique_key'] == $this->true)
+ $fld->unique = true; // What name is more compatible?
+ }
+ }
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[($normalize) ? strtoupper($fld->name) : $fld->name] = $fld;
+
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if (empty($retarr)) {
+ $false = false;
+ return $false;
+ } else return $retarr;
+
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlite.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlite.inc.php
new file mode 100644
index 0000000..2d2f8be
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlite.inc.php
@@ -0,0 +1,206 @@
+<?php
+
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Thanks Diogo Toscano (diogo#scriptcase.net) for the code.
+ And also Sid Dunayer [sdunayer#interserv.com] for extensive fixes.
+*/
+
+class ADODB_pdo_sqlite extends ADODB_pdo {
+ var $metaTablesSQL = "SELECT name FROM sqlite_master WHERE type='table'";
+ var $sysDate = 'current_date';
+ var $sysTimeStamp = 'current_timestamp';
+ var $nameQuote = '`';
+ var $replaceQuote = "''";
+ var $hasGenID = true;
+ var $_genIDSQL = "UPDATE %s SET id=id+1 WHERE id=%s";
+ var $_genSeqSQL = "CREATE TABLE %s (id integer)";
+ var $_genSeqCountSQL = 'SELECT COUNT(*) FROM %s';
+ var $_genSeq2SQL = 'INSERT INTO %s VALUES(%s)';
+ var $_dropSeqSQL = 'DROP TABLE %s';
+ var $concat_operator = '||';
+ var $pdoDriver = false;
+ var $random='abs(random())';
+
+ function _init($parentDriver)
+ {
+ $this->pdoDriver = $parentDriver;
+ $parentDriver->_bindInputArray = true;
+ $parentDriver->hasTransactions = false; // // should be set to false because of PDO SQLite driver not supporting changing autocommit mode
+ $parentDriver->hasInsertID = true;
+ }
+
+ function ServerInfo()
+ {
+ $parent = $this->pdoDriver;
+ @($ver = array_pop($parent->GetCol("SELECT sqlite_version()")));
+ @($enc = array_pop($parent->GetCol("PRAGMA encoding")));
+
+ $arr['version'] = $ver;
+ $arr['description'] = 'SQLite ';
+ $arr['encoding'] = $enc;
+
+ return $arr;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $parent = $this->pdoDriver;
+ $offsetStr = ($offset >= 0) ? " OFFSET $offset" : '';
+ $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ($offset >= 0 ? ' LIMIT 999999999' : '');
+ if ($secs2cache)
+ $rs = $parent->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr);
+ else
+ $rs = $parent->Execute($sql."$limitStr$offsetStr",$inputarr);
+
+ return $rs;
+ }
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ $parent = $this->pdoDriver;
+ // if you have to modify the parameter below, your database is overloaded,
+ // or you need to implement generation of id's yourself!
+ $MAXLOOPS = 100;
+ while (--$MAXLOOPS>=0) {
+ @($num = array_pop($parent->GetCol("SELECT id FROM {$seq}")));
+ if ($num === false || !is_numeric($num)) {
+ @$parent->Execute(sprintf($this->_genSeqSQL ,$seq));
+ $start -= 1;
+ $num = '0';
+ $cnt = $parent->GetOne(sprintf($this->_genSeqCountSQL,$seq));
+ if (!$cnt) {
+ $ok = $parent->Execute(sprintf($this->_genSeq2SQL,$seq,$start));
+ }
+ if (!$ok) return false;
+ }
+ $parent->Execute(sprintf($this->_genIDSQL,$seq,$num));
+
+ if ($parent->affected_rows() > 0) {
+ $num += 1;
+ $parent->genID = intval($num);
+ return intval($num);
+ }
+ }
+ if ($fn = $parent->raiseErrorFn) {
+ $fn($parent->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
+ }
+ return false;
+ }
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ $parent = $this->pdoDriver;
+ $ok = $parent->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) return false;
+ $start -= 1;
+ return $parent->Execute("insert into $seqname values($start)");
+ }
+
+ function SetTransactionMode($transaction_mode)
+ {
+ $parent = $this->pdoDriver;
+ $parent->_transmode = strtoupper($transaction_mode);
+ }
+
+ function BeginTrans()
+ {
+ $parent = $this->pdoDriver;
+ if ($parent->transOff) return true;
+ $parent->transCnt += 1;
+ $parent->_autocommit = false;
+ return $parent->Execute("BEGIN {$parent->_transmode}");
+ }
+
+ function CommitTrans($ok=true)
+ {
+ $parent = $this->pdoDriver;
+ if ($parent->transOff) return true;
+ if (!$ok) return $parent->RollbackTrans();
+ if ($parent->transCnt) $parent->transCnt -= 1;
+ $parent->_autocommit = true;
+
+ $ret = $parent->Execute('COMMIT');
+ return $ret;
+ }
+
+ function RollbackTrans()
+ {
+ $parent = $this->pdoDriver;
+ if ($parent->transOff) return true;
+ if ($parent->transCnt) $parent->transCnt -= 1;
+ $parent->_autocommit = true;
+
+ $ret = $parent->Execute('ROLLBACK');
+ return $ret;
+ }
+
+
+ // mark newnham
+ function MetaColumns($tab,$normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $parent = $this->pdoDriver;
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($parent->fetchMode !== false) $savem = $parent->SetFetchMode(false);
+ $rs = $parent->Execute("PRAGMA table_info('$tab')");
+ if (isset($savem)) $parent->SetFetchMode($savem);
+ if (!$rs) {
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+ $arr = array();
+ while ($r = $rs->FetchRow()) {
+ $type = explode('(',$r['type']);
+ $size = '';
+ if (sizeof($type)==2)
+ $size = trim($type[1],')');
+ $fn = strtoupper($r['name']);
+ $fld = new ADOFieldObject;
+ $fld->name = $r['name'];
+ $fld->type = $type[0];
+ $fld->max_length = $size;
+ $fld->not_null = $r['notnull'];
+ $fld->primary_key = $r['pk'];
+ $fld->default_value = $r['dflt_value'];
+ $fld->scale = 0;
+ if ($save == ADODB_FETCH_NUM) $arr[] = $fld;
+ else $arr[strtoupper($fld->name)] = $fld;
+ }
+ $rs->Close();
+ $ADODB_FETCH_MODE = $save;
+ return $arr;
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $parent = $this->pdoDriver;
+
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(strtoupper($mask));
+ $this->metaTablesSQL .= " AND name LIKE $mask";
+ }
+
+ $ret = $parent->GetCol($this->metaTablesSQL);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlsrv.inc.php b/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlsrv.inc.php
new file mode 100644
index 0000000..869e8e1
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-pdo_sqlsrv.inc.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Provided by Ned Andre to support sqlsrv library
+ */
+class ADODB_pdo_sqlsrv extends ADODB_pdo
+{
+
+ var $hasTop = 'top';
+ var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
+ var $sysTimeStamp = 'GetDate()';
+
+ function _init(ADODB_pdo $parentDriver)
+ {
+ $parentDriver->hasTransactions = true;
+ $parentDriver->_bindInputArray = true;
+ $parentDriver->hasInsertID = true;
+ $parentDriver->fmtTimeStamp = "'Y-m-d H:i:s'";
+ $parentDriver->fmtDate = "'Y-m-d'";
+ }
+
+ function BeginTrans()
+ {
+ $returnval = parent::BeginTrans();
+ return $returnval;
+ }
+
+ function MetaColumns($table, $normalize = true)
+ {
+ return false;
+ }
+
+ function MetaTables($ttype = false, $showSchema = false, $mask = false)
+ {
+ return false;
+ }
+
+ function SelectLimit($sql, $nrows = -1, $offset = -1, $inputarr = false, $secs2cache = 0)
+ {
+ $ret = ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+ return $ret;
+ }
+
+ function ServerInfo()
+ {
+ return ADOConnection::ServerInfo();
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-postgres.inc.php b/vendor/adodb/adodb-php/drivers/adodb-postgres.inc.php
new file mode 100644
index 0000000..95fdbe5
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-postgres.inc.php
@@ -0,0 +1,14 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ NOTE: Since 3.31, this file is no longer used, and the "postgres" driver is
+ remapped to "postgres7". Maintaining multiple postgres drivers is no easy
+ job, so hopefully this will ensure greater consistency and fewer bugs.
+*/
diff --git a/vendor/adodb/adodb-php/drivers/adodb-postgres64.inc.php b/vendor/adodb/adodb-php/drivers/adodb-postgres64.inc.php
new file mode 100644
index 0000000..657e23c
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-postgres64.inc.php
@@ -0,0 +1,1118 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+
+ Original version derived from Alberto Cerezal (acerezalp@dbnet.es) - DBNet Informatica & Comunicaciones.
+ 08 Nov 2000 jlim - Minor corrections, removing mysql stuff
+ 09 Nov 2000 jlim - added insertid support suggested by "Christopher Kings-Lynne" <chriskl@familyhealth.com.au>
+ jlim - changed concat operator to || and data types to MetaType to match documented pgsql types
+ see http://www.postgresql.org/devel-corner/docs/postgres/datatype.htm
+ 22 Nov 2000 jlim - added changes to FetchField() and MetaTables() contributed by "raser" <raser@mail.zen.com.tw>
+ 27 Nov 2000 jlim - added changes to _connect/_pconnect from ideas by "Lennie" <leen@wirehub.nl>
+ 15 Dec 2000 jlim - added changes suggested by Additional code changes by "Eric G. Werk" egw@netguide.dk.
+ 31 Jan 2002 jlim - finally installed postgresql. testing
+ 01 Mar 2001 jlim - Freek Dijkstra changes, also support for text type
+
+ See http://www.varlena.com/varlena/GeneralBits/47.php
+
+ -- What indexes are on my table?
+ select * from pg_indexes where tablename = 'tablename';
+
+ -- What triggers are on my table?
+ select c.relname as "Table", t.tgname as "Trigger Name",
+ t.tgconstrname as "Constraint Name", t.tgenabled as "Enabled",
+ t.tgisconstraint as "Is Constraint", cc.relname as "Referenced Table",
+ p.proname as "Function Name"
+ from pg_trigger t, pg_class c, pg_class cc, pg_proc p
+ where t.tgfoid = p.oid and t.tgrelid = c.oid
+ and t.tgconstrrelid = cc.oid
+ and c.relname = 'tablename';
+
+ -- What constraints are on my table?
+ select r.relname as "Table", c.conname as "Constraint Name",
+ contype as "Constraint Type", conkey as "Key Columns",
+ confkey as "Foreign Columns", consrc as "Source"
+ from pg_class r, pg_constraint c
+ where r.oid = c.conrelid
+ and relname = 'tablename';
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+function adodb_addslashes($s)
+{
+ $len = strlen($s);
+ if ($len == 0) return "''";
+ if (strncmp($s,"'",1) === 0 && substr($s,$len-1) == "'") return $s; // already quoted
+
+ return "'".addslashes($s)."'";
+}
+
+class ADODB_postgres64 extends ADOConnection{
+ var $databaseType = 'postgres64';
+ var $dataProvider = 'postgres';
+ var $hasInsertID = true;
+ var $_resultid = false;
+ var $concat_operator='||';
+ var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1";
+ var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
+ and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages',
+ 'sql_packages', 'sql_sizing', 'sql_sizing_profiles')
+ union
+ select viewname,'V' from pg_views where viewname not like 'pg\_%'";
+ //"select tablename from pg_tables where tablename not like 'pg_%' order by 1";
+ var $isoDates = true; // accepts dates in ISO format
+ var $sysDate = "CURRENT_DATE";
+ var $sysTimeStamp = "CURRENT_TIMESTAMP";
+ var $blobEncodeType = 'C';
+ var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum
+ FROM pg_class c, pg_attribute a,pg_type t
+ WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%'
+ AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+
+ // used when schema defined
+ var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
+ FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n
+ WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s'))
+ and c.relnamespace=n.oid and n.nspname='%s'
+ and a.attname not like '....%%' AND a.attnum > 0
+ AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+
+ // get primary key etc -- from Freek Dijkstra
+ var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key
+ FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a
+ WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid
+ AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum)
+ AND a.attrelid = bc.oid AND bc.relname = '%s'";
+
+ var $hasAffectedRows = true;
+ var $hasLimit = false; // set to true for pgsql 7 only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10
+ // below suggested by Freek Dijkstra
+ var $true = 'TRUE'; // string that represents TRUE for a database
+ var $false = 'FALSE'; // string that represents FALSE for a database
+ var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database
+ var $fmtTimeStamp = "'Y-m-d H:i:s'"; // used by DBTimeStamp as the default timestamp fmt.
+ var $hasMoveFirst = true;
+ var $hasGenID = true;
+ var $_genIDSQL = "SELECT NEXTVAL('%s')";
+ var $_genSeqSQL = "CREATE SEQUENCE %s START %s";
+ var $_dropSeqSQL = "DROP SEQUENCE %s";
+ var $metaDefaultsSQL = "SELECT d.adnum as num, d.adsrc as def from pg_attrdef d, pg_class c where d.adrelid=c.oid and c.relname='%s' order by d.adnum";
+ var $random = 'random()'; /// random function
+ var $autoRollback = true; // apparently pgsql does not autorollback properly before php 4.3.4
+ // http://bugs.php.net/bug.php?id=25404
+
+ var $uniqueIisR = true;
+ var $_bindInputArray = false; // requires postgresql 7.3+ and ability to modify database
+ var $disableBlobs = false; // set to true to disable blob checking, resulting in 2-5% improvement in performance.
+
+ var $_pnum = 0;
+
+ // The last (fmtTimeStamp is not entirely correct:
+ // PostgreSQL also has support for time zones,
+ // and writes these time in this format: "2001-03-01 18:59:26+02".
+ // There is no code for the "+02" time zone information, so I just left that out.
+ // I'm not familiar enough with both ADODB as well as Postgres
+ // to know what the concequences are. The other values are correct (wheren't in 0.94)
+ // -- Freek Dijkstra
+
+ function __construct()
+ {
+ // changes the metaColumnsSQL, adds columns: attnum[6]
+ }
+
+ function ServerInfo()
+ {
+ if (isset($this->version)) return $this->version;
+
+ $arr['description'] = $this->GetOne("select version()");
+ $arr['version'] = ADOConnection::_findvers($arr['description']);
+ $this->version = $arr;
+ return $arr;
+ }
+
+ function IfNull( $field, $ifNull )
+ {
+ return " coalesce($field, $ifNull) ";
+ }
+
+ // get the last id - never tested
+ function pg_insert_id($tablename,$fieldname)
+ {
+ $result=pg_query($this->_connectionID, 'SELECT last_value FROM '. $tablename .'_'. $fieldname .'_seq');
+ if ($result) {
+ $arr = @pg_fetch_row($result,0);
+ pg_free_result($result);
+ if (isset($arr[0])) return $arr[0];
+ }
+ return false;
+ }
+
+ /**
+ * Warning from http://www.php.net/manual/function.pg-getlastoid.php:
+ * Using a OID as a unique identifier is not generally wise.
+ * Unless you are very careful, you might end up with a tuple having
+ * a different OID if a database must be reloaded.
+ */
+ function _insertid($table,$column)
+ {
+ if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false;
+ $oid = pg_getlastoid($this->_resultid);
+ // to really return the id, we need the table and column-name, else we can only return the oid != id
+ return empty($table) || empty($column) ? $oid : $this->GetOne("SELECT $column FROM $table WHERE oid=".(int)$oid);
+ }
+
+ function _affectedrows()
+ {
+ if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false;
+ return pg_affected_rows($this->_resultid);
+ }
+
+
+ /**
+ * @return true/false
+ */
+ function BeginTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+ return pg_query($this->_connectionID, 'begin '.$this->_transmode);
+ }
+
+ function RowLock($tables,$where,$col='1 as adodbignore')
+ {
+ if (!$this->transCnt) $this->BeginTrans();
+ return $this->GetOne("select $col from $tables where $where for update");
+ }
+
+ // returns true/false.
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+ if (!$ok) return $this->RollbackTrans();
+
+ $this->transCnt -= 1;
+ return pg_query($this->_connectionID, 'commit');
+ }
+
+ // returns true/false
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt -= 1;
+ return pg_query($this->_connectionID, 'rollback');
+ }
+
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $info = $this->ServerInfo();
+ if ($info['version'] >= 7.3) {
+ $this->metaTablesSQL = "
+ select table_name,'T' from information_schema.tables where table_schema not in ( 'pg_catalog','information_schema')
+ union
+ select table_name,'V' from information_schema.views where table_schema not in ( 'pg_catalog','information_schema') ";
+ }
+ if ($mask) {
+ $save = $this->metaTablesSQL;
+ $mask = $this->qstr(strtolower($mask));
+ if ($info['version']>=7.3)
+ $this->metaTablesSQL = "
+ select table_name,'T' from information_schema.tables where table_name like $mask and table_schema not in ( 'pg_catalog','information_schema')
+ union
+ select table_name,'V' from information_schema.views where table_name like $mask and table_schema not in ( 'pg_catalog','information_schema') ";
+ else
+ $this->metaTablesSQL = "
+ select tablename,'T' from pg_tables where tablename like $mask
+ union
+ select viewname,'V' from pg_views where viewname like $mask";
+ }
+ $ret = ADOConnection::MetaTables($ttype,$showSchema);
+
+ if ($mask) {
+ $this->metaTablesSQL = $save;
+ }
+ return $ret;
+ }
+
+
+ // if magic quotes disabled, use pg_escape_string()
+ function qstr($s,$magic_quotes=false)
+ {
+ if (is_bool($s)) return $s ? 'true' : 'false';
+
+ if (!$magic_quotes) {
+ if (ADODB_PHPVER >= 0x5200 && $this->_connectionID) {
+ return "'".pg_escape_string($this->_connectionID,$s)."'";
+ }
+ if (ADODB_PHPVER >= 0x4200) {
+ return "'".pg_escape_string($s)."'";
+ }
+ if ($this->replaceQuote[0] == '\\'){
+ $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\\000"),$s);
+ }
+ return "'".str_replace("'",$this->replaceQuote,$s)."'";
+ }
+
+ // undo magic quotes for "
+ $s = str_replace('\\"','"',$s);
+ return "'$s'";
+ }
+
+
+
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = 'TO_CHAR('.$col.",'";
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= 'YYYY';
+ break;
+ case 'Q':
+ case 'q':
+ $s .= 'Q';
+ break;
+
+ case 'M':
+ $s .= 'Mon';
+ break;
+
+ case 'm':
+ $s .= 'MM';
+ break;
+ case 'D':
+ case 'd':
+ $s .= 'DD';
+ break;
+
+ case 'H':
+ $s.= 'HH24';
+ break;
+
+ case 'h':
+ $s .= 'HH';
+ break;
+
+ case 'i':
+ $s .= 'MI';
+ break;
+
+ case 's':
+ $s .= 'SS';
+ break;
+
+ case 'a':
+ case 'A':
+ $s .= 'AM';
+ break;
+
+ case 'w':
+ $s .= 'D';
+ break;
+
+ case 'l':
+ $s .= 'DAY';
+ break;
+
+ case 'W':
+ $s .= 'WW';
+ break;
+
+ default:
+ // handle escape characters...
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ if (strpos('-/.:;, ',$ch) !== false) $s .= $ch;
+ else $s .= '"'.$ch.'"';
+
+ }
+ }
+ return $s. "')";
+ }
+
+
+
+ /*
+ * Load a Large Object from a file
+ * - the procedure stores the object id in the table and imports the object using
+ * postgres proprietary blob handling routines
+ *
+ * contributed by Mattia Rossi mattia@technologist.com
+ * modified for safe mode by juraj chlebec
+ */
+ function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
+ {
+ pg_query($this->_connectionID, 'begin');
+
+ $fd = fopen($path,'r');
+ $contents = fread($fd,filesize($path));
+ fclose($fd);
+
+ $oid = pg_lo_create($this->_connectionID);
+ $handle = pg_lo_open($this->_connectionID, $oid, 'w');
+ pg_lo_write($handle, $contents);
+ pg_lo_close($handle);
+
+ // $oid = pg_lo_import ($path);
+ pg_query($this->_connectionID, 'commit');
+ $rs = ADOConnection::UpdateBlob($table,$column,$oid,$where,$blobtype);
+ $rez = !empty($rs);
+ return $rez;
+ }
+
+ /*
+ * Deletes/Unlinks a Blob from the database, otherwise it
+ * will be left behind
+ *
+ * Returns TRUE on success or FALSE on failure.
+ *
+ * contributed by Todd Rogers todd#windfox.net
+ */
+ function BlobDelete( $blob )
+ {
+ pg_query($this->_connectionID, 'begin');
+ $result = @pg_lo_unlink($blob);
+ pg_query($this->_connectionID, 'commit');
+ return( $result );
+ }
+
+ /*
+ Hueristic - not guaranteed to work.
+ */
+ function GuessOID($oid)
+ {
+ if (strlen($oid)>16) return false;
+ return is_numeric($oid);
+ }
+
+ /*
+ * If an OID is detected, then we use pg_lo_* to open the oid file and read the
+ * real blob from the db using the oid supplied as a parameter. If you are storing
+ * blobs using bytea, we autodetect and process it so this function is not needed.
+ *
+ * contributed by Mattia Rossi mattia@technologist.com
+ *
+ * see http://www.postgresql.org/idocs/index.php?largeobjects.html
+ *
+ * Since adodb 4.54, this returns the blob, instead of sending it to stdout. Also
+ * added maxsize parameter, which defaults to $db->maxblobsize if not defined.
+ */
+ function BlobDecode($blob,$maxsize=false,$hastrans=true)
+ {
+ if (!$this->GuessOID($blob)) return $blob;
+
+ if ($hastrans) pg_query($this->_connectionID,'begin');
+ $fd = @pg_lo_open($this->_connectionID,$blob,'r');
+ if ($fd === false) {
+ if ($hastrans) pg_query($this->_connectionID,'commit');
+ return $blob;
+ }
+ if (!$maxsize) $maxsize = $this->maxblobsize;
+ $realblob = @pg_lo_read($fd,$maxsize);
+ @pg_loclose($fd);
+ if ($hastrans) pg_query($this->_connectionID,'commit');
+ return $realblob;
+ }
+
+ /*
+ See http://www.postgresql.org/idocs/index.php?datatype-binary.html
+
+ NOTE: SQL string literals (input strings) must be preceded with two backslashes
+ due to the fact that they must pass through two parsers in the PostgreSQL
+ backend.
+ */
+ function BlobEncode($blob)
+ {
+ if (ADODB_PHPVER >= 0x5200) return pg_escape_bytea($this->_connectionID, $blob);
+ if (ADODB_PHPVER >= 0x4200) return pg_escape_bytea($blob);
+
+ /*92=backslash, 0=null, 39=single-quote*/
+ $badch = array(chr(92),chr(0),chr(39)); # \ null '
+ $fixch = array('\\\\134','\\\\000','\\\\047');
+ return adodb_str_replace($badch,$fixch,$blob);
+
+ // note that there is a pg_escape_bytea function only for php 4.2.0 or later
+ }
+
+ // assumes bytea for blob, and varchar for clob
+ function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+ {
+ if ($blobtype == 'CLOB') {
+ return $this->Execute("UPDATE $table SET $column=" . $this->qstr($val) . " WHERE $where");
+ }
+ // do not use bind params which uses qstr(), as blobencode() already quotes data
+ return $this->Execute("UPDATE $table SET $column='".$this->BlobEncode($val)."'::bytea WHERE $where");
+ }
+
+ function OffsetDate($dayFraction,$date=false)
+ {
+ if (!$date) $date = $this->sysDate;
+ else if (strncmp($date,"'",1) == 0) {
+ $len = strlen($date);
+ if (10 <= $len && $len <= 12) $date = 'date '.$date;
+ else $date = 'timestamp '.$date;
+ }
+
+
+ return "($date+interval'".($dayFraction * 1440)." minutes')";
+ #return "($date+interval'$dayFraction days')";
+ }
+
+ /**
+ * Generate the SQL to retrieve MetaColumns data
+ * @param string $table Table name
+ * @param string $schema Schema name (can be blank)
+ * @return string SQL statement to execute
+ */
+ protected function _generateMetaColumnsSQL($table, $schema)
+ {
+ if ($schema) {
+ return sprintf($this->metaColumnsSQL1, $table, $table, $schema);
+ }
+ else {
+ return sprintf($this->metaColumnsSQL, $table, $table, $schema);
+ }
+ }
+
+ // for schema support, pass in the $table param "$schema.$tabname".
+ // converts field names to lowercase, $upper is ignored
+ // see http://phplens.com/lens/lensforum/msgs.php?id=14018 for more info
+ function MetaColumns($table,$normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = false;
+ $false = false;
+ $this->_findschema($table,$schema);
+
+ if ($normalize) $table = strtolower($table);
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+
+ $rs = $this->Execute($this->_generateMetaColumnsSQL($table, $schema));
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rs === false) {
+ return $false;
+ }
+ if (!empty($this->metaKeySQL)) {
+ // If we want the primary keys, we have to issue a separate query
+ // Of course, a modified version of the metaColumnsSQL query using a
+ // LEFT JOIN would have been much more elegant, but postgres does
+ // not support OUTER JOINS. So here is the clumsy way.
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+ $rskey = $this->Execute(sprintf($this->metaKeySQL,($table)));
+ // fetch all result in once for performance.
+ $keys = $rskey->GetArray();
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ $rskey->Close();
+ unset($rskey);
+ }
+
+ $rsdefa = array();
+ if (!empty($this->metaDefaultsSQL)) {
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $sql = sprintf($this->metaDefaultsSQL, ($table));
+ $rsdef = $this->Execute($sql);
+ if (isset($savem)) $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if ($rsdef) {
+ while (!$rsdef->EOF) {
+ $num = $rsdef->fields['num'];
+ $s = $rsdef->fields['def'];
+ if (strpos($s,'::')===false && substr($s, 0, 1) == "'") { /* quoted strings hack... for now... fixme */
+ $s = substr($s, 1);
+ $s = substr($s, 0, strlen($s) - 1);
+ }
+
+ $rsdefa[$num] = $s;
+ $rsdef->MoveNext();
+ }
+ } else {
+ ADOConnection::outp( "==> SQL => " . $sql);
+ }
+ unset($rsdef);
+ }
+
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->fields[0];
+ $fld->type = $rs->fields[1];
+ $fld->max_length = $rs->fields[2];
+ $fld->attnum = $rs->fields[6];
+
+ if ($fld->max_length <= 0) $fld->max_length = $rs->fields[3]-4;
+ if ($fld->max_length <= 0) $fld->max_length = -1;
+ if ($fld->type == 'numeric') {
+ $fld->scale = $fld->max_length & 0xFFFF;
+ $fld->max_length >>= 16;
+ }
+ // dannym
+ // 5 hasdefault; 6 num-of-column
+ $fld->has_default = ($rs->fields[5] == 't');
+ if ($fld->has_default) {
+ $fld->default_value = $rsdefa[$rs->fields[6]];
+ }
+
+ //Freek
+ $fld->not_null = $rs->fields[4] == 't';
+
+
+ // Freek
+ if (is_array($keys)) {
+ foreach($keys as $key) {
+ if ($fld->name == $key['column_name'] AND $key['primary_key'] == 't')
+ $fld->primary_key = true;
+ if ($fld->name == $key['column_name'] AND $key['unique_key'] == 't')
+ $fld->unique = true; // What name is more compatible?
+ }
+ }
+
+ if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
+ else $retarr[($normalize) ? strtoupper($fld->name) : $fld->name] = $fld;
+
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ if (empty($retarr))
+ return $false;
+ else
+ return $retarr;
+
+ }
+
+ function Param($name,$type='C')
+ {
+ if ($name) {
+ $this->_pnum += 1;
+ } else {
+ // Reset param num if $name is false
+ $this->_pnum = 1;
+ }
+ return '$'.$this->_pnum;
+ }
+
+ function MetaIndexes ($table, $primary = FALSE, $owner = false)
+ {
+ global $ADODB_FETCH_MODE;
+
+ $schema = false;
+ $this->_findschema($table,$schema);
+
+ if ($schema) { // requires pgsql 7.3+ - pg_namespace used.
+ $sql = '
+ SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns"
+ FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid
+ JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid
+ ,pg_namespace n
+ WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\'))
+ and c.relnamespace=c2.relnamespace
+ and c.relnamespace=n.oid
+ and n.nspname=\'%s\'';
+ } else {
+ $sql = '
+ SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns"
+ FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid
+ JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid
+ WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\'))';
+ }
+
+ if ($primary == FALSE) {
+ $sql .= ' AND i.indisprimary=false;';
+ }
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute(sprintf($sql,$table,$table,$schema));
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ $false = false;
+ return $false;
+ }
+
+ $col_names = $this->MetaColumnNames($table,true,true);
+ // 3rd param is use attnum,
+ // see https://sourceforge.net/p/adodb/bugs/45/
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ $columns = array();
+ foreach (explode(' ', $row[2]) as $col) {
+ $columns[] = $col_names[$col];
+ }
+
+ $indexes[$row[0]] = array(
+ 'unique' => ($row[1] == 't'),
+ 'columns' => $columns
+ );
+ }
+ return $indexes;
+ }
+
+ // returns true or false
+ //
+ // examples:
+ // $db->Connect("host=host1 user=user1 password=secret port=4341");
+ // $db->Connect('host1','user1','secret');
+ function _connect($str,$user='',$pwd='',$db='',$ctype=0)
+ {
+ if (!function_exists('pg_connect')) return null;
+
+ $this->_errorMsg = false;
+
+ if ($user || $pwd || $db) {
+ $user = adodb_addslashes($user);
+ $pwd = adodb_addslashes($pwd);
+ if (strlen($db) == 0) $db = 'template1';
+ $db = adodb_addslashes($db);
+ if ($str) {
+ $host = explode(":", $str);
+ if ($host[0]) $str = "host=".adodb_addslashes($host[0]);
+ else $str = '';
+ if (isset($host[1])) $str .= " port=$host[1]";
+ else if (!empty($this->port)) $str .= " port=".$this->port;
+ }
+ if ($user) $str .= " user=".$user;
+ if ($pwd) $str .= " password=".$pwd;
+ if ($db) $str .= " dbname=".$db;
+ }
+
+ //if ($user) $linea = "user=$user host=$linea password=$pwd dbname=$db port=5432";
+
+ if ($ctype === 1) { // persistent
+ $this->_connectionID = pg_pconnect($str);
+ } else {
+ if ($ctype === -1) { // nconnect, we trick pgsql ext by changing the connection str
+ static $ncnt;
+
+ if (empty($ncnt)) $ncnt = 1;
+ else $ncnt += 1;
+
+ $str .= str_repeat(' ',$ncnt);
+ }
+ $this->_connectionID = pg_connect($str);
+ }
+ if ($this->_connectionID === false) return false;
+ $this->Execute("set datestyle='ISO'");
+
+ $info = $this->ServerInfo();
+ $this->pgVersion = (float) substr($info['version'],0,3);
+ if ($this->pgVersion >= 7.1) { // good till version 999
+ $this->_nestedSQL = true;
+ }
+
+ # PostgreSQL 9.0 changed the default output for bytea from 'escape' to 'hex'
+ # PHP does not handle 'hex' properly ('x74657374' is returned as 't657374')
+ # https://bugs.php.net/bug.php?id=59831 states this is in fact not a bug,
+ # so we manually set bytea_output
+ if ( !empty($this->connection->noBlobs) && version_compare($info['version'], '9.0', '>=')) {
+ $this->Execute('set bytea_output=escape');
+ }
+
+ return true;
+ }
+
+ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName)
+ {
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName,-1);
+ }
+
+ // returns true or false
+ //
+ // examples:
+ // $db->PConnect("host=host1 user=user1 password=secret port=4341");
+ // $db->PConnect('host1','user1','secret');
+ function _pconnect($str,$user='',$pwd='',$db='')
+ {
+ return $this->_connect($str,$user,$pwd,$db,1);
+ }
+
+
+ // returns queryID or false
+ function _query($sql,$inputarr=false)
+ {
+ $this->_pnum = 0;
+ $this->_errorMsg = false;
+ if ($inputarr) {
+ /*
+ It appears that PREPARE/EXECUTE is slower for many queries.
+
+ For query executed 1000 times:
+ "select id,firstname,lastname from adoxyz
+ where firstname not like ? and lastname not like ? and id = ?"
+
+ with plan = 1.51861286163 secs
+ no plan = 1.26903700829 secs
+ */
+ $plan = 'P'.md5($sql);
+
+ $execp = '';
+ foreach($inputarr as $v) {
+ if ($execp) $execp .= ',';
+ if (is_string($v)) {
+ if (strncmp($v,"'",1) !== 0) $execp .= $this->qstr($v);
+ } else {
+ $execp .= $v;
+ }
+ }
+
+ if ($execp) $exsql = "EXECUTE $plan ($execp)";
+ else $exsql = "EXECUTE $plan";
+
+
+ $rez = @pg_execute($this->_connectionID,$exsql);
+ if (!$rez) {
+ # Perhaps plan does not exist? Prepare/compile plan.
+ $params = '';
+ foreach($inputarr as $v) {
+ if ($params) $params .= ',';
+ if (is_string($v)) {
+ $params .= 'VARCHAR';
+ } else if (is_integer($v)) {
+ $params .= 'INTEGER';
+ } else {
+ $params .= "REAL";
+ }
+ }
+ $sqlarr = explode('?',$sql);
+ //print_r($sqlarr);
+ $sql = '';
+ $i = 1;
+ foreach($sqlarr as $v) {
+ $sql .= $v.' $'.$i;
+ $i++;
+ }
+ $s = "PREPARE $plan ($params) AS ".substr($sql,0,strlen($sql)-2);
+ //adodb_pr($s);
+ $rez = pg_execute($this->_connectionID,$s);
+ //echo $this->ErrorMsg();
+ }
+ if ($rez)
+ $rez = pg_execute($this->_connectionID,$exsql);
+ } else {
+ //adodb_backtrace();
+ $rez = pg_query($this->_connectionID,$sql);
+ }
+ // check if no data returned, then no need to create real recordset
+ if ($rez && pg_num_fields($rez) <= 0) {
+ if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') {
+ pg_free_result($this->_resultid);
+ }
+ $this->_resultid = $rez;
+ return true;
+ }
+
+ return $rez;
+ }
+
+ function _errconnect()
+ {
+ if (defined('DB_ERROR_CONNECT_FAILED')) return DB_ERROR_CONNECT_FAILED;
+ else return 'Database connection failed';
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+ if ($this->_errorMsg !== false) return $this->_errorMsg;
+ if (ADODB_PHPVER >= 0x4300) {
+ if (!empty($this->_resultid)) {
+ $this->_errorMsg = @pg_result_error($this->_resultid);
+ if ($this->_errorMsg) return $this->_errorMsg;
+ }
+
+ if (!empty($this->_connectionID)) {
+ $this->_errorMsg = @pg_last_error($this->_connectionID);
+ } else $this->_errorMsg = $this->_errconnect();
+ } else {
+ if (empty($this->_connectionID)) $this->_errconnect();
+ else $this->_errorMsg = @pg_errormessage($this->_connectionID);
+ }
+ return $this->_errorMsg;
+ }
+
+ function ErrorNo()
+ {
+ $e = $this->ErrorMsg();
+ if (strlen($e)) {
+ return ADOConnection::MetaError($e);
+ }
+ return 0;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ if ($this->transCnt) $this->RollbackTrans();
+ if ($this->_resultid) {
+ @pg_free_result($this->_resultid);
+ $this->_resultid = false;
+ }
+ @pg_close($this->_connectionID);
+ $this->_connectionID = false;
+ return true;
+ }
+
+
+ /*
+ * Maximum size of C field
+ */
+ function CharMax()
+ {
+ return 1000000000; // should be 1 Gb?
+ }
+
+ /*
+ * Maximum size of X field
+ */
+ function TextMax()
+ {
+ return 1000000000; // should be 1 Gb?
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_postgres64 extends ADORecordSet{
+ var $_blobArr;
+ var $databaseType = "postgres64";
+ var $canSeek = true;
+
+ function __construct($queryID, $mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch ($mode)
+ {
+ case ADODB_FETCH_NUM: $this->fetchMode = PGSQL_NUM; break;
+ case ADODB_FETCH_ASSOC:$this->fetchMode = PGSQL_ASSOC; break;
+
+ case ADODB_FETCH_DEFAULT:
+ case ADODB_FETCH_BOTH:
+ default: $this->fetchMode = PGSQL_BOTH; break;
+ }
+ $this->adodbFetchMode = $mode;
+
+ // Parent's constructor
+ parent::__construct($queryID);
+ }
+
+ function GetRowAssoc($upper = ADODB_ASSOC_CASE)
+ {
+ if ($this->fetchMode == PGSQL_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
+ return $this->fields;
+ }
+ $row = ADORecordSet::GetRowAssoc($upper);
+ return $row;
+ }
+
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $qid = $this->_queryID;
+ $this->_numOfRows = ($ADODB_COUNTRECS)? @pg_num_rows($qid):-1;
+ $this->_numOfFields = @pg_num_fields($qid);
+
+ // cache types for blob decode check
+ // apparently pg_field_type actually performs an sql query on the database to get the type.
+ if (empty($this->connection->noBlobs))
+ for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) {
+ if (pg_field_type($qid,$i) == 'bytea') {
+ $this->_blobArr[$i] = pg_field_name($qid,$i);
+ }
+ }
+ }
+
+ /* Use associative array to get fields array */
+ function Fields($colname)
+ {
+ if ($this->fetchMode != PGSQL_NUM) return @$this->fields[$colname];
+
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function FetchField($off = 0)
+ {
+ // offsets begin at 0
+
+ $o= new ADOFieldObject();
+ $o->name = @pg_field_name($this->_queryID,$off);
+ $o->type = @pg_field_type($this->_queryID,$off);
+ $o->max_length = @pg_fieldsize($this->_queryID,$off);
+ return $o;
+ }
+
+ function _seek($row)
+ {
+ return @pg_fetch_row($this->_queryID,$row);
+ }
+
+ function _decode($blob)
+ {
+ if ($blob === NULL) return NULL;
+// eval('$realblob="'.adodb_str_replace(array('"','$'),array('\"','\$'),$blob).'";');
+ return pg_unescape_bytea($blob);
+ }
+
+ function _fixblobs()
+ {
+ if ($this->fetchMode == PGSQL_NUM || $this->fetchMode == PGSQL_BOTH) {
+ foreach($this->_blobArr as $k => $v) {
+ $this->fields[$k] = ADORecordSet_postgres64::_decode($this->fields[$k]);
+ }
+ }
+ if ($this->fetchMode == PGSQL_ASSOC || $this->fetchMode == PGSQL_BOTH) {
+ foreach($this->_blobArr as $k => $v) {
+ $this->fields[$v] = ADORecordSet_postgres64::_decode($this->fields[$v]);
+ }
+ }
+ }
+
+ // 10% speedup to move MoveNext to child class
+ function MoveNext()
+ {
+ if (!$this->EOF) {
+ $this->_currentRow++;
+ if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
+ $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+ if (is_array($this->fields) && $this->fields) {
+ if (isset($this->_blobArr)) $this->_fixblobs();
+ return true;
+ }
+ }
+ $this->fields = false;
+ $this->EOF = true;
+ }
+ return false;
+ }
+
+ function _fetch()
+ {
+
+ if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0)
+ return false;
+
+ $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+
+ if ($this->fields && isset($this->_blobArr)) $this->_fixblobs();
+
+ return (is_array($this->fields));
+ }
+
+ function _close()
+ {
+ return @pg_free_result($this->_queryID);
+ }
+
+ function MetaType($t,$len=-1,$fieldobj=false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ switch (strtoupper($t)) {
+ case 'MONEY': // stupid, postgres expects money to be a string
+ case 'INTERVAL':
+ case 'CHAR':
+ case 'CHARACTER':
+ case 'VARCHAR':
+ case 'NAME':
+ case 'BPCHAR':
+ case '_VARCHAR':
+ case 'INET':
+ case 'MACADDR':
+ if ($len <= $this->blobSize) return 'C';
+
+ case 'TEXT':
+ return 'X';
+
+ case 'IMAGE': // user defined type
+ case 'BLOB': // user defined type
+ case 'BIT': // This is a bit string, not a single bit, so don't return 'L'
+ case 'VARBIT':
+ case 'BYTEA':
+ return 'B';
+
+ case 'BOOL':
+ case 'BOOLEAN':
+ return 'L';
+
+ case 'DATE':
+ return 'D';
+
+
+ case 'TIMESTAMP WITHOUT TIME ZONE':
+ case 'TIME':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+ case 'TIMESTAMPTZ':
+ return 'T';
+
+ case 'SMALLINT':
+ case 'BIGINT':
+ case 'INTEGER':
+ case 'INT8':
+ case 'INT4':
+ case 'INT2':
+ if (isset($fieldobj) &&
+ empty($fieldobj->primary_key) && (!$this->connection->uniqueIisR || empty($fieldobj->unique))) return 'I';
+
+ case 'OID':
+ case 'SERIAL':
+ return 'R';
+
+ default:
+ return 'N';
+ }
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-postgres7.inc.php b/vendor/adodb/adodb-php/drivers/adodb-postgres7.inc.php
new file mode 100644
index 0000000..55a5e5c
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-postgres7.inc.php
@@ -0,0 +1,388 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ Postgres7 support.
+ 28 Feb 2001: Currently indicate that we support LIMIT
+ 01 Dec 2001: dannym added support for default values
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-postgres64.inc.php");
+
+class ADODB_postgres7 extends ADODB_postgres64 {
+ var $databaseType = 'postgres7';
+ var $hasLimit = true; // set to true for pgsql 6.5+ only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10
+ var $ansiOuter = true;
+ var $charSet = true; //set to true for Postgres 7 and above - PG client supports encodings
+
+ // Richard 3/18/2012 - Modified SQL to return SERIAL type correctly AS old driver no longer return SERIAL as data type.
+ var $metaColumnsSQL = "
+ SELECT
+ a.attname,
+ CASE
+ WHEN x.sequence_name != ''
+ THEN 'SERIAL'
+ ELSE t.typname
+ END AS typname,
+ a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
+ FROM
+ pg_class c,
+ pg_attribute a
+ JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN (
+ SELECT
+ c.relname as sequence_name,
+ c1.relname as related_table,
+ a.attname as related_column
+ FROM pg_class c
+ JOIN pg_depend d ON d.objid = c.oid
+ LEFT JOIN pg_class c1 ON d.refobjid = c1.oid
+ LEFT JOIN pg_attribute a ON (d.refobjid, d.refobjsubid) = (a.attrelid, a.attnum)
+ WHERE c.relkind = 'S' AND c1.relname = '%s'
+ ) x ON x.related_column= a.attname
+ WHERE
+ c.relkind in ('r','v')
+ AND (c.relname='%s' or c.relname = lower('%s'))
+ AND a.attname not like '....%%'
+ AND a.attnum > 0
+ AND a.attrelid = c.oid
+ ORDER BY
+ a.attnum";
+
+ // used when schema defined
+ var $metaColumnsSQL1 = "
+ SELECT
+ a.attname,
+ CASE
+ WHEN x.sequence_name != ''
+ THEN 'SERIAL'
+ ELSE t.typname
+ END AS typname,
+ a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
+ FROM
+ pg_class c,
+ pg_namespace n,
+ pg_attribute a
+ JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN (
+ SELECT
+ c.relname as sequence_name,
+ c1.relname as related_table,
+ a.attname as related_column
+ FROM pg_class c
+ JOIN pg_depend d ON d.objid = c.oid
+ LEFT JOIN pg_class c1 ON d.refobjid = c1.oid
+ LEFT JOIN pg_attribute a ON (d.refobjid, d.refobjsubid) = (a.attrelid, a.attnum)
+ WHERE c.relkind = 'S' AND c1.relname = '%s'
+ ) x ON x.related_column= a.attname
+ WHERE
+ c.relkind in ('r','v')
+ AND (c.relname='%s' or c.relname = lower('%s'))
+ AND c.relnamespace=n.oid and n.nspname='%s'
+ AND a.attname not like '....%%'
+ AND a.attnum > 0
+ AND a.atttypid = t.oid
+ AND a.attrelid = c.oid
+ ORDER BY a.attnum";
+
+
+ function __construct()
+ {
+ parent::__construct();
+ if (ADODB_ASSOC_CASE !== ADODB_ASSOC_CASE_NATIVE) {
+ $this->rsPrefix .= 'assoc_';
+ }
+ $this->_bindInputArray = PHP_VERSION >= 5.1;
+ }
+
+
+ // the following should be compat with postgresql 7.2,
+ // which makes obsolete the LIMIT limit,offset syntax
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr = ($offset >= 0) ? " OFFSET ".((integer)$offset) : '';
+ $limitStr = ($nrows >= 0) ? " LIMIT ".((integer)$nrows) : '';
+ if ($secs2cache)
+ $rs = $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr);
+ else
+ $rs = $this->Execute($sql."$limitStr$offsetStr",$inputarr);
+
+ return $rs;
+ }
+
+ /*
+ function Prepare($sql)
+ {
+ $info = $this->ServerInfo();
+ if ($info['version']>=7.3) {
+ return array($sql,false);
+ }
+ return $sql;
+ }
+ */
+
+ /**
+ * Generate the SQL to retrieve MetaColumns data
+ * @param string $table Table name
+ * @param string $schema Schema name (can be blank)
+ * @return string SQL statement to execute
+ */
+ protected function _generateMetaColumnsSQL($table, $schema)
+ {
+ if ($schema) {
+ return sprintf($this->metaColumnsSQL1, $table, $table, $table, $schema);
+ }
+ else {
+ return sprintf($this->metaColumnsSQL, $table, $table, $schema);
+ }
+ }
+
+ /**
+ * @returns assoc array where keys are tables, and values are foreign keys
+ */
+ function MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ # Regex isolates the 2 terms between parenthesis using subexpressions
+ $regex = '^.*\((.*)\).*\((.*)\).*$';
+ $sql="
+ SELECT
+ lookup_table,
+ regexp_replace(consrc, '$regex', '\\2') AS lookup_field,
+ dep_table,
+ regexp_replace(consrc, '$regex', '\\1') AS dep_field
+ FROM (
+ SELECT
+ pg_get_constraintdef(c.oid) AS consrc,
+ t.relname AS dep_table,
+ ft.relname AS lookup_table
+ FROM pg_constraint c
+ JOIN pg_class t ON (t.oid = c.conrelid)
+ JOIN pg_class ft ON (ft.oid = c.confrelid)
+ JOIN pg_namespace nft ON (nft.oid = ft.relnamespace)
+ LEFT JOIN pg_description ds ON (ds.objoid = c.oid)
+ JOIN pg_namespace n ON (n.oid = t.relnamespace)
+ WHERE c.contype = 'f'::\"char\"
+ ORDER BY t.relname, n.nspname, c.conname, c.oid
+ ) constraints
+ WHERE
+ dep_table='".strtolower($table)."'
+ ORDER BY
+ lookup_table,
+ dep_table,
+ dep_field";
+ $rs = $this->Execute($sql);
+
+ if (!$rs || $rs->EOF) return false;
+
+ $a = array();
+ while (!$rs->EOF) {
+ if ($upper) {
+ $a[strtoupper($rs->Fields('lookup_table'))][] = strtoupper(str_replace('"','',$rs->Fields('dep_field').'='.$rs->Fields('lookup_field')));
+ } else {
+ $a[$rs->Fields('lookup_table')][] = str_replace('"','',$rs->Fields('dep_field').'='.$rs->Fields('lookup_field'));
+ }
+ $rs->MoveNext();
+ }
+
+ return $a;
+
+ }
+
+ // from Edward Jaramilla, improved version - works on pg 7.4
+ function _old_MetaForeignKeys($table, $owner=false, $upper=false)
+ {
+ $sql = 'SELECT t.tgargs as args
+ FROM
+ pg_trigger t,pg_class c,pg_proc p
+ WHERE
+ t.tgenabled AND
+ t.tgrelid = c.oid AND
+ t.tgfoid = p.oid AND
+ p.proname = \'RI_FKey_check_ins\' AND
+ c.relname = \''.strtolower($table).'\'
+ ORDER BY
+ t.tgrelid';
+
+ $rs = $this->Execute($sql);
+
+ if (!$rs || $rs->EOF) return false;
+
+ $arr = $rs->GetArray();
+ $a = array();
+ foreach($arr as $v) {
+ $data = explode(chr(0), $v['args']);
+ $size = count($data)-1; //-1 because the last node is empty
+ for($i = 4; $i < $size; $i++) {
+ if ($upper)
+ $a[strtoupper($data[2])][] = strtoupper($data[$i].'='.$data[++$i]);
+ else
+ $a[$data[2]][] = $data[$i].'='.$data[++$i];
+ }
+ }
+ return $a;
+ }
+
+ function _query($sql,$inputarr=false)
+ {
+ if (! $this->_bindInputArray) {
+ // We don't have native support for parameterized queries, so let's emulate it at the parent
+ return ADODB_postgres64::_query($sql, $inputarr);
+ }
+
+ $this->_pnum = 0;
+ $this->_errorMsg = false;
+ // -- added Cristiano da Cunha Duarte
+ if ($inputarr) {
+ $sqlarr = explode('?',trim($sql));
+ $sql = '';
+ $i = 1;
+ $last = sizeof($sqlarr)-1;
+ foreach($sqlarr as $v) {
+ if ($last < $i) $sql .= $v;
+ else $sql .= $v.' $'.$i;
+ $i++;
+ }
+
+ $rez = pg_query_params($this->_connectionID,$sql, $inputarr);
+ } else {
+ $rez = pg_query($this->_connectionID,$sql);
+ }
+ // check if no data returned, then no need to create real recordset
+ if ($rez && pg_num_fields($rez) <= 0) {
+ if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') {
+ pg_free_result($this->_resultid);
+ }
+ $this->_resultid = $rez;
+ return true;
+ }
+ return $rez;
+ }
+
+ // this is a set of functions for managing client encoding - very important if the encodings
+ // of your database and your output target (i.e. HTML) don't match
+ //for instance, you may have UNICODE database and server it on-site as WIN1251 etc.
+ // GetCharSet - get the name of the character set the client is using now
+ // the functions should work with Postgres 7.0 and above, the set of charsets supported
+ // depends on compile flags of postgres distribution - if no charsets were compiled into the server
+ // it will return 'SQL_ANSI' always
+ function GetCharSet()
+ {
+ //we will use ADO's builtin property charSet
+ $this->charSet = @pg_client_encoding($this->_connectionID);
+ if (!$this->charSet) {
+ return false;
+ } else {
+ return $this->charSet;
+ }
+ }
+
+ // SetCharSet - switch the client encoding
+ function SetCharSet($charset_name)
+ {
+ $this->GetCharSet();
+ if ($this->charSet !== $charset_name) {
+ $if = pg_set_client_encoding($this->_connectionID, $charset_name);
+ if ($if == "0" & $this->GetCharSet() == $charset_name) {
+ return true;
+ } else return false;
+ } else return true;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordSet_postgres7 extends ADORecordSet_postgres64{
+
+ var $databaseType = "postgres7";
+
+
+ function __construct($queryID, $mode=false)
+ {
+ parent::__construct($queryID, $mode);
+ }
+
+ // 10% speedup to move MoveNext to child class
+ function MoveNext()
+ {
+ if (!$this->EOF) {
+ $this->_currentRow++;
+ if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
+ $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+
+ if (is_array($this->fields)) {
+ if ($this->fields && isset($this->_blobArr)) $this->_fixblobs();
+ return true;
+ }
+ }
+ $this->fields = false;
+ $this->EOF = true;
+ }
+ return false;
+ }
+
+}
+
+class ADORecordSet_assoc_postgres7 extends ADORecordSet_postgres64{
+
+ var $databaseType = "postgres7";
+
+
+ function __construct($queryID, $mode=false)
+ {
+ parent::__construct($queryID, $mode);
+ }
+
+ function _fetch()
+ {
+ if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0) {
+ return false;
+ }
+
+ $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+
+ if ($this->fields) {
+ if (isset($this->_blobArr)) $this->_fixblobs();
+ $this->_updatefields();
+ }
+
+ return (is_array($this->fields));
+ }
+
+ function MoveNext()
+ {
+ if (!$this->EOF) {
+ $this->_currentRow++;
+ if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
+ $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+
+ if (is_array($this->fields)) {
+ if ($this->fields) {
+ if (isset($this->_blobArr)) $this->_fixblobs();
+
+ $this->_updatefields();
+ }
+ return true;
+ }
+ }
+
+
+ $this->fields = false;
+ $this->EOF = true;
+ }
+ return false;
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-postgres8.inc.php b/vendor/adodb/adodb-php/drivers/adodb-postgres8.inc.php
new file mode 100644
index 0000000..f1b8576
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-postgres8.inc.php
@@ -0,0 +1,50 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ Postgres8 support.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-postgres7.inc.php");
+
+class ADODB_postgres8 extends ADODB_postgres7
+{
+ var $databaseType = 'postgres8';
+
+
+ /**
+ * Retrieve last inserted ID
+ * Don't use OIDs, since as per {@link http://php.net/function.pg-last-oid php manual }
+ * they won't be there in Postgres 8.1
+ * (and they're not what the application wants back, anyway).
+ * @param string $table
+ * @param string $column
+ * @return int last inserted ID for given table/column, or the most recently
+ * returned one if $table or $column are empty
+ */
+ function _insertid($table, $column)
+ {
+ return empty($table) || empty($column)
+ ? $this->GetOne("SELECT lastval()")
+ : $this->GetOne("SELECT currval(pg_get_serial_sequence('$table', '$column'))");
+ }
+}
+
+class ADORecordSet_postgres8 extends ADORecordSet_postgres7
+{
+ var $databaseType = "postgres8";
+}
+
+class ADORecordSet_assoc_postgres8 extends ADORecordSet_assoc_postgres7
+{
+ var $databaseType = "postgres8";
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-postgres9.inc.php b/vendor/adodb/adodb-php/drivers/adodb-postgres9.inc.php
new file mode 100644
index 0000000..72ddb9a
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-postgres9.inc.php
@@ -0,0 +1,32 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ Postgres9 support.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR."/drivers/adodb-postgres8.inc.php");
+
+class ADODB_postgres9 extends ADODB_postgres8
+{
+ var $databaseType = 'postgres9';
+}
+
+class ADORecordSet_postgres9 extends ADORecordSet_postgres8
+{
+ var $databaseType = "postgres9";
+}
+
+class ADORecordSet_assoc_postgres9 extends ADORecordSet_assoc_postgres8
+{
+ var $databaseType = "postgres9";
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-proxy.inc.php b/vendor/adodb/adodb-php/drivers/adodb-proxy.inc.php
new file mode 100644
index 0000000..79ba006
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-proxy.inc.php
@@ -0,0 +1,33 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4.
+
+ Synonym for csv driver.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_PROXY_LAYER")) {
+ define("_ADODB_PROXY_LAYER", 1 );
+ include(ADODB_DIR."/drivers/adodb-csv.inc.php");
+
+ class ADODB_proxy extends ADODB_csv {
+ var $databaseType = 'proxy';
+ var $databaseProvider = 'csv';
+ }
+ class ADORecordset_proxy extends ADORecordset_csv {
+ var $databaseType = "proxy";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+ };
+} // define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sapdb.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sapdb.inc.php
new file mode 100644
index 0000000..4ab9ffb
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sapdb.inc.php
@@ -0,0 +1,185 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ SAPDB data driver. Requires ODBC.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+if (!defined('ADODB_SAPDB')){
+define('ADODB_SAPDB',1);
+
+class ADODB_SAPDB extends ADODB_odbc {
+ var $databaseType = "sapdb";
+ var $concat_operator = '||';
+ var $sysDate = 'DATE';
+ var $sysTimeStamp = 'TIMESTAMP';
+ var $fmtDate = "'Y-m-d'"; /// used by DBDate() as the default date format used by the database
+ var $fmtTimeStamp = "'Y-m-d H:i:s'"; /// used by DBTimeStamp as the default timestamp fmt.
+ var $hasInsertId = true;
+ var $_bindInputArray = true;
+
+ function __construct()
+ {
+ //if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC;
+ parent::__construct();
+ }
+
+ function ServerInfo()
+ {
+ $info = ADODB_odbc::ServerInfo();
+ if (!$info['version'] && preg_match('/([0-9.]+)/',$info['description'],$matches)) {
+ $info['version'] = $matches[1];
+ }
+ return $info;
+ }
+
+ function MetaPrimaryKeys($table, $owner = false)
+ {
+ $table = $this->Quote(strtoupper($table));
+
+ return $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table AND mode='KEY' ORDER BY pos");
+ }
+
+ function MetaIndexes ($table, $primary = FALSE, $owner = false)
+ {
+ $table = $this->Quote(strtoupper($table));
+
+ $sql = "SELECT INDEXNAME,TYPE,COLUMNNAME FROM INDEXCOLUMNS ".
+ " WHERE TABLENAME=$table".
+ " ORDER BY INDEXNAME,COLUMNNO";
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+
+ $rs = $this->Execute($sql);
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ if (!is_object($rs)) {
+ return FALSE;
+ }
+
+ $indexes = array();
+ while ($row = $rs->FetchRow()) {
+ $indexes[$row[0]]['unique'] = $row[1] == 'UNIQUE';
+ $indexes[$row[0]]['columns'][] = $row[2];
+ }
+ if ($primary) {
+ $indexes['SYSPRIMARYKEYINDEX'] = array(
+ 'unique' => True, // by definition
+ 'columns' => $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table AND mode='KEY' ORDER BY pos"),
+ );
+ }
+ return $indexes;
+ }
+
+ function MetaColumns ($table, $normalize = true)
+ {
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+ $table = $this->Quote(strtoupper($table));
+
+ $retarr = array();
+ foreach($this->GetAll("SELECT COLUMNNAME,DATATYPE,LEN,DEC,NULLABLE,MODE,\"DEFAULT\",CASE WHEN \"DEFAULT\" IS NULL THEN 0 ELSE 1 END AS HAS_DEFAULT FROM COLUMNS WHERE tablename=$table ORDER BY pos") as $column)
+ {
+ $fld = new ADOFieldObject();
+ $fld->name = $column[0];
+ $fld->type = $column[1];
+ $fld->max_length = $fld->type == 'LONG' ? 2147483647 : $column[2];
+ $fld->scale = $column[3];
+ $fld->not_null = $column[4] == 'NO';
+ $fld->primary_key = $column[5] == 'KEY';
+ if ($fld->has_default = $column[7]) {
+ if ($fld->primary_key && $column[6] == 'DEFAULT SERIAL (1)') {
+ $fld->auto_increment = true;
+ $fld->has_default = false;
+ } else {
+ $fld->default_value = $column[6];
+ switch($fld->type) {
+ case 'VARCHAR':
+ case 'CHARACTER':
+ case 'LONG':
+ $fld->default_value = $column[6];
+ break;
+ default:
+ $fld->default_value = trim($column[6]);
+ break;
+ }
+ }
+ }
+ $retarr[$fld->name] = $fld;
+ }
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+
+ return $retarr;
+ }
+
+ function MetaColumnNames($table, $numIndexes = false, $useattnum = false)
+ {
+ $table = $this->Quote(strtoupper($table));
+
+ return $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table ORDER BY pos");
+ }
+
+ // unlike it seems, this depends on the db-session and works in a multiuser environment
+ function _insertid($table,$column)
+ {
+ return empty($table) ? False : $this->GetOne("SELECT $table.CURRVAL FROM DUAL");
+ }
+
+ /*
+ SelectLimit implementation problems:
+
+ The following will return random 10 rows as order by performed after "WHERE rowno<10"
+ which is not ideal...
+
+ select * from table where rowno < 10 order by 1
+
+ This means that we have to use the adoconnection base class SelectLimit when
+ there is an "order by".
+
+ See http://listserv.sap.com/pipermail/sapdb.general/2002-January/010405.html
+ */
+
+};
+
+
+class ADORecordSet_sapdb extends ADORecordSet_odbc {
+
+ var $databaseType = "sapdb";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+}
+
+} //define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sqlanywhere.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sqlanywhere.inc.php
new file mode 100644
index 0000000..80a7ace
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sqlanywhere.inc.php
@@ -0,0 +1,165 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+reserved.
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ 21.02.2002 - Wade Johnson wade@wadejohnson.de
+ Extended ODBC class for Sybase SQLAnywhere.
+ 1) Added support to retrieve the last row insert ID on tables with
+ primary key column using autoincrement function.
+
+ 2) Added blob support. Usage:
+ a) create blob variable on db server:
+
+ $dbconn->create_blobvar($blobVarName);
+
+ b) load blob var from file. $filename must be complete path
+
+ $dbcon->load_blobvar_from_file($blobVarName, $filename);
+
+ c) Use the $blobVarName in SQL insert or update statement in the values
+ clause:
+
+ $recordSet = $dbconn->Execute('INSERT INTO tabname (idcol, blobcol) '
+ .
+ 'VALUES (\'test\', ' . $blobVarName . ')');
+
+ instead of loading blob from a file, you can also load from
+ an unformatted (raw) blob variable:
+ $dbcon->load_blobvar_from_var($blobVarName, $varName);
+
+ d) drop blob variable on db server to free up resources:
+ $dbconn->drop_blobvar($blobVarName);
+
+ Sybase_SQLAnywhere data driver. Requires ODBC.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+
+if (!defined('ADODB_SYBASE_SQLANYWHERE')){
+
+ define('ADODB_SYBASE_SQLANYWHERE',1);
+
+ class ADODB_sqlanywhere extends ADODB_odbc {
+ var $databaseType = "sqlanywhere";
+ var $hasInsertID = true;
+
+ function _insertid() {
+ return $this->GetOne('select @@identity');
+ }
+
+ function create_blobvar($blobVarName) {
+ $this->Execute("create variable $blobVarName long binary");
+ return;
+ }
+
+ function drop_blobvar($blobVarName) {
+ $this->Execute("drop variable $blobVarName");
+ return;
+ }
+
+ function load_blobvar_from_file($blobVarName, $filename) {
+ $chunk_size = 1000;
+
+ $fd = fopen ($filename, "rb");
+
+ $integer_chunks = (integer)filesize($filename) / $chunk_size;
+ $modulus = filesize($filename) % $chunk_size;
+ if ($modulus != 0){
+ $integer_chunks += 1;
+ }
+
+ for($loop=1;$loop<=$integer_chunks;$loop++){
+ $contents = fread ($fd, $chunk_size);
+ $contents = bin2hex($contents);
+
+ $hexstring = '';
+
+ for($loop2=0;$loop2<strlen($contents);$loop2+=2){
+ $hexstring .= '\x' . substr($contents,$loop2,2);
+ }
+
+ $hexstring = $this->qstr($hexstring);
+
+ $this->Execute("set $blobVarName = $blobVarName || " . $hexstring);
+ }
+
+ fclose ($fd);
+ return;
+ }
+
+ function load_blobvar_from_var($blobVarName, &$varName) {
+ $chunk_size = 1000;
+
+ $integer_chunks = (integer)strlen($varName) / $chunk_size;
+ $modulus = strlen($varName) % $chunk_size;
+ if ($modulus != 0){
+ $integer_chunks += 1;
+ }
+
+ for($loop=1;$loop<=$integer_chunks;$loop++){
+ $contents = substr ($varName, (($loop - 1) * $chunk_size), $chunk_size);
+ $contents = bin2hex($contents);
+
+ $hexstring = '';
+
+ for($loop2=0;$loop2<strlen($contents);$loop2+=2){
+ $hexstring .= '\x' . substr($contents,$loop2,2);
+ }
+
+ $hexstring = $this->qstr($hexstring);
+
+ $this->Execute("set $blobVarName = $blobVarName || " . $hexstring);
+ }
+
+ return;
+ }
+
+ /*
+ Insert a null into the blob field of the table first.
+ Then use UpdateBlob to store the blob.
+
+ Usage:
+
+ $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
+ $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
+ */
+ function UpdateBlob($table,$column,&$val,$where,$blobtype='BLOB')
+ {
+ $blobVarName = 'hold_blob';
+ $this->create_blobvar($blobVarName);
+ $this->load_blobvar_from_var($blobVarName, $val);
+ $this->Execute("UPDATE $table SET $column=$blobVarName WHERE $where");
+ $this->drop_blobvar($blobVarName);
+ return true;
+ }
+ }; //class
+
+ class ADORecordSet_sqlanywhere extends ADORecordSet_odbc {
+
+ var $databaseType = "sqlanywhere";
+
+ function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+
+ }; //class
+
+
+} //define
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sqlite.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sqlite.inc.php
new file mode 100644
index 0000000..aa32223
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sqlite.inc.php
@@ -0,0 +1,453 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ SQLite info: http://www.hwaci.com/sw/sqlite/
+
+ Install Instructions:
+ ====================
+ 1. Place this in adodb/drivers
+ 2. Rename the file, remove the .txt prefix.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB_sqlite extends ADOConnection {
+ var $databaseType = "sqlite";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $concat_operator='||';
+ var $_errorNo = 0;
+ var $hasLimit = true;
+ var $hasInsertID = true; /// supports autoincrement ID?
+ var $hasAffectedRows = true; /// supports affected rows for update/delete?
+ var $metaTablesSQL = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
+ var $sysDate = "adodb_date('Y-m-d')";
+ var $sysTimeStamp = "adodb_date('Y-m-d H:i:s')";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+
+ function __construct()
+ {
+ }
+
+ function ServerInfo()
+ {
+ $arr['version'] = sqlite_libversion();
+ $arr['description'] = 'SQLite ';
+ $arr['encoding'] = sqlite_libencoding();
+ return $arr;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ $ret = $this->Execute("BEGIN TRANSACTION");
+ $this->transCnt += 1;
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ if (!$ok) {
+ return $this->RollbackTrans();
+ }
+ $ret = $this->Execute("COMMIT");
+ if ($this->transCnt > 0) {
+ $this->transCnt -= 1;
+ }
+ return !empty($ret);
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ $ret = $this->Execute("ROLLBACK");
+ if ($this->transCnt > 0) {
+ $this->transCnt -= 1;
+ }
+ return !empty($ret);
+ }
+
+ // mark newnham
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+ $rs = $this->Execute("PRAGMA table_info('$table')");
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ if (!$rs) {
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+ $arr = array();
+ while ($r = $rs->FetchRow()) {
+ $type = explode('(',$r['type']);
+ $size = '';
+ if (sizeof($type)==2) {
+ $size = trim($type[1],')');
+ }
+ $fn = strtoupper($r['name']);
+ $fld = new ADOFieldObject;
+ $fld->name = $r['name'];
+ $fld->type = $type[0];
+ $fld->max_length = $size;
+ $fld->not_null = $r['notnull'];
+ $fld->default_value = $r['dflt_value'];
+ $fld->scale = 0;
+ if (isset($r['pk']) && $r['pk']) {
+ $fld->primary_key=1;
+ }
+ if ($save == ADODB_FETCH_NUM) {
+ $arr[] = $fld;
+ } else {
+ $arr[strtoupper($fld->name)] = $fld;
+ }
+ }
+ $rs->Close();
+ $ADODB_FETCH_MODE = $save;
+ return $arr;
+ }
+
+ function _init($parentDriver)
+ {
+ $parentDriver->hasTransactions = false;
+ $parentDriver->hasInsertID = true;
+ }
+
+ function _insertid()
+ {
+ return sqlite_last_insert_rowid($this->_connectionID);
+ }
+
+ function _affectedrows()
+ {
+ return sqlite_changes($this->_connectionID);
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_logsql) {
+ return $this->_errorMsg;
+ }
+ return ($this->_errorNo) ? sqlite_error_string($this->_errorNo) : '';
+ }
+
+ function ErrorNo()
+ {
+ return $this->_errorNo;
+ }
+
+ function SQLDate($fmt, $col=false)
+ {
+ $fmt = $this->qstr($fmt);
+ return ($col) ? "adodb_date2($fmt,$col)" : "adodb_date($fmt)";
+ }
+
+
+ function _createFunctions()
+ {
+ @sqlite_create_function($this->_connectionID, 'adodb_date', 'adodb_date', 1);
+ @sqlite_create_function($this->_connectionID, 'adodb_date2', 'adodb_date2', 2);
+ }
+
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('sqlite_open')) {
+ return null;
+ }
+ if (empty($argHostname) && $argDatabasename) {
+ $argHostname = $argDatabasename;
+ }
+
+ $this->_connectionID = sqlite_open($argHostname);
+ if ($this->_connectionID === false) {
+ return false;
+ }
+ $this->_createFunctions();
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('sqlite_open')) {
+ return null;
+ }
+ if (empty($argHostname) && $argDatabasename) {
+ $argHostname = $argDatabasename;
+ }
+
+ $this->_connectionID = sqlite_popen($argHostname);
+ if ($this->_connectionID === false) {
+ return false;
+ }
+ $this->_createFunctions();
+ return true;
+ }
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ $rez = sqlite_query($sql,$this->_connectionID);
+ if (!$rez) {
+ $this->_errorNo = sqlite_last_error($this->_connectionID);
+ }
+ // If no data was returned, we don't need to create a real recordset
+ // Note: this code is untested, as I don't have a sqlite2 setup available
+ elseif (sqlite_num_fields($rez) == 0) {
+ $rez = true;
+ }
+
+ return $rez;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr = ($offset >= 0) ? " OFFSET $offset" : '';
+ $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ($offset >= 0 ? ' LIMIT 999999999' : '');
+ if ($secs2cache) {
+ $rs = $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr);
+ } else {
+ $rs = $this->Execute($sql."$limitStr$offsetStr",$inputarr);
+ }
+
+ return $rs;
+ }
+
+ /*
+ This algorithm is not very efficient, but works even if table locking
+ is not available.
+
+ Will return false if unable to generate an ID after $MAXLOOPS attempts.
+ */
+ var $_genSeqSQL = "create table %s (id integer)";
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ // if you have to modify the parameter below, your database is overloaded,
+ // or you need to implement generation of id's yourself!
+ $MAXLOOPS = 100;
+ //$this->debug=1;
+ while (--$MAXLOOPS>=0) {
+ @($num = $this->GetOne("select id from $seq"));
+ if ($num === false) {
+ $this->Execute(sprintf($this->_genSeqSQL ,$seq));
+ $start -= 1;
+ $num = '0';
+ $ok = $this->Execute("insert into $seq values($start)");
+ if (!$ok) {
+ return false;
+ }
+ }
+ $this->Execute("update $seq set id=id+1 where id=$num");
+
+ if ($this->affected_rows() > 0) {
+ $num += 1;
+ $this->genID = $num;
+ return $num;
+ }
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
+ }
+ return false;
+ }
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ if (empty($this->_genSeqSQL)) {
+ return false;
+ }
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) {
+ return false;
+ }
+ $start -= 1;
+ return $this->Execute("insert into $seqname values($start)");
+ }
+
+ var $_dropSeqSQL = 'drop table %s';
+ function DropSequence($seqname = 'adodbseq')
+ {
+ if (empty($this->_dropSeqSQL)) {
+ return false;
+ }
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ // returns true or false
+ function _close()
+ {
+ return @sqlite_close($this->_connectionID);
+ }
+
+ function MetaIndexes($table, $primary = FALSE, $owner = false)
+ {
+ $false = false;
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+ $SQL=sprintf("SELECT name,sql FROM sqlite_master WHERE type='index' AND tbl_name='%s'", strtolower($table));
+ $rs = $this->Execute($SQL);
+ if (!is_object($rs)) {
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+
+ $indexes = array ();
+ while ($row = $rs->FetchRow()) {
+ if ($primary && preg_match("/primary/i",$row[1]) == 0) {
+ continue;
+ }
+ if (!isset($indexes[$row[0]])) {
+ $indexes[$row[0]] = array(
+ 'unique' => preg_match("/unique/i",$row[1]),
+ 'columns' => array()
+ );
+ }
+ /**
+ * There must be a more elegant way of doing this,
+ * the index elements appear in the SQL statement
+ * in cols[1] between parentheses
+ * e.g CREATE UNIQUE INDEX ware_0 ON warehouse (org,warehouse)
+ */
+ $cols = explode("(",$row[1]);
+ $cols = explode(")",$cols[1]);
+ array_pop($cols);
+ $indexes[$row[0]]['columns'] = $cols;
+ }
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ }
+ return $indexes;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_sqlite extends ADORecordSet {
+
+ var $databaseType = "sqlite";
+ var $bind = false;
+
+ function __construct($queryID,$mode=false)
+ {
+
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch($mode) {
+ case ADODB_FETCH_NUM:
+ $this->fetchMode = SQLITE_NUM;
+ break;
+ case ADODB_FETCH_ASSOC:
+ $this->fetchMode = SQLITE_ASSOC;
+ break;
+ default:
+ $this->fetchMode = SQLITE_BOTH;
+ break;
+ }
+ $this->adodbFetchMode = $mode;
+
+ $this->_queryID = $queryID;
+
+ $this->_inited = true;
+ $this->fields = array();
+ if ($queryID) {
+ $this->_currentRow = 0;
+ $this->EOF = !$this->_fetch();
+ @$this->_initrs();
+ } else {
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ $this->EOF = true;
+ }
+
+ return $this->_queryID;
+ }
+
+
+ function FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $fld->name = sqlite_field_name($this->_queryID, $fieldOffset);
+ $fld->type = 'VARCHAR';
+ $fld->max_length = -1;
+ return $fld;
+ }
+
+ function _initrs()
+ {
+ $this->_numOfRows = @sqlite_num_rows($this->_queryID);
+ $this->_numOfFields = @sqlite_num_fields($this->_queryID);
+ }
+
+ function Fields($colname)
+ {
+ if ($this->fetchMode != SQLITE_NUM) {
+ return $this->fields[$colname];
+ }
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _seek($row)
+ {
+ return sqlite_seek($this->_queryID, $row);
+ }
+
+ function _fetch($ignore_fields=false)
+ {
+ $this->fields = @sqlite_fetch_array($this->_queryID,$this->fetchMode);
+ return !empty($this->fields);
+ }
+
+ function _close()
+ {
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sqlite3.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sqlite3.inc.php
new file mode 100644
index 0000000..1953c21
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sqlite3.inc.php
@@ -0,0 +1,440 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Latest version is available at http://adodb.org/
+
+ SQLite info: http://www.hwaci.com/sw/sqlite/
+
+ Install Instructions:
+ ====================
+ 1. Place this in adodb/drivers
+ 2. Rename the file, remove the .txt prefix.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB_sqlite3 extends ADOConnection {
+ var $databaseType = "sqlite3";
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $concat_operator='||';
+ var $_errorNo = 0;
+ var $hasLimit = true;
+ var $hasInsertID = true; /// supports autoincrement ID?
+ var $hasAffectedRows = true; /// supports affected rows for update/delete?
+ var $metaTablesSQL = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
+ var $sysDate = "adodb_date('Y-m-d')";
+ var $sysTimeStamp = "adodb_date('Y-m-d H:i:s')";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+
+ function __construct()
+ {
+ }
+
+ function ServerInfo()
+ {
+ $version = SQLite3::version();
+ $arr['version'] = $version['versionString'];
+ $arr['description'] = 'SQLite 3';
+ return $arr;
+ }
+
+ function BeginTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ $ret = $this->Execute("BEGIN TRANSACTION");
+ $this->transCnt += 1;
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ if (!$ok) {
+ return $this->RollbackTrans();
+ }
+ $ret = $this->Execute("COMMIT");
+ if ($this->transCnt > 0) {
+ $this->transCnt -= 1;
+ }
+ return !empty($ret);
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) {
+ return true;
+ }
+ $ret = $this->Execute("ROLLBACK");
+ if ($this->transCnt > 0) {
+ $this->transCnt -= 1;
+ }
+ return !empty($ret);
+ }
+
+ // mark newnham
+ function MetaColumns($table, $normalize=true)
+ {
+ global $ADODB_FETCH_MODE;
+ $false = false;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($this->fetchMode !== false) {
+ $savem = $this->SetFetchMode(false);
+ }
+ $rs = $this->Execute("PRAGMA table_info('$table')");
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ if (!$rs) {
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+ $arr = array();
+ while ($r = $rs->FetchRow()) {
+ $type = explode('(',$r['type']);
+ $size = '';
+ if (sizeof($type)==2) {
+ $size = trim($type[1],')');
+ }
+ $fn = strtoupper($r['name']);
+ $fld = new ADOFieldObject;
+ $fld->name = $r['name'];
+ $fld->type = $type[0];
+ $fld->max_length = $size;
+ $fld->not_null = $r['notnull'];
+ $fld->default_value = $r['dflt_value'];
+ $fld->scale = 0;
+ if (isset($r['pk']) && $r['pk']) {
+ $fld->primary_key=1;
+ }
+ if ($save == ADODB_FETCH_NUM) {
+ $arr[] = $fld;
+ } else {
+ $arr[strtoupper($fld->name)] = $fld;
+ }
+ }
+ $rs->Close();
+ $ADODB_FETCH_MODE = $save;
+ return $arr;
+ }
+
+ function _init($parentDriver)
+ {
+ $parentDriver->hasTransactions = false;
+ $parentDriver->hasInsertID = true;
+ }
+
+ function _insertid()
+ {
+ return $this->_connectionID->lastInsertRowID();
+ }
+
+ function _affectedrows()
+ {
+ return $this->_connectionID->changes();
+ }
+
+ function ErrorMsg()
+ {
+ if ($this->_logsql) {
+ return $this->_errorMsg;
+ }
+ return ($this->_errorNo) ? $this->ErrorNo() : ''; //**tochange?
+ }
+
+ function ErrorNo()
+ {
+ return $this->_connectionID->lastErrorCode(); //**tochange??
+ }
+
+ function SQLDate($fmt, $col=false)
+ {
+ $fmt = $this->qstr($fmt);
+ return ($col) ? "adodb_date2($fmt,$col)" : "adodb_date($fmt)";
+ }
+
+
+ function _createFunctions()
+ {
+ $this->_connectionID->createFunction('adodb_date', 'adodb_date', 1);
+ $this->_connectionID->createFunction('adodb_date2', 'adodb_date2', 2);
+ }
+
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (empty($argHostname) && $argDatabasename) {
+ $argHostname = $argDatabasename;
+ }
+ $this->_connectionID = new SQLite3($argHostname);
+ $this->_createFunctions();
+
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ // There's no permanent connect in SQLite3
+ return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
+ }
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ $rez = $this->_connectionID->query($sql);
+ if ($rez === false) {
+ $this->_errorNo = $this->_connectionID->lastErrorCode();
+ }
+ // If no data was returned, we don't need to create a real recordset
+ elseif ($rez->numColumns() == 0) {
+ $rez->finalize();
+ $rez = true;
+ }
+
+ return $rez;
+ }
+
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ $nrows = (int) $nrows;
+ $offset = (int) $offset;
+ $offsetStr = ($offset >= 0) ? " OFFSET $offset" : '';
+ $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ($offset >= 0 ? ' LIMIT 999999999' : '');
+ if ($secs2cache) {
+ $rs = $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr);
+ } else {
+ $rs = $this->Execute($sql."$limitStr$offsetStr",$inputarr);
+ }
+
+ return $rs;
+ }
+
+ /*
+ This algorithm is not very efficient, but works even if table locking
+ is not available.
+
+ Will return false if unable to generate an ID after $MAXLOOPS attempts.
+ */
+ var $_genSeqSQL = "create table %s (id integer)";
+
+ function GenID($seq='adodbseq',$start=1)
+ {
+ // if you have to modify the parameter below, your database is overloaded,
+ // or you need to implement generation of id's yourself!
+ $MAXLOOPS = 100;
+ //$this->debug=1;
+ while (--$MAXLOOPS>=0) {
+ @($num = $this->GetOne("select id from $seq"));
+ if ($num === false) {
+ $this->Execute(sprintf($this->_genSeqSQL ,$seq));
+ $start -= 1;
+ $num = '0';
+ $ok = $this->Execute("insert into $seq values($start)");
+ if (!$ok) {
+ return false;
+ }
+ }
+ $this->Execute("update $seq set id=id+1 where id=$num");
+
+ if ($this->affected_rows() > 0) {
+ $num += 1;
+ $this->genID = $num;
+ return $num;
+ }
+ }
+ if ($fn = $this->raiseErrorFn) {
+ $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
+ }
+ return false;
+ }
+
+ function CreateSequence($seqname='adodbseq',$start=1)
+ {
+ if (empty($this->_genSeqSQL)) {
+ return false;
+ }
+ $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
+ if (!$ok) {
+ return false;
+ }
+ $start -= 1;
+ return $this->Execute("insert into $seqname values($start)");
+ }
+
+ var $_dropSeqSQL = 'drop table %s';
+ function DropSequence($seqname = 'adodbseq')
+ {
+ if (empty($this->_dropSeqSQL)) {
+ return false;
+ }
+ return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
+ }
+
+ // returns true or false
+ function _close()
+ {
+ return $this->_connectionID->close();
+ }
+
+ function MetaIndexes($table, $primary = FALSE, $owner = false)
+ {
+ $false = false;
+ // save old fetch mode
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->fetchMode !== FALSE) {
+ $savem = $this->SetFetchMode(FALSE);
+ }
+ $SQL=sprintf("SELECT name,sql FROM sqlite_master WHERE type='index' AND tbl_name='%s'", strtolower($table));
+ $rs = $this->Execute($SQL);
+ if (!is_object($rs)) {
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ }
+ $ADODB_FETCH_MODE = $save;
+ return $false;
+ }
+
+ $indexes = array ();
+ while ($row = $rs->FetchRow()) {
+ if ($primary && preg_match("/primary/i",$row[1]) == 0) {
+ continue;
+ }
+ if (!isset($indexes[$row[0]])) {
+ $indexes[$row[0]] = array(
+ 'unique' => preg_match("/unique/i",$row[1]),
+ 'columns' => array()
+ );
+ }
+ /**
+ * There must be a more elegant way of doing this,
+ * the index elements appear in the SQL statement
+ * in cols[1] between parentheses
+ * e.g CREATE UNIQUE INDEX ware_0 ON warehouse (org,warehouse)
+ */
+ $cols = explode("(",$row[1]);
+ $cols = explode(")",$cols[1]);
+ array_pop($cols);
+ $indexes[$row[0]]['columns'] = $cols;
+ }
+ if (isset($savem)) {
+ $this->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+ }
+ return $indexes;
+ }
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_sqlite3 extends ADORecordSet {
+
+ var $databaseType = "sqlite3";
+ var $bind = false;
+
+ function __construct($queryID,$mode=false)
+ {
+
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ switch($mode) {
+ case ADODB_FETCH_NUM:
+ $this->fetchMode = SQLITE3_NUM;
+ break;
+ case ADODB_FETCH_ASSOC:
+ $this->fetchMode = SQLITE3_ASSOC;
+ break;
+ default:
+ $this->fetchMode = SQLITE3_BOTH;
+ break;
+ }
+ $this->adodbFetchMode = $mode;
+
+ $this->_queryID = $queryID;
+
+ $this->_inited = true;
+ $this->fields = array();
+ if ($queryID) {
+ $this->_currentRow = 0;
+ $this->EOF = !$this->_fetch();
+ @$this->_initrs();
+ } else {
+ $this->_numOfRows = 0;
+ $this->_numOfFields = 0;
+ $this->EOF = true;
+ }
+
+ return $this->_queryID;
+ }
+
+
+ function FetchField($fieldOffset = -1)
+ {
+ $fld = new ADOFieldObject;
+ $fld->name = $this->_queryID->columnName($fieldOffset);
+ $fld->type = 'VARCHAR';
+ $fld->max_length = -1;
+ return $fld;
+ }
+
+ function _initrs()
+ {
+ $this->_numOfFields = $this->_queryID->numColumns();
+
+ }
+
+ function Fields($colname)
+ {
+ if ($this->fetchMode != SQLITE3_NUM) {
+ return $this->fields[$colname];
+ }
+ if (!$this->bind) {
+ $this->bind = array();
+ for ($i=0; $i < $this->_numOfFields; $i++) {
+ $o = $this->FetchField($i);
+ $this->bind[strtoupper($o->name)] = $i;
+ }
+ }
+
+ return $this->fields[$this->bind[strtoupper($colname)]];
+ }
+
+ function _seek($row)
+ {
+ // sqlite3 does not implement seek
+ if ($this->debug) {
+ ADOConnection::outp("SQLite3 does not implement seek");
+ }
+ return false;
+ }
+
+ function _fetch($ignore_fields=false)
+ {
+ $this->fields = $this->_queryID->fetchArray($this->fetchMode);
+ return !empty($this->fields);
+ }
+
+ function _close()
+ {
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sqlitepo.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sqlitepo.inc.php
new file mode 100644
index 0000000..edf71ef
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sqlitepo.inc.php
@@ -0,0 +1,58 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Portable version of sqlite driver, to make it more similar to other database drivers.
+ The main differences are
+
+ 1. When selecting (joining) multiple tables, in assoc mode the table
+ names are included in the assoc keys in the "sqlite" driver.
+
+ In "sqlitepo" driver, the table names are stripped from the returned column names.
+ When this results in a conflict, the first field get preference.
+
+ Contributed by Herman Kuiper herman#ozuzo.net
+*/
+
+if (!defined('ADODB_DIR')) die();
+
+include_once(ADODB_DIR.'/drivers/adodb-sqlite.inc.php');
+
+class ADODB_sqlitepo extends ADODB_sqlite {
+ var $databaseType = 'sqlitepo';
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+class ADORecordset_sqlitepo extends ADORecordset_sqlite {
+
+ var $databaseType = 'sqlitepo';
+
+ function __construct($queryID,$mode=false)
+ {
+ parent::__construct($queryID,$mode);
+ }
+
+ // Modified to strip table names from returned fields
+ function _fetch($ignore_fields=false)
+ {
+ $this->fields = array();
+ $fields = @sqlite_fetch_array($this->_queryID,$this->fetchMode);
+ if(is_array($fields))
+ foreach($fields as $n => $v)
+ {
+ if(($p = strpos($n, ".")) !== false)
+ $n = substr($n, $p+1);
+ $this->fields[$n] = $v;
+ }
+
+ return !empty($this->fields);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sybase.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sybase.inc.php
new file mode 100644
index 0000000..62aef61
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sybase.inc.php
@@ -0,0 +1,445 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim. All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Sybase driver contributed by Toni (toni.tunkkari@finebyte.com)
+
+ - MSSQL date patch applied.
+
+ Date patch by Toni 15 Feb 2002
+*/
+
+ // security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class ADODB_sybase extends ADOConnection {
+ var $databaseType = "sybase";
+ var $dataProvider = 'sybase';
+ var $replaceQuote = "''"; // string to use to replace quotes
+ var $fmtDate = "'Y-m-d'";
+ var $fmtTimeStamp = "'Y-m-d H:i:s'";
+ var $hasInsertID = true;
+ var $hasAffectedRows = true;
+ var $metaTablesSQL="select name from sysobjects where type='U' or type='V'";
+ // see http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=5981;uf=0?target=0;window=new;showtoc=true;book=dbrfen8
+ var $metaColumnsSQL = "SELECT c.column_name, c.column_type, c.width FROM syscolumn c, systable t WHERE t.table_name='%s' AND c.table_id=t.table_id AND t.table_type='BASE'";
+ /*
+ "select c.name,t.name,c.length from
+ syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id
+ where o.name='%s'";
+ */
+ var $concat_operator = '+';
+ var $arrayClass = 'ADORecordSet_array_sybase';
+ var $sysDate = 'GetDate()';
+ var $leftOuter = '*=';
+ var $rightOuter = '=*';
+
+ var $port;
+
+ function __construct()
+ {
+ }
+
+ // might require begintrans -- committrans
+ function _insertid()
+ {
+ return $this->GetOne('select @@identity');
+ }
+ // might require begintrans -- committrans
+ function _affectedrows()
+ {
+ return $this->GetOne('select @@rowcount');
+ }
+
+
+ function BeginTrans()
+ {
+
+ if ($this->transOff) return true;
+ $this->transCnt += 1;
+
+ $this->Execute('BEGIN TRAN');
+ return true;
+ }
+
+ function CommitTrans($ok=true)
+ {
+ if ($this->transOff) return true;
+
+ if (!$ok) return $this->RollbackTrans();
+
+ $this->transCnt -= 1;
+ $this->Execute('COMMIT TRAN');
+ return true;
+ }
+
+ function RollbackTrans()
+ {
+ if ($this->transOff) return true;
+ $this->transCnt -= 1;
+ $this->Execute('ROLLBACK TRAN');
+ return true;
+ }
+
+ // http://www.isug.com/Sybase_FAQ/ASE/section6.1.html#6.1.4
+ function RowLock($tables,$where,$col='top 1 null as ignore')
+ {
+ if (!$this->_hastrans) $this->BeginTrans();
+ $tables = str_replace(',',' HOLDLOCK,',$tables);
+ return $this->GetOne("select $col from $tables HOLDLOCK where $where");
+
+ }
+
+ function SelectDB($dbName)
+ {
+ $this->database = $dbName;
+ $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
+ if ($this->_connectionID) {
+ return @sybase_select_db($dbName);
+ }
+ else return false;
+ }
+
+ /* Returns: the last error message from previous database operation
+ Note: This function is NOT available for Microsoft SQL Server. */
+
+
+ function ErrorMsg()
+ {
+ if ($this->_logsql) return $this->_errorMsg;
+ if (function_exists('sybase_get_last_message'))
+ $this->_errorMsg = sybase_get_last_message();
+ else {
+ $this->_errorMsg = 'SYBASE error messages not supported on this platform';
+ }
+
+ return $this->_errorMsg;
+ }
+
+ // returns true or false
+ function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('sybase_connect')) return null;
+
+ // Sybase connection on custom port
+ if ($this->port) {
+ $argHostname .= ':' . $this->port;
+ }
+
+ if ($this->charSet) {
+ $this->_connectionID = @sybase_connect($argHostname,$argUsername,$argPassword, $this->charSet);
+ } else {
+ $this->_connectionID = @sybase_connect($argHostname,$argUsername,$argPassword);
+ }
+
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ // returns true or false
+ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+ {
+ if (!function_exists('sybase_connect')) return null;
+
+ // Sybase connection on custom port
+ if ($this->port) {
+ $argHostname .= ':' . $this->port;
+ }
+
+ if ($this->charSet) {
+ $this->_connectionID = @sybase_pconnect($argHostname,$argUsername,$argPassword, $this->charSet);
+ } else {
+ $this->_connectionID = @sybase_pconnect($argHostname,$argUsername,$argPassword);
+ }
+
+ if ($this->_connectionID === false) return false;
+ if ($argDatabasename) return $this->SelectDB($argDatabasename);
+ return true;
+ }
+
+ // returns query ID if successful, otherwise false
+ function _query($sql,$inputarr=false)
+ {
+ global $ADODB_COUNTRECS;
+
+ if ($ADODB_COUNTRECS == false && ADODB_PHPVER >= 0x4300)
+ return sybase_unbuffered_query($sql,$this->_connectionID);
+ else
+ return sybase_query($sql,$this->_connectionID);
+ }
+
+ // See http://www.isug.com/Sybase_FAQ/ASE/section6.2.html#6.2.12
+ function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0)
+ {
+ if ($secs2cache > 0) {// we do not cache rowcount, so we have to load entire recordset
+ $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ return $rs;
+ }
+
+ $nrows = (integer) $nrows;
+ $offset = (integer) $offset;
+
+ $cnt = ($nrows >= 0) ? $nrows : 999999999;
+ if ($offset > 0 && $cnt) $cnt += $offset;
+
+ $this->Execute("set rowcount $cnt");
+ $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,0);
+ $this->Execute("set rowcount 0");
+
+ return $rs;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ return @sybase_close($this->_connectionID);
+ }
+
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_sybase::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_sybase::UnixTimeStamp($v);
+ }
+
+
+
+ # Added 2003-10-05 by Chris Phillipson
+ # Used ASA SQL Reference Manual -- http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=16756?target=%25N%15_12018_START_RESTART_N%25
+ # to convert similar Microsoft SQL*Server (mssql) API into Sybase compatible version
+ // Format date column in sql string given an input format that understands Y M D
+ function SQLDate($fmt, $col=false)
+ {
+ if (!$col) $col = $this->sysTimeStamp;
+ $s = '';
+
+ $len = strlen($fmt);
+ for ($i=0; $i < $len; $i++) {
+ if ($s) $s .= '+';
+ $ch = $fmt[$i];
+ switch($ch) {
+ case 'Y':
+ case 'y':
+ $s .= "datename(yy,$col)";
+ break;
+ case 'M':
+ $s .= "convert(char(3),$col,0)";
+ break;
+ case 'm':
+ $s .= "str_replace(str(month($col),2),' ','0')";
+ break;
+ case 'Q':
+ case 'q':
+ $s .= "datename(qq,$col)";
+ break;
+ case 'D':
+ case 'd':
+ $s .= "str_replace(str(datepart(dd,$col),2),' ','0')";
+ break;
+ case 'h':
+ $s .= "substring(convert(char(14),$col,0),13,2)";
+ break;
+
+ case 'H':
+ $s .= "str_replace(str(datepart(hh,$col),2),' ','0')";
+ break;
+
+ case 'i':
+ $s .= "str_replace(str(datepart(mi,$col),2),' ','0')";
+ break;
+ case 's':
+ $s .= "str_replace(str(datepart(ss,$col),2),' ','0')";
+ break;
+ case 'a':
+ case 'A':
+ $s .= "substring(convert(char(19),$col,0),18,2)";
+ break;
+
+ default:
+ if ($ch == '\\') {
+ $i++;
+ $ch = substr($fmt,$i,1);
+ }
+ $s .= $this->qstr($ch);
+ break;
+ }
+ }
+ return $s;
+ }
+
+ # Added 2003-10-07 by Chris Phillipson
+ # Used ASA SQL Reference Manual -- http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=5981;uf=0?target=0;window=new;showtoc=true;book=dbrfen8
+ # to convert similar Microsoft SQL*Server (mssql) API into Sybase compatible version
+ function MetaPrimaryKeys($table, $owner = false)
+ {
+ $sql = "SELECT c.column_name " .
+ "FROM syscolumn c, systable t " .
+ "WHERE t.table_name='$table' AND c.table_id=t.table_id " .
+ "AND t.table_type='BASE' " .
+ "AND c.pkey = 'Y' " .
+ "ORDER BY c.column_id";
+
+ $a = $this->GetCol($sql);
+ if ($a && sizeof($a)>0) return $a;
+ return false;
+ }
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+global $ADODB_sybase_mths;
+$ADODB_sybase_mths = array(
+ 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,
+ 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
+
+class ADORecordset_sybase extends ADORecordSet {
+
+ var $databaseType = "sybase";
+ var $canSeek = true;
+ // _mths works only in non-localised system
+ var $_mths = array('JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
+
+ function __construct($id,$mode=false)
+ {
+ if ($mode === false) {
+ global $ADODB_FETCH_MODE;
+ $mode = $ADODB_FETCH_MODE;
+ }
+ if (!$mode) $this->fetchMode = ADODB_FETCH_ASSOC;
+ else $this->fetchMode = $mode;
+ parent::__construct($id,$mode);
+ }
+
+ /* Returns: an object containing field information.
+ Get column information in the Recordset object. fetchField() can be used in order to obtain information about
+ fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
+ fetchField() is retrieved. */
+ function FetchField($fieldOffset = -1)
+ {
+ if ($fieldOffset != -1) {
+ $o = @sybase_fetch_field($this->_queryID, $fieldOffset);
+ }
+ else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */
+ $o = @sybase_fetch_field($this->_queryID);
+ }
+ // older versions of PHP did not support type, only numeric
+ if ($o && !isset($o->type)) $o->type = ($o->numeric) ? 'float' : 'varchar';
+ return $o;
+ }
+
+ function _initrs()
+ {
+ global $ADODB_COUNTRECS;
+ $this->_numOfRows = ($ADODB_COUNTRECS)? @sybase_num_rows($this->_queryID):-1;
+ $this->_numOfFields = @sybase_num_fields($this->_queryID);
+ }
+
+ function _seek($row)
+ {
+ return @sybase_data_seek($this->_queryID, $row);
+ }
+
+ function _fetch($ignore_fields=false)
+ {
+ if ($this->fetchMode == ADODB_FETCH_NUM) {
+ $this->fields = @sybase_fetch_row($this->_queryID);
+ } else if ($this->fetchMode == ADODB_FETCH_ASSOC) {
+ $this->fields = @sybase_fetch_assoc($this->_queryID);
+
+ if (is_array($this->fields)) {
+ $this->fields = $this->GetRowAssoc();
+ return true;
+ }
+ return false;
+ } else {
+ $this->fields = @sybase_fetch_array($this->_queryID);
+ }
+ if ( is_array($this->fields)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /* close() only needs to be called if you are worried about using too much memory while your script
+ is running. All associated result memory for the specified result identifier will automatically be freed. */
+ function _close() {
+ return @sybase_free_result($this->_queryID);
+ }
+
+ // sybase/mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ return ADORecordSet_array_sybase::UnixDate($v);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ return ADORecordSet_array_sybase::UnixTimeStamp($v);
+ }
+}
+
+class ADORecordSet_array_sybase extends ADORecordSet_array {
+ function __construct($id=-1)
+ {
+ parent::__construct($id);
+ }
+
+ // sybase/mssql uses a default date like Dec 30 2000 12:00AM
+ static function UnixDate($v)
+ {
+ global $ADODB_sybase_mths;
+
+ //Dec 30 2000 12:00AM
+ if (!preg_match( "/([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})/"
+ ,$v, $rr)) return parent::UnixDate($v);
+
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $themth = substr(strtoupper($rr[1]),0,3);
+ $themth = $ADODB_sybase_mths[$themth];
+ if ($themth <= 0) return false;
+ // h-m-s-MM-DD-YY
+ return adodb_mktime(0,0,0,$themth,$rr[2],$rr[3]);
+ }
+
+ static function UnixTimeStamp($v)
+ {
+ global $ADODB_sybase_mths;
+ //11.02.2001 Toni Tunkkari toni.tunkkari@finebyte.com
+ //Changed [0-9] to [0-9 ] in day conversion
+ if (!preg_match( "/([A-Za-z]{3})[-/\. ]([0-9 ]{1,2})[-/\. ]([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})/"
+ ,$v, $rr)) return parent::UnixTimeStamp($v);
+ if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+
+ $themth = substr(strtoupper($rr[1]),0,3);
+ $themth = $ADODB_sybase_mths[$themth];
+ if ($themth <= 0) return false;
+
+ switch (strtoupper($rr[6])) {
+ case 'P':
+ if ($rr[4]<12) $rr[4] += 12;
+ break;
+ case 'A':
+ if ($rr[4]==12) $rr[4] = 0;
+ break;
+ default:
+ break;
+ }
+ // h-m-s-MM-DD-YY
+ return adodb_mktime($rr[4],$rr[5],0,$themth,$rr[2],$rr[3]);
+ }
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-sybase_ase.inc.php b/vendor/adodb/adodb-php/drivers/adodb-sybase_ase.inc.php
new file mode 100644
index 0000000..070432d
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-sybase_ase.inc.php
@@ -0,0 +1,120 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4.
+
+ Contributed by Interakt Online. Thx Cristian MARIN cristic#interaktonline.com
+*/
+
+
+require_once ADODB_DIR."/drivers/adodb-sybase.inc.php";
+
+class ADODB_sybase_ase extends ADODB_sybase {
+ var $databaseType = "sybase_ase";
+
+ var $metaTablesSQL="SELECT sysobjects.name FROM sysobjects, sysusers WHERE sysobjects.type='U' AND sysobjects.uid = sysusers.uid";
+ var $metaColumnsSQL = "SELECT syscolumns.name AS field_name, systypes.name AS type, systypes.length AS width FROM sysobjects, syscolumns, systypes WHERE sysobjects.name='%s' AND syscolumns.id = sysobjects.id AND systypes.type=syscolumns.type";
+ var $metaDatabasesSQL ="SELECT a.name FROM master.dbo.sysdatabases a, master.dbo.syslogins b WHERE a.suid = b.suid and a.name like '%' and a.name != 'tempdb' and a.status3 != 256 order by 1";
+
+ function __construct()
+ {
+ }
+
+ // split the Views, Tables and procedures.
+ function MetaTables($ttype=false,$showSchema=false,$mask=false)
+ {
+ $false = false;
+ if ($this->metaTablesSQL) {
+ // complicated state saving by the need for backward compat
+
+ if ($ttype == 'VIEWS'){
+ $sql = str_replace('U', 'V', $this->metaTablesSQL);
+ }elseif (false === $ttype){
+ $sql = str_replace('U',"U' OR type='V", $this->metaTablesSQL);
+ }else{ // TABLES OR ANY OTHER
+ $sql = $this->metaTablesSQL;
+ }
+ $rs = $this->Execute($sql);
+
+ if ($rs === false || !method_exists($rs, 'GetArray')){
+ return $false;
+ }
+ $arr = $rs->GetArray();
+
+ $arr2 = array();
+ foreach($arr as $key=>$value){
+ $arr2[] = trim($value['name']);
+ }
+ return $arr2;
+ }
+ return $false;
+ }
+
+ function MetaDatabases()
+ {
+ $arr = array();
+ if ($this->metaDatabasesSQL!='') {
+ $rs = $this->Execute($this->metaDatabasesSQL);
+ if ($rs && !$rs->EOF){
+ while (!$rs->EOF){
+ $arr[] = $rs->Fields('name');
+ $rs->MoveNext();
+ }
+ return $arr;
+ }
+ }
+ return false;
+ }
+
+ // fix a bug which prevent the metaColumns query to be executed for Sybase ASE
+ function MetaColumns($table,$upper=false)
+ {
+ $false = false;
+ if (!empty($this->metaColumnsSQL)) {
+
+ $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
+ if ($rs === false) return $false;
+
+ $retarr = array();
+ while (!$rs->EOF) {
+ $fld = new ADOFieldObject();
+ $fld->name = $rs->Fields('field_name');
+ $fld->type = $rs->Fields('type');
+ $fld->max_length = $rs->Fields('width');
+ $retarr[strtoupper($fld->name)] = $fld;
+ $rs->MoveNext();
+ }
+ $rs->Close();
+ return $retarr;
+ }
+ return $false;
+ }
+
+ function getProcedureList($schema)
+ {
+ return false;
+ }
+
+ function ErrorMsg()
+ {
+ if (!function_exists('sybase_connect')){
+ return 'Your PHP doesn\'t contain the Sybase connection module!';
+ }
+ return parent::ErrorMsg();
+ }
+}
+
+class adorecordset_sybase_ase extends ADORecordset_sybase {
+var $databaseType = "sybase_ase";
+function __construct($id,$mode=false)
+ {
+ parent::__construct($id,$mode);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/drivers/adodb-text.inc.php b/vendor/adodb/adodb-php/drivers/adodb-text.inc.php
new file mode 100644
index 0000000..62073b3
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-text.inc.php
@@ -0,0 +1,388 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Set tabs to 4.
+*/
+
+/*
+Setup:
+
+ $db = NewADOConnection('text');
+ $db->Connect($array,[$types],[$colnames]);
+
+ Parameter $array is the 2 dimensional array of data. The first row can contain the
+ column names. If column names is not defined in first row, you MUST define $colnames,
+ the 3rd parameter.
+
+ Parameter $types is optional. If defined, it should contain an array matching
+ the number of columns in $array, with each element matching the correct type defined
+ by MetaType: (B,C,I,L,N). If undefined, we will probe for $this->_proberows rows
+ to guess the type. Only C,I and N are recognised.
+
+ Parameter $colnames is optional. If defined, it is an array that contains the
+ column names of $array. If undefined, we assume the first row of $array holds the
+ column names.
+
+ The Execute() function will return a recordset. The recordset works like a normal recordset.
+ We have partial support for SQL parsing. We process the SQL using the following rules:
+
+ 1. SQL order by's always work for the first column ordered. Subsequent cols are ignored
+
+ 2. All operations take place on the same table. No joins possible. In fact the FROM clause
+ is ignored! You can use any name for the table.
+
+ 3. To simplify code, all columns are returned, except when selecting 1 column
+
+ $rs = $db->Execute('select col1,col2 from table'); // sql ignored, will generate all cols
+
+ We special case handling of 1 column because it is used in filter popups
+
+ $rs = $db->Execute('select col1 from table');
+ // sql accepted and processed -- any table name is accepted
+
+ $rs = $db->Execute('select distinct col1 from table');
+ // sql accepted and processed
+
+4. Where clauses are ignored, but searching with the 3rd parameter of Execute is permitted.
+ This has to use PHP syntax and we will eval() it. You can even use PHP functions.
+
+ $rs = $db->Execute('select * from table',false,"\$COL1='abc' and $\COL2=3")
+ // the 3rd param is searched -- make sure that $COL1 is a legal column name
+ // and all column names must be in upper case.
+
+4. Group by, having, other clauses are ignored
+
+5. Expression columns, min(), max() are ignored
+
+6. All data is readonly. Only SELECTs permitted.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (! defined("_ADODB_TEXT_LAYER")) {
+ define("_ADODB_TEXT_LAYER", 1 );
+
+// for sorting in _query()
+function adodb_cmp($a, $b) {
+ if ($a[0] == $b[0]) return 0;
+ return ($a[0] < $b[0]) ? -1 : 1;
+}
+// for sorting in _query()
+function adodb_cmpr($a, $b) {
+ if ($a[0] == $b[0]) return 0;
+ return ($a[0] > $b[0]) ? -1 : 1;
+}
+class ADODB_text extends ADOConnection {
+ var $databaseType = 'text';
+
+ var $_origarray; // original data
+ var $_types;
+ var $_proberows = 8;
+ var $_colnames;
+ var $_skiprow1=false;
+ var $readOnly = true;
+ var $hasTransactions = false;
+
+ var $_rezarray;
+ var $_reznames;
+ var $_reztypes;
+
+ function __construct()
+ {
+ }
+
+ function RSRecordCount()
+ {
+ if (!empty($this->_rezarray)) return sizeof($this->_rezarray);
+
+ return sizeof($this->_origarray);
+ }
+
+ function _insertid()
+ {
+ return false;
+ }
+
+ function _affectedrows()
+ {
+ return false;
+ }
+
+ // returns true or false
+ function PConnect(&$array, $types = false, $colnames = false)
+ {
+ return $this->Connect($array, $types, $colnames);
+ }
+ // returns true or false
+ function Connect(&$array, $types = false, $colnames = false)
+ {
+ if (is_string($array) and $array === 'iluvphplens') return 'me2';
+
+ if (!$array) {
+ $this->_origarray = false;
+ return true;
+ }
+ $row = $array[0];
+ $cols = sizeof($row);
+
+
+ if ($colnames) $this->_colnames = $colnames;
+ else {
+ $this->_colnames = $array[0];
+ $this->_skiprow1 = true;
+ }
+ if (!$types) {
+ // probe and guess the type
+ $types = array();
+ $firstrow = true;
+ if ($this->_proberows > sizeof($array)) $max = sizeof($array);
+ else $max = $this->_proberows;
+ for ($j=($this->_skiprow1)?1:0;$j < $max; $j++) {
+ $row = $array[$j];
+ if (!$row) break;
+ $i = -1;
+ foreach($row as $v) {
+ $i += 1;
+ //print " ($i ".$types[$i]. "$v) ";
+ $v = trim($v);
+ if (!preg_match('/^[+-]{0,1}[0-9\.]+$/',$v)) {
+ $types[$i] = 'C'; // once C, always C
+ continue;
+ }
+ if (isset($types[$i]) && $types[$i]=='C') continue;
+ if ($firstrow) {
+ // If empty string, we presume is character
+ // test for integer for 1st row only
+ // after that it is up to testing other rows to prove
+ // that it is not an integer
+ if (strlen($v) == 0) $types[0] = 'C';
+ if (strpos($v,'.') !== false) $types[0] = 'N';
+ else $types[$i] = 'I';
+ continue;
+ }
+
+ if (strpos($v,'.') !== false) $types[$i] = 'N';
+
+ }
+ $firstrow = false;
+ }
+ }
+ //print_r($types);
+ $this->_origarray = $array;
+ $this->_types = $types;
+ return true;
+ }
+
+
+
+ // returns queryID or false
+ // We presume that the select statement is on the same table (what else?),
+ // with the only difference being the order by.
+ //You can filter by using $eval and each clause is stored in $arr .eg. $arr[1] == 'name'
+ // also supports SELECT [DISTINCT] COL FROM ... -- only 1 col supported
+ function _query($sql,$input_arr,$eval=false)
+ {
+ if ($this->_origarray === false) return false;
+
+ $eval = $this->evalAll;
+ $usql = strtoupper(trim($sql));
+ $usql = preg_replace("/[\t\n\r]/",' ',$usql);
+ $usql = preg_replace('/ *BY/i',' BY',strtoupper($usql));
+
+ $eregword ='([A-Z_0-9]*)';
+ //print "<BR> $sql $eval ";
+ if ($eval) {
+ $i = 0;
+ foreach($this->_colnames as $n) {
+ $n = strtoupper(trim($n));
+ $eval = str_replace("\$$n","\$arr[$i]",$eval);
+
+ $i += 1;
+ }
+
+ $i = 0;
+ $eval = "\$rez=($eval);";
+ //print "<p>Eval string = $eval </p>";
+ $where_arr = array();
+
+ reset($this->_origarray);
+ foreach ($this->_origarray as $arr) {
+
+ if ($i == 0 && $this->_skiprow1)
+ $where_arr[] = $arr;
+ else {
+ eval($eval);
+ //print " $i: result=$rez arr[0]={$arr[0]} arr[1]={$arr[1]} <BR>\n ";
+ if ($rez) $where_arr[] = $arr;
+ }
+ $i += 1;
+ }
+ $this->_rezarray = $where_arr;
+ }else
+ $where_arr = $this->_origarray;
+
+ // THIS PROJECTION CODE ONLY WORKS FOR 1 COLUMN,
+ // OTHERWISE IT RETURNS ALL COLUMNS
+ if (substr($usql,0,7) == 'SELECT ') {
+ $at = strpos($usql,' FROM ');
+ $sel = trim(substr($usql,7,$at-7));
+
+ $distinct = false;
+ if (substr($sel,0,8) == 'DISTINCT') {
+ $distinct = true;
+ $sel = trim(substr($sel,8,$at));
+ }
+
+ // $sel holds the selection clause, comma delimited
+ // currently we only project if one column is involved
+ // this is to support popups in PHPLens
+ if (strpos(',',$sel)===false) {
+ $colarr = array();
+
+ preg_match("/$eregword/",$sel,$colarr);
+ $col = $colarr[1];
+ $i = 0;
+ $n = '';
+ reset($this->_colnames);
+ foreach ($this->_colnames as $n) {
+
+ if ($col == strtoupper(trim($n))) break;
+ $i += 1;
+ }
+
+ if ($n && $col) {
+ $distarr = array();
+ $projarray = array();
+ $projtypes = array($this->_types[$i]);
+ $projnames = array($n);
+
+ foreach ($where_arr as $a) {
+ if ($i == 0 && $this->_skiprow1) {
+ $projarray[] = array($n);
+ continue;
+ }
+
+ if ($distinct) {
+ $v = strtoupper($a[$i]);
+ if (! $distarr[$v]) {
+ $projarray[] = array($a[$i]);
+ $distarr[$v] = 1;
+ }
+ } else
+ $projarray[] = array($a[$i]);
+
+ } //foreach
+ //print_r($projarray);
+ }
+ } // check 1 column in projection
+ } // is SELECT
+
+ if (empty($projarray)) {
+ $projtypes = $this->_types;
+ $projarray = $where_arr;
+ $projnames = $this->_colnames;
+ }
+ $this->_rezarray = $projarray;
+ $this->_reztypes = $projtypes;
+ $this->_reznames = $projnames;
+
+
+ $pos = strpos($usql,' ORDER BY ');
+ if ($pos === false) return $this;
+ $orderby = trim(substr($usql,$pos+10));
+
+ preg_match("/$eregword/",$orderby,$arr);
+ if (sizeof($arr) < 2) return $this; // actually invalid sql
+ $col = $arr[1];
+ $at = (integer) $col;
+ if ($at == 0) {
+ $i = 0;
+ reset($projnames);
+ foreach ($projnames as $n) {
+ if (strtoupper(trim($n)) == $col) {
+ $at = $i+1;
+ break;
+ }
+ $i += 1;
+ }
+ }
+
+ if ($at <= 0 || $at > sizeof($projarray[0])) return $this; // cannot find sort column
+ $at -= 1;
+
+ // generate sort array consisting of (sortval1, row index1) (sortval2, row index2)...
+ $sorta = array();
+ $t = $projtypes[$at];
+ $num = ($t == 'I' || $t == 'N');
+ for ($i=($this->_skiprow1)?1:0, $max = sizeof($projarray); $i < $max; $i++) {
+ $row = $projarray[$i];
+ $val = ($num)?(float)$row[$at]:$row[$at];
+ $sorta[]=array($val,$i);
+ }
+
+ // check for desc sort
+ $orderby = substr($orderby,strlen($col)+1);
+ $arr == array();
+ preg_match('/([A-Z_0-9]*)/i',$orderby,$arr);
+
+ if (trim($arr[1]) == 'DESC') $sortf = 'adodb_cmpr';
+ else $sortf = 'adodb_cmp';
+
+ // hasta la sorta babe
+ usort($sorta, $sortf);
+
+ // rearrange original array
+ $arr2 = array();
+ if ($this->_skiprow1) $arr2[] = $projarray[0];
+ foreach($sorta as $v) {
+ $arr2[] = $projarray[$v[1]];
+ }
+
+ $this->_rezarray = $arr2;
+ return $this;
+ }
+
+ /* Returns: the last error message from previous database operation */
+ function ErrorMsg()
+ {
+ return '';
+ }
+
+ /* Returns: the last error number from previous database operation */
+ function ErrorNo()
+ {
+ return 0;
+ }
+
+ // returns true or false
+ function _close()
+ {
+ }
+
+
+}
+
+/*--------------------------------------------------------------------------------------
+ Class Name: Recordset
+--------------------------------------------------------------------------------------*/
+
+
+class ADORecordSet_text extends ADORecordSet_array
+{
+
+ var $databaseType = "text";
+
+ function __construct(&$conn,$mode=false)
+ {
+ parent::__construct();
+ $this->InitArray($conn->_rezarray,$conn->_reztypes,$conn->_reznames);
+ $conn->_rezarray = false;
+ }
+
+} // class ADORecordSet_text
+
+
+} // defined
diff --git a/vendor/adodb/adodb-php/drivers/adodb-vfp.inc.php b/vendor/adodb/adodb-php/drivers/adodb-vfp.inc.php
new file mode 100644
index 0000000..4a28884
--- /dev/null
+++ b/vendor/adodb/adodb-php/drivers/adodb-vfp.inc.php
@@ -0,0 +1,103 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Microsoft Visual FoxPro data driver. Requires ODBC. Works only on MS Windows.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+if (!defined('_ADODB_ODBC_LAYER')) {
+ include(ADODB_DIR."/drivers/adodb-odbc.inc.php");
+}
+if (!defined('ADODB_VFP')){
+define('ADODB_VFP',1);
+class ADODB_vfp extends ADODB_odbc {
+ var $databaseType = "vfp";
+ var $fmtDate = "{^Y-m-d}";
+ var $fmtTimeStamp = "{^Y-m-d, h:i:sA}";
+ var $replaceQuote = "'+chr(39)+'" ;
+ var $true = '.T.';
+ var $false = '.F.';
+ var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
+ var $_bindInputArray = false; // strangely enough, setting to true does not work reliably
+ var $sysTimeStamp = 'datetime()';
+ var $sysDate = 'date()';
+ var $ansiOuter = true;
+ var $hasTransactions = false;
+ var $curmode = false ; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L
+
+ function Time()
+ {
+ return time();
+ }
+
+ function BeginTrans() { return false;}
+
+ // quote string to be sent back to database
+ function qstr($s,$nofixquotes=false)
+ {
+ if (!$nofixquotes) return "'".str_replace("\r\n","'+chr(13)+'",str_replace("'",$this->replaceQuote,$s))."'";
+ return "'".$s."'";
+ }
+
+
+ // TOP requires ORDER BY for VFP
+ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
+ {
+ $this->hasTop = preg_match('/ORDER[ \t\r\n]+BY/is',$sql) ? 'top' : false;
+ $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
+ return $ret;
+ }
+
+
+
+};
+
+
+class ADORecordSet_vfp extends ADORecordSet_odbc {
+
+ var $databaseType = "vfp";
+
+
+ function __construct($id,$mode=false)
+ {
+ return parent::__construct($id,$mode);
+ }
+
+ function MetaType($t, $len = -1, $fieldobj = false)
+ {
+ if (is_object($t)) {
+ $fieldobj = $t;
+ $t = $fieldobj->type;
+ $len = $fieldobj->max_length;
+ }
+ switch (strtoupper($t)) {
+ case 'C':
+ if ($len <= $this->blobSize) return 'C';
+ case 'M':
+ return 'X';
+
+ case 'D': return 'D';
+
+ case 'T': return 'T';
+
+ case 'L': return 'L';
+
+ case 'I': return 'I';
+
+ default: return 'N';
+ }
+ }
+}
+
+} //define
diff --git a/vendor/adodb/adodb-php/lang/adodb-ar.inc.php b/vendor/adodb/adodb-php/lang/adodb-ar.inc.php
new file mode 100644
index 0000000..0b8f12f
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-ar.inc.php
@@ -0,0 +1,32 @@
+<?php
+// by "El-Shamaa, Khaled" <k.el-shamaa#cgiar.org>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ar',
+ DB_ERROR => 'خطأ غير محدد',
+ DB_ERROR_ALREADY_EXISTS => 'موجود مسبقا',
+ DB_ERROR_CANNOT_CREATE => 'لا يمكن إنشاء',
+ DB_ERROR_CANNOT_DELETE => 'لا يمكن حذف',
+ DB_ERROR_CANNOT_DROP => 'لا يمكن حذف',
+ DB_ERROR_CONSTRAINT => 'عملية إدخال ممنوعة',
+ DB_ERROR_DIVZERO => 'عملية التقسيم على صفر',
+ DB_ERROR_INVALID => 'غير صحيح',
+ DB_ERROR_INVALID_DATE => 'صيغة وقت أو تاريخ غير صحيحة',
+ DB_ERROR_INVALID_NUMBER => 'صيغة رقم غير صحيحة',
+ DB_ERROR_MISMATCH => 'غير متطابق',
+ DB_ERROR_NODBSELECTED => 'لم يتم إختيار قاعدة البيانات بعد',
+ DB_ERROR_NOSUCHFIELD => 'ليس هنالك حقل بهذا الاسم',
+ DB_ERROR_NOSUCHTABLE => 'ليس هنالك جدول بهذا الاسم',
+ DB_ERROR_NOT_CAPABLE => 'قاعدة البيانات المرتبط بها غير قادرة',
+ DB_ERROR_NOT_FOUND => 'لم يتم إيجاده',
+ DB_ERROR_NOT_LOCKED => 'غير مقفول',
+ DB_ERROR_SYNTAX => 'خطأ في الصيغة',
+ DB_ERROR_UNSUPPORTED => 'غير مدعوم',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'عدد القيم في السجل',
+ DB_ERROR_INVALID_DSN => 'DSN غير صحيح',
+ DB_ERROR_CONNECT_FAILED => 'فشل عملية الإتصال',
+ 0 => 'ليس هنالك أخطاء', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'البيانات المزودة غير كافية',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'لم يتم إيجاد الإضافة المتعلقة',
+ DB_ERROR_NOSUCHDB => 'ليس هنالك قاعدة بيانات بهذا الاسم',
+ DB_ERROR_ACCESS_VIOLATION => 'سماحيات غير كافية'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-bg.inc.php b/vendor/adodb/adodb-php/lang/adodb-bg.inc.php
new file mode 100644
index 0000000..07069b4
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-bg.inc.php
@@ -0,0 +1,36 @@
+<?php
+/*
+ Bulgarian language, v1.0, 25.03.2004, encoding by UTF-8 charset
+ contributed by Valentin Sheiretsky <valio#valio.eu.org>
+*/
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'bg',
+ DB_ERROR => 'неизвестна грешка',
+ DB_ERROR_ALREADY_EXISTS => 'вече съществува',
+ DB_ERROR_CANNOT_CREATE => 'не може да бъде създадена',
+ DB_ERROR_CANNOT_DELETE => 'не може да бъде изтрита',
+ DB_ERROR_CANNOT_DROP => 'не може да бъде унищожена',
+ DB_ERROR_CONSTRAINT => 'нарушено условие',
+ DB_ERROR_DIVZERO => 'деление на нула',
+ DB_ERROR_INVALID => 'неправилно',
+ DB_ERROR_INVALID_DATE => 'некоректна дата или час',
+ DB_ERROR_INVALID_NUMBER => 'невалиден номер',
+ DB_ERROR_MISMATCH => 'погрешна употреба',
+ DB_ERROR_NODBSELECTED => 'не е избрана база данни',
+ DB_ERROR_NOSUCHFIELD => 'несъществуващо поле',
+ DB_ERROR_NOSUCHTABLE => 'несъществуваща таблица',
+ DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
+ DB_ERROR_NOT_FOUND => 'не е намерена',
+ DB_ERROR_NOT_LOCKED => 'не е заключена',
+ DB_ERROR_SYNTAX => 'грешен синтаксис',
+ DB_ERROR_UNSUPPORTED => 'не се поддържа',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'некоректен брой колони в реда',
+ DB_ERROR_INVALID_DSN => 'невалиден DSN',
+ DB_ERROR_CONNECT_FAILED => 'връзката не може да бъде осъществена',
+ 0 => 'няма грешки', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'предоставените данни са недостатъчни',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'разширението не е намерено',
+ DB_ERROR_NOSUCHDB => 'несъществуваща база данни',
+ DB_ERROR_ACCESS_VIOLATION => 'нямате достатъчно права'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-ca.inc.php b/vendor/adodb/adodb-php/lang/adodb-ca.inc.php
new file mode 100644
index 0000000..adbafac
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-ca.inc.php
@@ -0,0 +1,33 @@
+<?php
+// Catalan language
+// contributed by "Josep Lladonosa" jlladono#pie.xtec.es
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ca',
+ DB_ERROR => 'error desconegut',
+ DB_ERROR_ALREADY_EXISTS => 'ja existeix',
+ DB_ERROR_CANNOT_CREATE => 'no es pot crear',
+ DB_ERROR_CANNOT_DELETE => 'no es pot esborrar',
+ DB_ERROR_CANNOT_DROP => 'no es pot eliminar',
+ DB_ERROR_CONSTRAINT => 'violació de constraint',
+ DB_ERROR_DIVZERO => 'divisió per zero',
+ DB_ERROR_INVALID => 'no és vàlid',
+ DB_ERROR_INVALID_DATE => 'la data o l\'hora no són vàlides',
+ DB_ERROR_INVALID_NUMBER => 'el nombre no és vàlid',
+ DB_ERROR_MISMATCH => 'no hi ha coincidència',
+ DB_ERROR_NODBSELECTED => 'cap base de dades seleccionada',
+ DB_ERROR_NOSUCHFIELD => 'camp inexistent',
+ DB_ERROR_NOSUCHTABLE => 'taula inexistent',
+ DB_ERROR_NOT_CAPABLE => 'l\'execució secundària de DB no pot',
+ DB_ERROR_NOT_FOUND => 'no trobat',
+ DB_ERROR_NOT_LOCKED => 'no blocat',
+ DB_ERROR_SYNTAX => 'error de sintaxi',
+ DB_ERROR_UNSUPPORTED => 'no suportat',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'el nombre de columnes no coincideix amb el nombre de valors en la fila',
+ DB_ERROR_INVALID_DSN => 'el DSN no és vàlid',
+ DB_ERROR_CONNECT_FAILED => 'connexió fallida',
+ 0 => 'cap error', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'les dades subministrades són insuficients',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensió no trobada',
+ DB_ERROR_NOSUCHDB => 'base de dades inexistent',
+ DB_ERROR_ACCESS_VIOLATION => 'permisos insuficients'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-cn.inc.php b/vendor/adodb/adodb-php/lang/adodb-cn.inc.php
new file mode 100644
index 0000000..9c97341
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-cn.inc.php
@@ -0,0 +1,33 @@
+<?php
+// Chinese language file contributed by "Cuiyan (cysoft)" cysoft#php.net.
+// Simplified Chinese
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'cn',
+ DB_ERROR => '未知错误',
+ DB_ERROR_ALREADY_EXISTS => '已经存在',
+ DB_ERROR_CANNOT_CREATE => '不能创建',
+ DB_ERROR_CANNOT_DELETE => '不能删除',
+ DB_ERROR_CANNOT_DROP => '不能丢弃',
+ DB_ERROR_CONSTRAINT => '约束限制',
+ DB_ERROR_DIVZERO => '被0除',
+ DB_ERROR_INVALID => '无效',
+ DB_ERROR_INVALID_DATE => '无效的日期或者时间',
+ DB_ERROR_INVALID_NUMBER => '无效的数字',
+ DB_ERROR_MISMATCH => '不匹配',
+ DB_ERROR_NODBSELECTED => '没有数据库被选择',
+ DB_ERROR_NOSUCHFIELD => '没有相应的字段',
+ DB_ERROR_NOSUCHTABLE => '没有相应的表',
+ DB_ERROR_NOT_CAPABLE => '数据库后台不兼容',
+ DB_ERROR_NOT_FOUND => '没有发现',
+ DB_ERROR_NOT_LOCKED => '没有被锁定',
+ DB_ERROR_SYNTAX => '语法错误',
+ DB_ERROR_UNSUPPORTED => '不支持',
+ DB_ERROR_VALUE_COUNT_ON_ROW => '在行上累计值',
+ DB_ERROR_INVALID_DSN => '无效的数据源 (DSN)',
+ DB_ERROR_CONNECT_FAILED => '连接失败',
+ 0 => '没有错误', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => '提供的数据不能符合要求',
+ DB_ERROR_EXTENSION_NOT_FOUND=> '扩展没有被发现',
+ DB_ERROR_NOSUCHDB => '没有相应的数据库',
+ DB_ERROR_ACCESS_VIOLATION => '没有合适的权限'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-cz.inc.php b/vendor/adodb/adodb-php/lang/adodb-cz.inc.php
new file mode 100644
index 0000000..d79d714
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-cz.inc.php
@@ -0,0 +1,35 @@
+<?php
+
+# Czech language
+# v1.0, 19.06.2003 Kamil Jakubovic <jake@host.sk>
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'cz',
+ DB_ERROR => 'neznámá chyba',
+ DB_ERROR_ALREADY_EXISTS => 'ji? existuje',
+ DB_ERROR_CANNOT_CREATE => 'nelze vytvo?it',
+ DB_ERROR_CANNOT_DELETE => 'nelze smazat',
+ DB_ERROR_CANNOT_DROP => 'nelze odstranit',
+ DB_ERROR_CONSTRAINT => 'poru?ení omezující podmínky',
+ DB_ERROR_DIVZERO => 'd?lení nulou',
+ DB_ERROR_INVALID => 'neplatné',
+ DB_ERROR_INVALID_DATE => 'neplatné datum nebo ?as',
+ DB_ERROR_INVALID_NUMBER => 'neplatné ?íslo',
+ DB_ERROR_MISMATCH => 'nesouhlasí',
+ DB_ERROR_NODBSELECTED => '?ádná databáze není vybrána',
+ DB_ERROR_NOSUCHFIELD => 'pole nenalezeno',
+ DB_ERROR_NOSUCHTABLE => 'tabulka nenalezena',
+ DB_ERROR_NOT_CAPABLE => 'nepodporováno',
+ DB_ERROR_NOT_FOUND => 'nenalezeno',
+ DB_ERROR_NOT_LOCKED => 'nezam?eno',
+ DB_ERROR_SYNTAX => 'syntaktická chyba',
+ DB_ERROR_UNSUPPORTED => 'nepodporováno',
+ DB_ERROR_VALUE_COUNT_ON_ROW => '',
+ DB_ERROR_INVALID_DSN => 'neplatné DSN',
+ DB_ERROR_CONNECT_FAILED => 'p?ipojení selhalo',
+ 0 => 'bez chyb', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'málo zdrojových dat',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'roz?í?ení nenalezeno',
+ DB_ERROR_NOSUCHDB => 'databáze neexistuje',
+ DB_ERROR_ACCESS_VIOLATION => 'nedostate?ná práva'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-da.inc.php b/vendor/adodb/adodb-php/lang/adodb-da.inc.php
new file mode 100644
index 0000000..14e720b
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-da.inc.php
@@ -0,0 +1,32 @@
+<?php
+// Arne Eckmann bananstat#users.sourceforge.net
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'da',
+ DB_ERROR => 'ukendt fejl',
+ DB_ERROR_ALREADY_EXISTS => 'eksisterer allerede',
+ DB_ERROR_CANNOT_CREATE => 'kan ikke oprette',
+ DB_ERROR_CANNOT_DELETE => 'kan ikke slette',
+ DB_ERROR_CANNOT_DROP => 'kan ikke droppe',
+ DB_ERROR_CONSTRAINT => 'begrænsning krænket',
+ DB_ERROR_DIVZERO => 'division med nul',
+ DB_ERROR_INVALID => 'ugyldig',
+ DB_ERROR_INVALID_DATE => 'ugyldig dato eller klokkeslet',
+ DB_ERROR_INVALID_NUMBER => 'ugyldigt tal',
+ DB_ERROR_MISMATCH => 'mismatch',
+ DB_ERROR_NODBSELECTED => 'ingen database valgt',
+ DB_ERROR_NOSUCHFIELD => 'felt findes ikke',
+ DB_ERROR_NOSUCHTABLE => 'tabel findes ikke',
+ DB_ERROR_NOT_CAPABLE => 'DB backend opgav',
+ DB_ERROR_NOT_FOUND => 'ikke fundet',
+ DB_ERROR_NOT_LOCKED => 'ikke låst',
+ DB_ERROR_SYNTAX => 'syntaksfejl',
+ DB_ERROR_UNSUPPORTED => 'ikke understøttet',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'resulterende antal felter svarer ikke til forespørgslens antal felter',
+ DB_ERROR_INVALID_DSN => 'ugyldig DSN',
+ DB_ERROR_CONNECT_FAILED => 'tilslutning mislykkedes',
+ 0 => 'ingen fejl', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'utilstrækkelige data angivet',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'udvidelse ikke fundet',
+ DB_ERROR_NOSUCHDB => 'database ikke fundet',
+ DB_ERROR_ACCESS_VIOLATION => 'utilstrækkelige rettigheder'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-de.inc.php b/vendor/adodb/adodb-php/lang/adodb-de.inc.php
new file mode 100644
index 0000000..dca4ffe
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-de.inc.php
@@ -0,0 +1,32 @@
+<?php
+// contributed by "Heinz Hombergs" <opn@hhombergs.de>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'de',
+ DB_ERROR => 'Unbekannter Fehler',
+ DB_ERROR_ALREADY_EXISTS => 'existiert bereits',
+ DB_ERROR_CANNOT_CREATE => 'kann nicht erstellen',
+ DB_ERROR_CANNOT_DELETE => 'kann nicht löschen',
+ DB_ERROR_CANNOT_DROP => 'Tabelle oder Index konnte nicht gelöscht werden',
+ DB_ERROR_CONSTRAINT => 'Constraint Verletzung',
+ DB_ERROR_DIVZERO => 'Division durch Null',
+ DB_ERROR_INVALID => 'ungültig',
+ DB_ERROR_INVALID_DATE => 'ungültiges Datum oder Zeit',
+ DB_ERROR_INVALID_NUMBER => 'ungültige Zahl',
+ DB_ERROR_MISMATCH => 'Unverträglichkeit',
+ DB_ERROR_NODBSELECTED => 'keine Dantebank ausgewählt',
+ DB_ERROR_NOSUCHFIELD => 'Feld nicht vorhanden',
+ DB_ERROR_NOSUCHTABLE => 'Tabelle nicht vorhanden',
+ DB_ERROR_NOT_CAPABLE => 'Funktion nicht installiert',
+ DB_ERROR_NOT_FOUND => 'nicht gefunden',
+ DB_ERROR_NOT_LOCKED => 'nicht gesperrt',
+ DB_ERROR_SYNTAX => 'Syntaxfehler',
+ DB_ERROR_UNSUPPORTED => 'nicht Unterstützt',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'Anzahl der zurückgelieferten Felder entspricht nicht der Anzahl der Felder in der Abfrage',
+ DB_ERROR_INVALID_DSN => 'ungültiger DSN',
+ DB_ERROR_CONNECT_FAILED => 'Verbindung konnte nicht hergestellt werden',
+ 0 => 'kein Fehler', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'Nicht genügend Daten geliefert',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'erweiterung nicht gefunden',
+ DB_ERROR_NOSUCHDB => 'keine Datenbank',
+ DB_ERROR_ACCESS_VIOLATION => 'ungenügende Rechte'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-en.inc.php b/vendor/adodb/adodb-php/lang/adodb-en.inc.php
new file mode 100644
index 0000000..0582855
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-en.inc.php
@@ -0,0 +1,35 @@
+<?php
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'en',
+ DB_ERROR => 'unknown error',
+ DB_ERROR_ALREADY_EXISTS => 'already exists',
+ DB_ERROR_CANNOT_CREATE => 'can not create',
+ DB_ERROR_CANNOT_DELETE => 'can not delete',
+ DB_ERROR_CANNOT_DROP => 'can not drop',
+ DB_ERROR_CONSTRAINT => 'constraint violation',
+ DB_ERROR_DIVZERO => 'division by zero',
+ DB_ERROR_INVALID => 'invalid',
+ DB_ERROR_INVALID_DATE => 'invalid date or time',
+ DB_ERROR_INVALID_NUMBER => 'invalid number',
+ DB_ERROR_MISMATCH => 'mismatch',
+ DB_ERROR_NODBSELECTED => 'no database selected',
+ DB_ERROR_NOSUCHFIELD => 'no such field',
+ DB_ERROR_NOSUCHTABLE => 'no such table',
+ DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
+ DB_ERROR_NOT_FOUND => 'not found',
+ DB_ERROR_NOT_LOCKED => 'not locked',
+ DB_ERROR_SYNTAX => 'syntax error',
+ DB_ERROR_UNSUPPORTED => 'not supported',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+ DB_ERROR_INVALID_DSN => 'invalid DSN',
+ DB_ERROR_CONNECT_FAILED => 'connect failed',
+ 0 => 'no error', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+ DB_ERROR_NOSUCHDB => 'no such database',
+ DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions',
+ DB_ERROR_DEADLOCK => 'deadlock detected',
+ DB_ERROR_STATEMENT_TIMEOUT => 'statement timeout',
+ DB_ERROR_SERIALIZATION_FAILURE => 'could not serialize access'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-eo.inc.php b/vendor/adodb/adodb-php/lang/adodb-eo.inc.php
new file mode 100644
index 0000000..baa589c
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-eo.inc.php
@@ -0,0 +1,34 @@
+<?php
+// Vivu Esperanto ĉiam!
+// Traduko fare de Antono Vasiljev (anders[#]brainactive.org)
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'eo',
+ DB_ERROR => 'nekonata eraro',
+ DB_ERROR_ALREADY_EXISTS => 'jam ekzistas',
+ DB_ERROR_CANNOT_CREATE => 'maleblas krei',
+ DB_ERROR_CANNOT_DELETE => 'maleblas elimini',
+ DB_ERROR_CANNOT_DROP => 'maleblas elimini (drop)',
+ DB_ERROR_CONSTRAINT => 'rompo de kondiĉoj de provo',
+ DB_ERROR_DIVZERO => 'divido per 0 (nul)',
+ DB_ERROR_INVALID => 'malregule',
+ DB_ERROR_INVALID_DATE => 'malregula dato kaj tempo',
+ DB_ERROR_INVALID_NUMBER => 'malregula nombro',
+ DB_ERROR_MISMATCH => 'eraro',
+ DB_ERROR_NODBSELECTED => 'datumbazo ne elektita',
+ DB_ERROR_NOSUCHFIELD => 'ne ekzistas kampo',
+ DB_ERROR_NOSUCHTABLE => 'ne ekzistas tabelo',
+ DB_ERROR_NOT_CAPABLE => 'DBMS ne povas',
+ DB_ERROR_NOT_FOUND => 'ne trovita',
+ DB_ERROR_NOT_LOCKED => 'ne blokita',
+ DB_ERROR_SYNTAX => 'sintaksa eraro',
+ DB_ERROR_UNSUPPORTED => 'ne apogata',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'nombrilo de valoroj en linio',
+ DB_ERROR_INVALID_DSN => 'malregula DSN-o',
+ DB_ERROR_CONNECT_FAILED => 'konekto malsukcesa',
+ 0 => 'ĉio bone', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'ne sufiĉe da datumo',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'etendo ne trovita',
+ DB_ERROR_NOSUCHDB => 'datumbazo ne ekzistas',
+ DB_ERROR_ACCESS_VIOLATION => 'ne sufiĉe da rajto por atingo'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-es.inc.php b/vendor/adodb/adodb-php/lang/adodb-es.inc.php
new file mode 100644
index 0000000..a80a644
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-es.inc.php
@@ -0,0 +1,32 @@
+<?php
+// contributed by "Horacio Degiorgi" <horaciod@codigophp.com>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'es',
+ DB_ERROR => 'error desconocido',
+ DB_ERROR_ALREADY_EXISTS => 'ya existe',
+ DB_ERROR_CANNOT_CREATE => 'imposible crear',
+ DB_ERROR_CANNOT_DELETE => 'imposible borrar',
+ DB_ERROR_CANNOT_DROP => 'imposible hacer drop',
+ DB_ERROR_CONSTRAINT => 'violacion de constraint',
+ DB_ERROR_DIVZERO => 'division por cero',
+ DB_ERROR_INVALID => 'invalido',
+ DB_ERROR_INVALID_DATE => 'fecha u hora invalida',
+ DB_ERROR_INVALID_NUMBER => 'numero invalido',
+ DB_ERROR_MISMATCH => 'error',
+ DB_ERROR_NODBSELECTED => 'no hay base de datos seleccionada',
+ DB_ERROR_NOSUCHFIELD => 'campo invalido',
+ DB_ERROR_NOSUCHTABLE => 'tabla no existe',
+ DB_ERROR_NOT_CAPABLE => 'capacidad invalida para esta DB',
+ DB_ERROR_NOT_FOUND => 'no encontrado',
+ DB_ERROR_NOT_LOCKED => 'no bloqueado',
+ DB_ERROR_SYNTAX => 'error de sintaxis',
+ DB_ERROR_UNSUPPORTED => 'no soportado',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'la cantidad de columnas no corresponden a la cantidad de valores',
+ DB_ERROR_INVALID_DSN => 'DSN invalido',
+ DB_ERROR_CONNECT_FAILED => 'fallo la conexion',
+ 0 => 'sin error', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'insuficientes datos',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension no encontrada',
+ DB_ERROR_NOSUCHDB => 'base de datos no encontrada',
+ DB_ERROR_ACCESS_VIOLATION => 'permisos insuficientes'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-fa.inc.php b/vendor/adodb/adodb-php/lang/adodb-fa.inc.php
new file mode 100644
index 0000000..7fa4618
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-fa.inc.php
@@ -0,0 +1,34 @@
+<?php
+
+/* Farsi - by "Peyman Hooshmandi Raad" <phooshmand#gmail.com> */
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'fa',
+ DB_ERROR => 'خطای ناشناخته',
+ DB_ERROR_ALREADY_EXISTS => 'وجود دارد',
+ DB_ERROR_CANNOT_CREATE => 'امکان create وجود ندارد',
+ DB_ERROR_CANNOT_DELETE => 'امکان حذف وجود ندارد',
+ DB_ERROR_CANNOT_DROP => 'امکان drop وجود ندارد',
+ DB_ERROR_CONSTRAINT => 'نقض شرط',
+ DB_ERROR_DIVZERO => 'تقسیم بر صفر',
+ DB_ERROR_INVALID => 'نامعتبر',
+ DB_ERROR_INVALID_DATE => 'زمان یا تاریخ نامعتبر',
+ DB_ERROR_INVALID_NUMBER => 'عدد نامعتبر',
+ DB_ERROR_MISMATCH => 'عدم مطابقت',
+ DB_ERROR_NODBSELECTED => 'بانک اطلاعاتی انتخاب نشده است',
+ DB_ERROR_NOSUCHFIELD => 'چنین ستونی وجود ندارد',
+ DB_ERROR_NOSUCHTABLE => 'چنین جدولی وجود ندارد',
+ DB_ERROR_NOT_CAPABLE => 'backend بانک اطلاعاتی قادر نیست',
+ DB_ERROR_NOT_FOUND => 'پیدا نشد',
+ DB_ERROR_NOT_LOCKED => 'قفل نشده',
+ DB_ERROR_SYNTAX => 'خطای دستوری',
+ DB_ERROR_UNSUPPORTED => 'پشتیبانی نمی شود',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'شمارش مقادیر روی ردیف',
+ DB_ERROR_INVALID_DSN => 'DSN نامعتبر',
+ DB_ERROR_CONNECT_FAILED => 'ارتباط برقرار نشد',
+ 0 => 'بدون خطا', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'داده ناکافی است',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension پیدا نشد',
+ DB_ERROR_NOSUCHDB => 'چنین بانک اطلاعاتی وجود ندارد',
+ DB_ERROR_ACCESS_VIOLATION => 'حق دسترسی ناکافی'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-fr.inc.php b/vendor/adodb/adodb-php/lang/adodb-fr.inc.php
new file mode 100644
index 0000000..620196b
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-fr.inc.php
@@ -0,0 +1,32 @@
+<?php
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'fr',
+ DB_ERROR => 'erreur inconnue',
+ DB_ERROR_ALREADY_EXISTS => 'existe déjà',
+ DB_ERROR_CANNOT_CREATE => 'création impossible',
+ DB_ERROR_CANNOT_DELETE => 'effacement impossible',
+ DB_ERROR_CANNOT_DROP => 'suppression impossible',
+ DB_ERROR_CONSTRAINT => 'violation de contrainte',
+ DB_ERROR_DIVZERO => 'division par zéro',
+ DB_ERROR_INVALID => 'invalide',
+ DB_ERROR_INVALID_DATE => 'date ou heure invalide',
+ DB_ERROR_INVALID_NUMBER => 'nombre invalide',
+ DB_ERROR_MISMATCH => 'erreur de concordance',
+ DB_ERROR_NODBSELECTED => 'pas de base de données sélectionnée',
+ DB_ERROR_NOSUCHFIELD => 'nom de colonne invalide',
+ DB_ERROR_NOSUCHTABLE => 'table ou vue inexistante',
+ DB_ERROR_NOT_CAPABLE => 'fonction optionnelle non installée',
+ DB_ERROR_NOT_FOUND => 'pas trouvé',
+ DB_ERROR_NOT_LOCKED => 'non verrouillé',
+ DB_ERROR_SYNTAX => 'erreur de syntaxe',
+ DB_ERROR_UNSUPPORTED => 'non supporté',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'valeur insérée trop grande pour colonne',
+ DB_ERROR_INVALID_DSN => 'DSN invalide',
+ DB_ERROR_CONNECT_FAILED => 'échec à la connexion',
+ 0 => "pas d'erreur", // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'données fournies insuffisantes',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension non trouvée',
+ DB_ERROR_NOSUCHDB => 'base de données inconnue',
+ DB_ERROR_ACCESS_VIOLATION => 'droits insuffisants'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-hu.inc.php b/vendor/adodb/adodb-php/lang/adodb-hu.inc.php
new file mode 100644
index 0000000..49357ce
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-hu.inc.php
@@ -0,0 +1,33 @@
+<?php
+# Hungarian language, encoding by ISO 8859-2 charset (Iso Latin-2)
+# Halászvári Gábor <g.halaszvari#portmax.hu>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'hu',
+ DB_ERROR => 'ismeretlen hiba',
+ DB_ERROR_ALREADY_EXISTS => 'már létezik',
+ DB_ERROR_CANNOT_CREATE => 'nem sikerült létrehozni',
+ DB_ERROR_CANNOT_DELETE => 'nem sikerült törölni',
+ DB_ERROR_CANNOT_DROP => 'nem sikerült eldobni',
+ DB_ERROR_CONSTRAINT => 'szabályok megszegése',
+ DB_ERROR_DIVZERO => 'osztás nullával',
+ DB_ERROR_INVALID => 'érvénytelen',
+ DB_ERROR_INVALID_DATE => 'érvénytelen dátum vagy idő',
+ DB_ERROR_INVALID_NUMBER => 'érvénytelen szám',
+ DB_ERROR_MISMATCH => 'nem megfelelő',
+ DB_ERROR_NODBSELECTED => 'nincs kiválasztott adatbázis',
+ DB_ERROR_NOSUCHFIELD => 'nincs ilyen mező',
+ DB_ERROR_NOSUCHTABLE => 'nincs ilyen tábla',
+ DB_ERROR_NOT_CAPABLE => 'DB backend nem támogatja',
+ DB_ERROR_NOT_FOUND => 'nem található',
+ DB_ERROR_NOT_LOCKED => 'nincs lezárva',
+ DB_ERROR_SYNTAX => 'szintaktikai hiba',
+ DB_ERROR_UNSUPPORTED => 'nem támogatott',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'soron végzett érték számlálás',
+ DB_ERROR_INVALID_DSN => 'hibás DSN',
+ DB_ERROR_CONNECT_FAILED => 'sikertelen csatlakozás',
+ 0 => 'nincs hiba', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'túl kevés az adat',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'bővítmény nem található',
+ DB_ERROR_NOSUCHDB => 'nincs ilyen adatbázis',
+ DB_ERROR_ACCESS_VIOLATION => 'nincs jogosultság'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-it.inc.php b/vendor/adodb/adodb-php/lang/adodb-it.inc.php
new file mode 100644
index 0000000..80524e1
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-it.inc.php
@@ -0,0 +1,33 @@
+<?php
+// Italian language file contributed by Tiraboschi Massimiliano aka TiMax
+// www.maxdev.com timax@maxdev.com
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'it',
+ DB_ERROR => 'errore sconosciuto',
+ DB_ERROR_ALREADY_EXISTS => 'esiste già',
+ DB_ERROR_CANNOT_CREATE => 'non posso creare',
+ DB_ERROR_CANNOT_DELETE => 'non posso cancellare',
+ DB_ERROR_CANNOT_DROP => 'non posso eliminare',
+ DB_ERROR_CONSTRAINT => 'violazione constraint',
+ DB_ERROR_DIVZERO => 'divisione per zero',
+ DB_ERROR_INVALID => 'non valido',
+ DB_ERROR_INVALID_DATE => 'data od ora non valida',
+ DB_ERROR_INVALID_NUMBER => 'numero non valido',
+ DB_ERROR_MISMATCH => 'diversi',
+ DB_ERROR_NODBSELECTED => 'nessun database selezionato',
+ DB_ERROR_NOSUCHFIELD => 'nessun campo trovato',
+ DB_ERROR_NOSUCHTABLE => 'nessuna tabella trovata',
+ DB_ERROR_NOT_CAPABLE => 'DB backend non abilitato',
+ DB_ERROR_NOT_FOUND => 'non trovato',
+ DB_ERROR_NOT_LOCKED => 'non bloccato',
+ DB_ERROR_SYNTAX => 'errore di sintassi',
+ DB_ERROR_UNSUPPORTED => 'non supportato',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'valore inserito troppo grande per una colonna',
+ DB_ERROR_INVALID_DSN => 'DSN non valido',
+ DB_ERROR_CONNECT_FAILED => 'connessione fallita',
+ 0 => 'nessun errore', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'dati inseriti insufficienti',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'estensione non trovata',
+ DB_ERROR_NOSUCHDB => 'database non trovato',
+ DB_ERROR_ACCESS_VIOLATION => 'permessi insufficienti'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-nl.inc.php b/vendor/adodb/adodb-php/lang/adodb-nl.inc.php
new file mode 100644
index 0000000..43e3ee6
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-nl.inc.php
@@ -0,0 +1,32 @@
+<?php
+// Translated by Pim Koeman (pim#wittenborg-university.com)
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'nl',
+ DB_ERROR => 'onbekende fout',
+ DB_ERROR_ALREADY_EXISTS => 'bestaat al',
+ DB_ERROR_CANNOT_CREATE => 'kan niet aanmaken',
+ DB_ERROR_CANNOT_DELETE => 'kan niet wissen',
+ DB_ERROR_CANNOT_DROP => 'kan niet verwijderen',
+ DB_ERROR_CONSTRAINT => 'constraint overtreding',
+ DB_ERROR_DIVZERO => 'poging tot delen door nul',
+ DB_ERROR_INVALID => 'ongeldig',
+ DB_ERROR_INVALID_DATE => 'ongeldige datum of tijd',
+ DB_ERROR_INVALID_NUMBER => 'ongeldig nummer',
+ DB_ERROR_MISMATCH => 'is incorrect',
+ DB_ERROR_NODBSELECTED => 'geen database geselecteerd',
+ DB_ERROR_NOSUCHFIELD => 'onbekend veld',
+ DB_ERROR_NOSUCHTABLE => 'onbekende tabel',
+ DB_ERROR_NOT_CAPABLE => 'database systeem is niet tot uitvoer in staat',
+ DB_ERROR_NOT_FOUND => 'niet gevonden',
+ DB_ERROR_NOT_LOCKED => 'niet vergrendeld',
+ DB_ERROR_SYNTAX => 'syntaxis fout',
+ DB_ERROR_UNSUPPORTED => 'niet ondersteund',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'waarde telling op rij',
+ DB_ERROR_INVALID_DSN => 'ongeldige DSN',
+ DB_ERROR_CONNECT_FAILED => 'connectie mislukt',
+ 0 => 'geen fout', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'onvoldoende data gegeven',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensie niet gevonden',
+ DB_ERROR_NOSUCHDB => 'onbekende database',
+ DB_ERROR_ACCESS_VIOLATION => 'onvoldoende rechten'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-pl.inc.php b/vendor/adodb/adodb-php/lang/adodb-pl.inc.php
new file mode 100644
index 0000000..ffa10e3
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-pl.inc.php
@@ -0,0 +1,34 @@
+<?php
+
+// Contributed by Grzegorz Pacan <gp#dione.cc>
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'pl',
+ DB_ERROR => 'niezidentyfikowany błąd',
+ DB_ERROR_ALREADY_EXISTS => 'już istnieją',
+ DB_ERROR_CANNOT_CREATE => 'nie można stworzyć',
+ DB_ERROR_CANNOT_DELETE => 'nie można usunąć',
+ DB_ERROR_CANNOT_DROP => 'nie można porzucić',
+ DB_ERROR_CONSTRAINT => 'pogwałcenie uprawnień',
+ DB_ERROR_DIVZERO => 'dzielenie przez zero',
+ DB_ERROR_INVALID => 'błędny',
+ DB_ERROR_INVALID_DATE => 'błędna godzina lub data',
+ DB_ERROR_INVALID_NUMBER => 'błędny numer',
+ DB_ERROR_MISMATCH => 'niedopasowanie',
+ DB_ERROR_NODBSELECTED => 'baza danych nie została wybrana',
+ DB_ERROR_NOSUCHFIELD => 'nie znaleziono pola',
+ DB_ERROR_NOSUCHTABLE => 'nie znaleziono tabeli',
+ DB_ERROR_NOT_CAPABLE => 'nie zdolny',
+ DB_ERROR_NOT_FOUND => 'nie znaleziono',
+ DB_ERROR_NOT_LOCKED => 'nie zakmnięty',
+ DB_ERROR_SYNTAX => 'błąd składni',
+ DB_ERROR_UNSUPPORTED => 'nie obsługuje',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'wartość liczona w szeregu',
+ DB_ERROR_INVALID_DSN => 'błędny DSN',
+ DB_ERROR_CONNECT_FAILED => 'połączenie nie zostało zrealizowane',
+ 0 => 'brak błędów', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'niedostateczna ilość informacji',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'nie znaleziono rozszerzenia',
+ DB_ERROR_NOSUCHDB => 'nie znaleziono bazy',
+ DB_ERROR_ACCESS_VIOLATION => 'niedostateczne uprawnienia'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-pt-br.inc.php b/vendor/adodb/adodb-php/lang/adodb-pt-br.inc.php
new file mode 100644
index 0000000..9c687b0
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-pt-br.inc.php
@@ -0,0 +1,34 @@
+<?php
+// contributed by "Levi Fukumori" levi _AT_ fukumori _DOT_ com _DOT_ br
+// portugese (brazilian)
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'pt-br',
+ DB_ERROR => 'erro desconhecido',
+ DB_ERROR_ALREADY_EXISTS => 'já existe',
+ DB_ERROR_CANNOT_CREATE => 'impossível criar',
+ DB_ERROR_CANNOT_DELETE => 'impossível excluír',
+ DB_ERROR_CANNOT_DROP => 'impossível remover',
+ DB_ERROR_CONSTRAINT => 'violação do confinamente',
+ DB_ERROR_DIVZERO => 'divisão por zero',
+ DB_ERROR_INVALID => 'inválido',
+ DB_ERROR_INVALID_DATE => 'data ou hora inválida',
+ DB_ERROR_INVALID_NUMBER => 'número inválido',
+ DB_ERROR_MISMATCH => 'erro',
+ DB_ERROR_NODBSELECTED => 'nenhum banco de dados selecionado',
+ DB_ERROR_NOSUCHFIELD => 'campo inválido',
+ DB_ERROR_NOSUCHTABLE => 'tabela inexistente',
+ DB_ERROR_NOT_CAPABLE => 'capacidade inválida para este BD',
+ DB_ERROR_NOT_FOUND => 'não encontrado',
+ DB_ERROR_NOT_LOCKED => 'não bloqueado',
+ DB_ERROR_SYNTAX => 'erro de sintaxe',
+ DB_ERROR_UNSUPPORTED =>
+'não suportado',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'a quantidade de colunas não corresponde ao de valores',
+ DB_ERROR_INVALID_DSN => 'DSN inválido',
+ DB_ERROR_CONNECT_FAILED => 'falha na conexão',
+ 0 => 'sem erro', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'dados insuficientes',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensão não encontrada',
+ DB_ERROR_NOSUCHDB => 'banco de dados não encontrado',
+ DB_ERROR_ACCESS_VIOLATION => 'permissão insuficiente'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-ro.inc.php b/vendor/adodb/adodb-php/lang/adodb-ro.inc.php
new file mode 100644
index 0000000..b6ddd31
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-ro.inc.php
@@ -0,0 +1,34 @@
+<?php
+
+/* Romanian - by "bogdan stefan" <sbogdan#rsb.ro> */
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ro',
+ DB_ERROR => 'eroare necunoscuta',
+ DB_ERROR_ALREADY_EXISTS => 'deja exista',
+ DB_ERROR_CANNOT_CREATE => 'nu se poate creea',
+ DB_ERROR_CANNOT_DELETE => 'nu se poate sterge',
+ DB_ERROR_CANNOT_DROP => 'nu se poate executa drop',
+ DB_ERROR_CONSTRAINT => 'violare de constrain',
+ DB_ERROR_DIVZERO => 'se divide la zero',
+ DB_ERROR_INVALID => 'invalid',
+ DB_ERROR_INVALID_DATE => 'data sau timp invalide',
+ DB_ERROR_INVALID_NUMBER => 'numar invalid',
+ DB_ERROR_MISMATCH => 'nepotrivire-mismatch',
+ DB_ERROR_NODBSELECTED => 'nu exista baza de date selectata',
+ DB_ERROR_NOSUCHFIELD => 'camp inexistent',
+ DB_ERROR_NOSUCHTABLE => 'tabela inexistenta',
+ DB_ERROR_NOT_CAPABLE => 'functie optionala neinstalata',
+ DB_ERROR_NOT_FOUND => 'negasit',
+ DB_ERROR_NOT_LOCKED => 'neblocat',
+ DB_ERROR_SYNTAX => 'eroare de sintaxa',
+ DB_ERROR_UNSUPPORTED => 'nu e suportat',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'valoare prea mare pentru coloana',
+ DB_ERROR_INVALID_DSN => 'DSN invalid',
+ DB_ERROR_CONNECT_FAILED => 'conectare esuata',
+ 0 => 'fara eroare', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'data introduse insuficiente',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensie negasita',
+ DB_ERROR_NOSUCHDB => 'nu exista baza de date',
+ DB_ERROR_ACCESS_VIOLATION => 'permisiuni insuficiente'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-ru.inc.php b/vendor/adodb/adodb-php/lang/adodb-ru.inc.php
new file mode 100644
index 0000000..67d80f2
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-ru.inc.php
@@ -0,0 +1,34 @@
+<?php
+
+// Russian language file contributed by "Cyrill Malevanov" cyrill#malevanov.spb.ru.
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ru',
+ DB_ERROR => 'неизвестная ошибка',
+ DB_ERROR_ALREADY_EXISTS => 'уже существует',
+ DB_ERROR_CANNOT_CREATE => 'невозможно создать',
+ DB_ERROR_CANNOT_DELETE => 'невозможно удалить',
+ DB_ERROR_CANNOT_DROP => 'невозможно удалить (drop)',
+ DB_ERROR_CONSTRAINT => 'нарушение условий проверки',
+ DB_ERROR_DIVZERO => 'деление на 0',
+ DB_ERROR_INVALID => 'неправильно',
+ DB_ERROR_INVALID_DATE => 'некорректная дата или время',
+ DB_ERROR_INVALID_NUMBER => 'некорректное число',
+ DB_ERROR_MISMATCH => 'ошибка',
+ DB_ERROR_NODBSELECTED => 'БД не выбрана',
+ DB_ERROR_NOSUCHFIELD => 'не существует поле',
+ DB_ERROR_NOSUCHTABLE => 'не существует таблица',
+ DB_ERROR_NOT_CAPABLE => 'СУБД не в состоянии',
+ DB_ERROR_NOT_FOUND => 'не найдено',
+ DB_ERROR_NOT_LOCKED => 'не заблокировано',
+ DB_ERROR_SYNTAX => 'синтаксическая ошибка',
+ DB_ERROR_UNSUPPORTED => 'не поддерживается',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'счетчик значений в строке',
+ DB_ERROR_INVALID_DSN => 'неправильная DSN',
+ DB_ERROR_CONNECT_FAILED => 'соединение неуспешно',
+ 0 => 'нет ошибки', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'предоставлено недостаточно данных',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'расширение не найдено',
+ DB_ERROR_NOSUCHDB => 'не существует БД',
+ DB_ERROR_ACCESS_VIOLATION => 'недостаточно прав доступа'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-sv.inc.php b/vendor/adodb/adodb-php/lang/adodb-sv.inc.php
new file mode 100644
index 0000000..d3be6b0
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-sv.inc.php
@@ -0,0 +1,32 @@
+<?php
+// Christian Tiberg" christian@commsoft.nu
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'en',
+ DB_ERROR => 'Okänt fel',
+ DB_ERROR_ALREADY_EXISTS => 'finns redan',
+ DB_ERROR_CANNOT_CREATE => 'kan inte skapa',
+ DB_ERROR_CANNOT_DELETE => 'kan inte ta bort',
+ DB_ERROR_CANNOT_DROP => 'kan inte släppa',
+ DB_ERROR_CONSTRAINT => 'begränsning kränkt',
+ DB_ERROR_DIVZERO => 'division med noll',
+ DB_ERROR_INVALID => 'ogiltig',
+ DB_ERROR_INVALID_DATE => 'ogiltigt datum eller tid',
+ DB_ERROR_INVALID_NUMBER => 'ogiltigt tal',
+ DB_ERROR_MISMATCH => 'felaktig matchning',
+ DB_ERROR_NODBSELECTED => 'ingen databas vald',
+ DB_ERROR_NOSUCHFIELD => 'inget sådant fält',
+ DB_ERROR_NOSUCHTABLE => 'ingen sådan tabell',
+ DB_ERROR_NOT_CAPABLE => 'DB backend klarar det inte',
+ DB_ERROR_NOT_FOUND => 'finns inte',
+ DB_ERROR_NOT_LOCKED => 'inte låst',
+ DB_ERROR_SYNTAX => 'syntaxfel',
+ DB_ERROR_UNSUPPORTED => 'stöds ej',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'värde räknat på rad',
+ DB_ERROR_INVALID_DSN => 'ogiltig DSN',
+ DB_ERROR_CONNECT_FAILED => 'anslutning misslyckades',
+ 0 => 'inget fel', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'otillräckligt med data angivet',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'utökning hittades ej',
+ DB_ERROR_NOSUCHDB => 'ingen sådan databas',
+ DB_ERROR_ACCESS_VIOLATION => 'otillräckliga rättigheter'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-th.inc.php b/vendor/adodb/adodb-php/lang/adodb-th.inc.php
new file mode 100644
index 0000000..a068564
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-th.inc.php
@@ -0,0 +1,32 @@
+<?php
+// by Trirat Petchsingh <rosskouk#gmail.com>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'th',
+ DB_ERROR => 'error ไม่รู้สาเหตุ',
+ DB_ERROR_ALREADY_EXISTS => 'มี�?ล้ว',
+ DB_ERROR_CANNOT_CREATE => 'สร้างไม่ได้',
+ DB_ERROR_CANNOT_DELETE => 'ลบไม่ได้',
+ DB_ERROR_CANNOT_DROP => 'drop ไม่ได้',
+ DB_ERROR_CONSTRAINT => 'constraint violation',
+ DB_ERROR_DIVZERO => 'หา�?ด้วยสู�?',
+ DB_ERROR_INVALID => 'ไม่ valid',
+ DB_ERROR_INVALID_DATE => 'วันที่ เวลา ไม่ valid',
+ DB_ERROR_INVALID_NUMBER => 'เลขไม่ valid',
+ DB_ERROR_MISMATCH => 'mismatch',
+ DB_ERROR_NODBSELECTED => 'ไม่ได้เลือ�?�?านข้อมูล',
+ DB_ERROR_NOSUCHFIELD => 'ไม่มีฟีลด์นี้',
+ DB_ERROR_NOSUCHTABLE => 'ไม่มีตารางนี้',
+ DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
+ DB_ERROR_NOT_FOUND => 'ไม่พบ',
+ DB_ERROR_NOT_LOCKED => 'ไม่ได้ล๊อ�?',
+ DB_ERROR_SYNTAX => 'ผิด syntax',
+ DB_ERROR_UNSUPPORTED => 'ไม่ support',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+ DB_ERROR_INVALID_DSN => 'invalid DSN',
+ DB_ERROR_CONNECT_FAILED => 'ไม่สามารถ connect',
+ 0 => 'no error',
+ DB_ERROR_NEED_MORE_DATA => 'ข้อมูลไม่เพียงพอ',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'ไม่พบ extension',
+ DB_ERROR_NOSUCHDB => 'ไม่มีข้อมูลนี้',
+ DB_ERROR_ACCESS_VIOLATION => 'permissions ไม่พอ'
+);
diff --git a/vendor/adodb/adodb-php/lang/adodb-uk.inc.php b/vendor/adodb/adodb-php/lang/adodb-uk.inc.php
new file mode 100644
index 0000000..2ace5bc
--- /dev/null
+++ b/vendor/adodb/adodb-php/lang/adodb-uk.inc.php
@@ -0,0 +1,34 @@
+<?php
+
+// Ukrainian language file contributed by Alex Rootoff rootoff{AT}pisem.net.
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'uk',
+ DB_ERROR => 'невідома помилка',
+ DB_ERROR_ALREADY_EXISTS => 'вже існує',
+ DB_ERROR_CANNOT_CREATE => 'неможливо створити',
+ DB_ERROR_CANNOT_DELETE => 'неможливо видалити',
+ DB_ERROR_CANNOT_DROP => 'неможливо знищити (drop)',
+ DB_ERROR_CONSTRAINT => 'порушення умов перевірки',
+ DB_ERROR_DIVZERO => 'ділення на 0',
+ DB_ERROR_INVALID => 'неправильно',
+ DB_ERROR_INVALID_DATE => 'неправильна дата чи час',
+ DB_ERROR_INVALID_NUMBER => 'неправильне число',
+ DB_ERROR_MISMATCH => 'помилка',
+ DB_ERROR_NODBSELECTED => 'не вибрано БД',
+ DB_ERROR_NOSUCHFIELD => 'не існує поле',
+ DB_ERROR_NOSUCHTABLE => 'не існує таблиця',
+ DB_ERROR_NOT_CAPABLE => 'СУБД не в стані',
+ DB_ERROR_NOT_FOUND => 'не знайдено',
+ DB_ERROR_NOT_LOCKED => 'не заблоковано',
+ DB_ERROR_SYNTAX => 'синтаксична помилка',
+ DB_ERROR_UNSUPPORTED => 'не підтримується',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'рахівник значень в стрічці',
+ DB_ERROR_INVALID_DSN => 'неправильна DSN',
+ DB_ERROR_CONNECT_FAILED => 'з\'єднання неуспішне',
+ 0 => 'все гаразд', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'надано недостатньо даних',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'розширення не знайдено',
+ DB_ERROR_NOSUCHDB => 'не існує БД',
+ DB_ERROR_ACCESS_VIOLATION => 'недостатньо прав доступа'
+);
diff --git a/vendor/adodb/adodb-php/pear/Auth/Container/ADOdb.php b/vendor/adodb/adodb-php/pear/Auth/Container/ADOdb.php
new file mode 100644
index 0000000..f500e25
--- /dev/null
+++ b/vendor/adodb/adodb-php/pear/Auth/Container/ADOdb.php
@@ -0,0 +1,406 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Original Authors: Martin Jansen <mj#php.net>
+ Richard Tango-Lowy <richtl#arscognita.com>
+*/
+
+require_once 'Auth/Container.php';
+require_once 'adodb.inc.php';
+require_once 'adodb-pear.inc.php';
+require_once 'adodb-errorpear.inc.php';
+
+/**
+ * Storage driver for fetching login data from a database using ADOdb-PHP.
+ *
+ * This storage driver can use all databases which are supported
+ * by the ADBdb DB abstraction layer to fetch login data.
+ * See http://adodb.org/ for information on ADOdb.
+ * NOTE: The ADOdb directory MUST be in your PHP include_path!
+ *
+ * @author Richard Tango-Lowy <richtl@arscognita.com>
+ * @package Auth
+ * @version $Revision: 1.3 $
+ */
+class Auth_Container_ADOdb extends Auth_Container
+{
+
+ /**
+ * Additional options for the storage container
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * DB object
+ * @var object
+ */
+ var $db = null;
+ var $dsn = '';
+
+ /**
+ * User that is currently selected from the DB.
+ * @var string
+ */
+ var $activeUser = '';
+
+ // {{{ Constructor
+
+ /**
+ * Constructor of the container class
+ *
+ * Initate connection to the database via PEAR::ADOdb
+ *
+ * @param string Connection data or DB object
+ * @return object Returns an error object if something went wrong
+ */
+ function __construct($dsn)
+ {
+ $this->_setDefaults();
+
+ if (is_array($dsn)) {
+ $this->_parseOptions($dsn);
+
+ if (empty($this->options['dsn'])) {
+ PEAR::raiseError('No connection parameters specified!');
+ }
+ } else {
+ // Extract db_type from dsn string.
+ $this->options['dsn'] = $dsn;
+ }
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to database by using the given DSN string
+ *
+ * @access private
+ * @param string DSN string
+ * @return mixed Object on error, otherwise bool
+ */
+ function _connect($dsn)
+ {
+ if (is_string($dsn) || is_array($dsn)) {
+ if(!$this->db) {
+ $this->db = ADONewConnection($dsn);
+ if( $err = ADODB_Pear_error() ) {
+ return PEAR::raiseError($err);
+ }
+ }
+
+ } else {
+ return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
+ 41,
+ PEAR_ERROR_RETURN,
+ null,
+ null
+ );
+ }
+
+ if(!$this->db) {
+ return PEAR::raiseError(ADODB_Pear_error());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare database connection
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a DB error object.
+ */
+ function _prepare()
+ {
+ if(!$this->db) {
+ $res = $this->_connect($this->options['dsn']);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ query()
+
+ /**
+ * Prepare query to the database
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ * After that the query is passed to the database.
+ *
+ * @access public
+ * @param string Query string
+ * @return mixed a DB_result object or DB_OK on success, a DB
+ * or PEAR error on failure
+ */
+ function query($query)
+ {
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return $err;
+ }
+ return $this->db->query($query);
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->options['db_type'] = 'mysql';
+ $this->options['table'] = 'auth';
+ $this->options['usernamecol'] = 'username';
+ $this->options['passwordcol'] = 'password';
+ $this->options['dsn'] = '';
+ $this->options['db_fields'] = '';
+ $this->options['cryptType'] = 'md5';
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+
+ /* Include additional fields if they exist */
+ if(!empty($this->options['db_fields'])){
+ if(is_array($this->options['db_fields'])){
+ $this->options['db_fields'] = join($this->options['db_fields'], ', ');
+ }
+ $this->options['db_fields'] = ', '.$this->options['db_fields'];
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from database
+ *
+ * This function uses the given username to fetch
+ * the corresponding login data from the database
+ * table. If an account that matches the passed username
+ * and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password)
+ {
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // Find if db_fields contains a *, i so assume all col are selected
+ if(strstr($this->options['db_fields'], '*')){
+ $sql_from = "*";
+ }
+ else{
+ $sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields'];
+ }
+
+ $query = "SELECT ".$sql_from.
+ " FROM ".$this->options['table'].
+ " WHERE ".$this->options['usernamecol']." = " . $this->db->Quote($username);
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rset = $this->db->Execute( $query );
+ $res = $rset->fetchRow();
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ }
+ if (!is_array($res)) {
+ $this->activeUser = '';
+ return false;
+ }
+ if ($this->verifyPassword(trim($password, "\r\n"),
+ trim($res[$this->options['passwordcol']], "\r\n"),
+ $this->options['cryptType'])) {
+ // Store additional field values in the session
+ foreach ($res as $key => $value) {
+ if ($key == $this->options['passwordcol'] ||
+ $key == $this->options['usernamecol']) {
+ continue;
+ }
+ // Use reference to the auth object if exists
+ // This is because the auth session variable can change so a static call to setAuthData does not make sence
+ if(is_object($this->_auth_obj)){
+ $this->_auth_obj->setAuthData($key, $value);
+ } else {
+ Auth::setAuthData($key, $value);
+ }
+ }
+
+ return true;
+ }
+
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return false;
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ function listUsers()
+ {
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $retVal = array();
+
+ // Find if db_fileds contains a *, i so assume all col are selected
+ if(strstr($this->options['db_fields'], '*')){
+ $sql_from = "*";
+ }
+ else{
+ $sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields'];
+ }
+
+ $query = sprintf("SELECT %s FROM %s",
+ $sql_from,
+ $this->options['table']
+ );
+ $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ foreach ($res as $user) {
+ $user['username'] = $user[$this->options['usernamecol']];
+ $retVal[] = $user;
+ }
+ }
+ return $retVal;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add user to the storage container
+ *
+ * @access public
+ * @param string Username
+ * @param string Password
+ * @param mixed Additional information that are stored in the DB
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function addUser($username, $password, $additional = "")
+ {
+ if (function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $additional_key = '';
+ $additional_value = '';
+
+ if (is_array($additional)) {
+ foreach ($additional as $key => $value) {
+ $additional_key .= ', ' . $key;
+ $additional_value .= ", '" . $value . "'";
+ }
+ }
+
+ $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES ('%s', '%s'%s)",
+ $this->options['table'],
+ $this->options['usernamecol'],
+ $this->options['passwordcol'],
+ $additional_key,
+ $username,
+ $cryptFunction($password),
+ $additional_value
+ );
+
+ $res = $this->query($query);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @access public
+ * @param string Username
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function removeUser($username)
+ {
+ $query = sprintf("DELETE FROM %s WHERE %s = '%s'",
+ $this->options['table'],
+ $this->options['usernamecol'],
+ $username
+ );
+
+ $res = $this->query($query);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+}
+
+function showDbg( $string ) {
+ print "
+-- $string</P>";
+}
+function dump( $var, $str, $vardump = false ) {
+ print "<H4>$str</H4><pre>";
+ ( !$vardump ) ? ( print_r( $var )) : ( var_dump( $var ));
+ print "</pre>";
+}
diff --git a/vendor/adodb/adodb-php/pear/auth_adodb_example.php b/vendor/adodb/adodb-php/pear/auth_adodb_example.php
new file mode 100644
index 0000000..3b7cf5e
--- /dev/null
+++ b/vendor/adodb/adodb-php/pear/auth_adodb_example.php
@@ -0,0 +1,25 @@
+<?php
+// NOTE: The ADOdb and PEAR directories MUST be in your PHP include_path!
+require_once "Auth/Auth.php";
+
+function loginFunction() {
+?>
+ <form method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
+ <input type="text" name="username">
+ <input type="password" name="password">
+ <input type="submit">
+ </form>
+<?php
+}
+
+$dsn = 'mysql://username:password@hostname/database';
+// To use encrypted passwords, change cryptType to 'md5'
+$params = array('dsn' => $dsn, 'table' => 'auth', 'cryptType' => 'none',
+ 'usernamecol' => 'username', 'passwordcol' => 'password');
+$a = new Auth("ADOdb", $params, "loginFunction");
+$a->start();
+
+if ($a->getAuth()) {
+ echo "Success";
+ // * The output of your site goes here.
+}
diff --git a/vendor/adodb/adodb-php/pear/readme.Auth.txt b/vendor/adodb/adodb-php/pear/readme.Auth.txt
new file mode 100644
index 0000000..f5b162c
--- /dev/null
+++ b/vendor/adodb/adodb-php/pear/readme.Auth.txt
@@ -0,0 +1,20 @@
+From: Rich Tango-Lowy (richtl#arscognita.com)
+Date: Sat, May 29, 2004 11:20 am
+
+OK, I hacked out an ADOdb container for PEAR-Auth. The error handling's
+a bit of a mess, but all the methods work.
+
+Copy ADOdb.php to your pear/Auth/Container/ directory.
+
+Use the ADOdb container exactly as you would the DB
+container, but specify 'ADOdb' instead of 'DB':
+
+$dsn = "mysql://myuser:mypass@localhost/authdb";
+$a = new Auth("ADOdb", $dsn, "loginFunction");
+
+
+-------------------
+
+John Lim adds:
+
+See http://pear.php.net/manual/en/package.authentication.php
diff --git a/vendor/adodb/adodb-php/perf/perf-db2.inc.php b/vendor/adodb/adodb-php/perf/perf-db2.inc.php
new file mode 100644
index 0000000..b0d5c7a
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-db2.inc.php
@@ -0,0 +1,108 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+// Simple guide to configuring db2: so-so http://www.devx.com/gethelpon/10MinuteSolution/16575
+
+// SELECT * FROM TABLE(SNAPSHOT_APPL('SAMPLE', -1)) as t
+class perf_db2 extends adodb_perf{
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created TIMESTAMP NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 varchar(4000) NOT NULL,
+ params varchar(3000) NOT NULL,
+ tracer varchar(500) NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'data cache hit ratio' => array('RATIO',
+ "SELECT
+ case when sum(POOL_DATA_L_READS+POOL_INDEX_L_READS)=0 then 0
+ else 100*(1-sum(POOL_DATA_P_READS+POOL_INDEX_P_READS)/sum(POOL_DATA_L_READS+POOL_INDEX_L_READS)) end
+ FROM TABLE(SNAPSHOT_APPL('',-2)) as t",
+ '=WarnCacheRatio'),
+
+ 'Data Cache',
+ 'data cache buffers' => array('DATAC',
+ 'select sum(npages) from SYSCAT.BUFFERPOOLS',
+ 'See <a href=http://www7b.boulder.ibm.com/dmdd/library/techarticle/anshum/0107anshum.html#bufferpoolsize>tuning reference</a>.' ),
+ 'cache blocksize' => array('DATAC',
+ 'select avg(pagesize) from SYSCAT.BUFFERPOOLS',
+ '' ),
+ 'data cache size' => array('DATAC',
+ 'select sum(npages*pagesize) from SYSCAT.BUFFERPOOLS',
+ '' ),
+ 'Connections',
+ 'current connections' => array('SESS',
+ "SELECT count(*) FROM TABLE(SNAPSHOT_APPL_INFO('',-2)) as t",
+ ''),
+
+ false
+ );
+
+
+ function __construct(&$conn)
+ {
+ $this->conn = $conn;
+ }
+
+ function Explain($sql,$partial=false)
+ {
+ $save = $this->conn->LogSQL(false);
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+ $qno = rand();
+ $ok = $this->conn->Execute("EXPLAIN PLAN SET QUERYNO=$qno FOR $sql");
+ ob_start();
+ if (!$ok) echo "<p>Have EXPLAIN tables been created?</p>";
+ else {
+ $rs = $this->conn->Execute("select * from explain_statement where queryno=$qno");
+ if ($rs) rs2html($rs);
+ }
+ $s = ob_get_contents();
+ ob_end_clean();
+ $this->conn->LogSQL($save);
+
+ $s .= $this->Tracer($sql);
+ return $s;
+ }
+
+ /**
+ * Gets a list of tables
+ *
+ * @param int $throwaway discarded variable to match the parent method
+ * @return string The formatted table list
+ */
+ function Tables($throwaway=0)
+ {
+ $rs = $this->conn->Execute("select tabschema,tabname,card as rows,
+ npages pages_used,fpages pages_allocated, tbspace tablespace
+ from syscat.tables where tabschema not in ('SYSCAT','SYSIBM','SYSSTAT') order by 1,2");
+ return rs2html($rs,false,false,false,false);
+ }
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-informix.inc.php b/vendor/adodb/adodb-php/perf/perf-informix.inc.php
new file mode 100644
index 0000000..50eab39
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-informix.inc.php
@@ -0,0 +1,71 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+//
+// Thx to Fernando Ortiz, mailto:fortiz#lacorona.com.mx
+// With info taken from http://www.oninit.com/oninit/sysmaster/index.html
+//
+class perf_informix extends adodb_perf{
+
+ // Maximum size on varchar upto 9.30 255 chars
+ // better truncate varchar to 255 than char(4000) ?
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created datetime year to second NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 varchar(255) NOT NULL,
+ params varchar(255) NOT NULL,
+ tracer varchar(255) NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $tablesSQL = "select a.tabname tablename, ti_nptotal*2 size_in_k, ti_nextns extents, ti_nrows records from systables c, sysmaster:systabnames a, sysmaster:systabinfo b where c.tabname not matches 'sys*' and c.partnum = a.partnum and c.partnum = b.ti_partnum";
+
+ var $settings = array(
+ 'Ratios',
+ 'data cache hit ratio' => array('RATIOH',
+ "select round((1-(wt.value / (rd.value + wr.value)))*100,2)
+ from sysmaster:sysprofile wr, sysmaster:sysprofile rd, sysmaster:sysprofile wt
+ where rd.name = 'pagreads' and
+ wr.name = 'pagwrites' and
+ wt.name = 'buffwts'",
+ '=WarnCacheRatio'),
+ 'IO',
+ 'data reads' => array('IO',
+ "select value from sysmaster:sysprofile where name='pagreads'",
+ 'Page reads'),
+
+ 'data writes' => array('IO',
+ "select value from sysmaster:sysprofile where name='pagwrites'",
+ 'Page writes'),
+
+ 'Connections',
+ 'current connections' => array('SESS',
+ 'select count(*) from sysmaster:syssessions',
+ 'Number of sessions'),
+
+ false
+
+ );
+
+ function __construct(&$conn)
+ {
+ $this->conn = $conn;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-mssql.inc.php b/vendor/adodb/adodb-php/perf/perf-mssql.inc.php
new file mode 100644
index 0000000..c9b2fff
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-mssql.inc.php
@@ -0,0 +1,164 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+ MSSQL has moved most performance info to Performance Monitor
+*/
+class perf_mssql extends adodb_perf{
+ var $sql1 = 'cast(sql1 as text)';
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created datetime NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 varchar(4000) NOT NULL,
+ params varchar(3000) NOT NULL,
+ tracer varchar(500) NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'data cache hit ratio' => array('RATIO',
+ "select round((a.cntr_value*100.0)/b.cntr_value,2) from master.dbo.sysperfinfo a, master.dbo.sysperfinfo b where a.counter_name = 'Buffer cache hit ratio' and b.counter_name='Buffer cache hit ratio base'",
+ '=WarnCacheRatio'),
+ 'prepared sql hit ratio' => array('RATIO',
+ array('dbcc cachestats','Prepared',1,100),
+ ''),
+ 'adhoc sql hit ratio' => array('RATIO',
+ array('dbcc cachestats','Adhoc',1,100),
+ ''),
+ 'IO',
+ 'data reads' => array('IO',
+ "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page reads/sec'"),
+ 'data writes' => array('IO',
+ "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page writes/sec'"),
+
+ 'Data Cache',
+ 'data cache size' => array('DATAC',
+ "select cntr_value*8192 from master.dbo.sysperfinfo where counter_name = 'Total Pages' and object_name='SQLServer:Buffer Manager'",
+ '' ),
+ 'data cache blocksize' => array('DATAC',
+ "select 8192",'page size'),
+ 'Connections',
+ 'current connections' => array('SESS',
+ '=sp_who',
+ ''),
+ 'max connections' => array('SESS',
+ "SELECT @@MAX_CONNECTIONS",
+ ''),
+
+ false
+ );
+
+
+ function __construct(&$conn)
+ {
+ if ($conn->dataProvider == 'odbc') {
+ $this->sql1 = 'sql1';
+ //$this->explain = false;
+ }
+ $this->conn = $conn;
+ }
+
+ function Explain($sql,$partial=false)
+ {
+
+ $save = $this->conn->LogSQL(false);
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+
+ $s = '<p><b>Explain</b>: '.htmlspecialchars($sql).'</p>';
+ $this->conn->Execute("SET SHOWPLAN_ALL ON;");
+ $sql = str_replace('?',"''",$sql);
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $rs = $this->conn->Execute($sql);
+ //adodb_printr($rs);
+ $ADODB_FETCH_MODE = $save;
+ if ($rs && !$rs->EOF) {
+ $rs->MoveNext();
+ $s .= '<table bgcolor=white border=0 cellpadding="1" callspacing=0><tr><td nowrap align=center> Rows<td nowrap align=center> IO<td nowrap align=center> CPU<td align=left> &nbsp; &nbsp; Plan</tr>';
+ while (!$rs->EOF) {
+ $s .= '<tr><td>'.round($rs->fields[8],1).'<td>'.round($rs->fields[9],3).'<td align=right>'.round($rs->fields[10],3).'<td nowrap><pre>'.htmlspecialchars($rs->fields[0])."</td></pre></tr>\n"; ## NOTE CORRUPT </td></pre> tag is intentional!!!!
+ $rs->MoveNext();
+ }
+ $s .= '</table>';
+
+ $rs->NextRecordSet();
+ }
+
+ $this->conn->Execute("SET SHOWPLAN_ALL OFF;");
+ $this->conn->LogSQL($save);
+ $s .= $this->Tracer($sql);
+ return $s;
+ }
+
+ function Tables()
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ //$this->conn->debug=1;
+ $s = '<table border=1 bgcolor=white><tr><td><b>tablename</b></td><td><b>size_in_k</b></td><td><b>index size</b></td><td><b>reserved size</b></td></tr>';
+ $rs1 = $this->conn->Execute("select distinct name from sysobjects where xtype='U'");
+ if ($rs1) {
+ while (!$rs1->EOF) {
+ $tab = $rs1->fields[0];
+ $tabq = $this->conn->qstr($tab);
+ $rs2 = $this->conn->Execute("sp_spaceused $tabq");
+ if ($rs2) {
+ $s .= '<tr><td>'.$tab.'</td><td align=right>'.$rs2->fields[3].'</td><td align=right>'.$rs2->fields[4].'</td><td align=right>'.$rs2->fields[2].'</td></tr>';
+ $rs2->Close();
+ }
+ $rs1->MoveNext();
+ }
+ $rs1->Close();
+ }
+ $ADODB_FETCH_MODE = $save;
+ return $s.'</table>';
+ }
+
+ function sp_who()
+ {
+ $arr = $this->conn->GetArray('sp_who');
+ return sizeof($arr);
+ }
+
+ function HealthCheck($cli=false)
+ {
+
+ $this->conn->Execute('dbcc traceon(3604)');
+ $html = adodb_perf::HealthCheck($cli);
+ $this->conn->Execute('dbcc traceoff(3604)');
+ return $html;
+ }
+
+
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-mssqlnative.inc.php b/vendor/adodb/adodb-php/perf/perf-mssqlnative.inc.php
new file mode 100644
index 0000000..56cd2fd
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-mssqlnative.inc.php
@@ -0,0 +1,164 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+ MSSQL has moved most performance info to Performance Monitor
+*/
+class perf_mssqlnative extends adodb_perf{
+ var $sql1 = 'cast(sql1 as text)';
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created datetime NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 varchar(4000) NOT NULL,
+ params varchar(3000) NOT NULL,
+ tracer varchar(500) NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'data cache hit ratio' => array('RATIO',
+ "select round((a.cntr_value*100.0)/b.cntr_value,2) from master.dbo.sysperfinfo a, master.dbo.sysperfinfo b where a.counter_name = 'Buffer cache hit ratio' and b.counter_name='Buffer cache hit ratio base'",
+ '=WarnCacheRatio'),
+ 'prepared sql hit ratio' => array('RATIO',
+ array('dbcc cachestats','Prepared',1,100),
+ ''),
+ 'adhoc sql hit ratio' => array('RATIO',
+ array('dbcc cachestats','Adhoc',1,100),
+ ''),
+ 'IO',
+ 'data reads' => array('IO',
+ "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page reads/sec'"),
+ 'data writes' => array('IO',
+ "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page writes/sec'"),
+
+ 'Data Cache',
+ 'data cache size' => array('DATAC',
+ "select cntr_value*8192 from master.dbo.sysperfinfo where counter_name = 'Total Pages' and object_name='SQLServer:Buffer Manager'",
+ '' ),
+ 'data cache blocksize' => array('DATAC',
+ "select 8192",'page size'),
+ 'Connections',
+ 'current connections' => array('SESS',
+ '=sp_who',
+ ''),
+ 'max connections' => array('SESS',
+ "SELECT @@MAX_CONNECTIONS",
+ ''),
+
+ false
+ );
+
+
+ function __construct(&$conn)
+ {
+ if ($conn->dataProvider == 'odbc') {
+ $this->sql1 = 'sql1';
+ //$this->explain = false;
+ }
+ $this->conn =& $conn;
+ }
+
+ function Explain($sql,$partial=false)
+ {
+
+ $save = $this->conn->LogSQL(false);
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+
+ $s = '<p><b>Explain</b>: '.htmlspecialchars($sql).'</p>';
+ $this->conn->Execute("SET SHOWPLAN_ALL ON;");
+ $sql = str_replace('?',"''",$sql);
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $rs =& $this->conn->Execute($sql);
+ //adodb_printr($rs);
+ $ADODB_FETCH_MODE = $save;
+ if ($rs) {
+ $rs->MoveNext();
+ $s .= '<table bgcolor=white border=0 cellpadding="1" callspacing=0><tr><td nowrap align=center> Rows<td nowrap align=center> IO<td nowrap align=center> CPU<td align=left> &nbsp; &nbsp; Plan</tr>';
+ while (!$rs->EOF) {
+ $s .= '<tr><td>'.round($rs->fields[8],1).'<td>'.round($rs->fields[9],3).'<td align=right>'.round($rs->fields[10],3).'<td nowrap><pre>'.htmlspecialchars($rs->fields[0])."</td></pre></tr>\n"; ## NOTE CORRUPT </td></pre> tag is intentional!!!!
+ $rs->MoveNext();
+ }
+ $s .= '</table>';
+
+ $rs->NextRecordSet();
+ }
+
+ $this->conn->Execute("SET SHOWPLAN_ALL OFF;");
+ $this->conn->LogSQL($save);
+ $s .= $this->Tracer($sql);
+ return $s;
+ }
+
+ function Tables($orderby='1')
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ //$this->conn->debug=1;
+ $s = '<table border=1 bgcolor=white><tr><td><b>tablename</b></td><td><b>size_in_k</b></td><td><b>index size</b></td><td><b>reserved size</b></td></tr>';
+ $rs1 = $this->conn->Execute("select distinct name from sysobjects where xtype='U'");
+ if ($rs1) {
+ while (!$rs1->EOF) {
+ $tab = $rs1->fields[0];
+ $tabq = $this->conn->qstr($tab);
+ $rs2 = $this->conn->Execute("sp_spaceused $tabq");
+ if ($rs2) {
+ $s .= '<tr><td>'.$tab.'</td><td align=right>'.$rs2->fields[3].'</td><td align=right>'.$rs2->fields[4].'</td><td align=right>'.$rs2->fields[2].'</td></tr>';
+ $rs2->Close();
+ }
+ $rs1->MoveNext();
+ }
+ $rs1->Close();
+ }
+ $ADODB_FETCH_MODE = $save;
+ return $s.'</table>';
+ }
+
+ function sp_who()
+ {
+ $arr = $this->conn->GetArray('sp_who');
+ return sizeof($arr);
+ }
+
+ function HealthCheck($cli=false)
+ {
+
+ $this->conn->Execute('dbcc traceon(3604)');
+ $html = adodb_perf::HealthCheck($cli);
+ $this->conn->Execute('dbcc traceoff(3604)');
+ return $html;
+ }
+
+
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-mysql.inc.php b/vendor/adodb/adodb-php/perf/perf-mysql.inc.php
new file mode 100644
index 0000000..fe0f8b0
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-mysql.inc.php
@@ -0,0 +1,316 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class perf_mysql extends adodb_perf{
+
+ var $tablesSQL = 'show table status';
+
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created datetime NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 text NOT NULL,
+ params text NOT NULL,
+ tracer text NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'MyISAM cache hit ratio' => array('RATIO',
+ '=GetKeyHitRatio',
+ '=WarnCacheRatio'),
+ 'InnoDB cache hit ratio' => array('RATIO',
+ '=GetInnoDBHitRatio',
+ '=WarnCacheRatio'),
+ 'data cache hit ratio' => array('HIDE', # only if called
+ '=FindDBHitRatio',
+ '=WarnCacheRatio'),
+ 'sql cache hit ratio' => array('RATIO',
+ '=GetQHitRatio',
+ ''),
+ 'IO',
+ 'data reads' => array('IO',
+ '=GetReads',
+ 'Number of selects (Key_reads is not accurate)'),
+ 'data writes' => array('IO',
+ '=GetWrites',
+ 'Number of inserts/updates/deletes * coef (Key_writes is not accurate)'),
+
+ 'Data Cache',
+ 'MyISAM data cache size' => array('DATAC',
+ array("show variables", 'key_buffer_size'),
+ '' ),
+ 'BDB data cache size' => array('DATAC',
+ array("show variables", 'bdb_cache_size'),
+ '' ),
+ 'InnoDB data cache size' => array('DATAC',
+ array("show variables", 'innodb_buffer_pool_size'),
+ '' ),
+ 'Memory Usage',
+ 'read buffer size' => array('CACHE',
+ array("show variables", 'read_buffer_size'),
+ '(per session)'),
+ 'sort buffer size' => array('CACHE',
+ array("show variables", 'sort_buffer_size'),
+ 'Size of sort buffer (per session)' ),
+ 'table cache' => array('CACHE',
+ array("show variables", 'table_cache'),
+ 'Number of tables to keep open'),
+ 'Connections',
+ 'current connections' => array('SESS',
+ array('show status','Threads_connected'),
+ ''),
+ 'max connections' => array( 'SESS',
+ array("show variables",'max_connections'),
+ ''),
+
+ false
+ );
+
+ function __construct(&$conn)
+ {
+ $this->conn = $conn;
+ }
+
+ function Explain($sql,$partial=false)
+ {
+
+ if (strtoupper(substr(trim($sql),0,6)) !== 'SELECT') return '<p>Unable to EXPLAIN non-select statement</p>';
+ $save = $this->conn->LogSQL(false);
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+ $sql = str_replace('?',"''",$sql);
+
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $sql = $this->conn->GetOne("select sql1 from adodb_logsql where sql1 like $sqlq");
+ }
+
+ $s = '<p><b>Explain</b>: '.htmlspecialchars($sql).'</p>';
+ $rs = $this->conn->Execute('EXPLAIN '.$sql);
+ $s .= rs2html($rs,false,false,false,false);
+ $this->conn->LogSQL($save);
+ $s .= $this->Tracer($sql);
+ return $s;
+ }
+
+ function tables($orderby='1')
+ {
+ if (!$this->tablesSQL) return false;
+
+ $rs = $this->conn->Execute($this->tablesSQL);
+ if (!$rs) return false;
+
+ $html = rs2html($rs,false,false,false,false);
+ return $html;
+ }
+
+ function GetReads()
+ {
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->Execute('show status');
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$rs) return 0;
+ $val = 0;
+ while (!$rs->EOF) {
+ switch($rs->fields[0]) {
+ case 'Com_select':
+ $val = $rs->fields[1];
+ $rs->Close();
+ return $val;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+
+ return $val;
+ }
+
+ function GetWrites()
+ {
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->Execute('show status');
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$rs) return 0;
+ $val = 0.0;
+ while (!$rs->EOF) {
+ switch($rs->fields[0]) {
+ case 'Com_insert':
+ $val += $rs->fields[1]; break;
+ case 'Com_delete':
+ $val += $rs->fields[1]; break;
+ case 'Com_update':
+ $val += $rs->fields[1]/2;
+ $rs->Close();
+ return $val;
+ }
+ $rs->MoveNext();
+ }
+
+ $rs->Close();
+
+ return $val;
+ }
+
+ function FindDBHitRatio()
+ {
+ // first find out type of table
+ //$this->conn->debug=1;
+
+ global $ADODB_FETCH_MODE;
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->Execute('show table status');
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$rs) return '';
+ $type = strtoupper($rs->fields[1]);
+ $rs->Close();
+ switch($type){
+ case 'MYISAM':
+ case 'ISAM':
+ return $this->DBParameter('MyISAM cache hit ratio').' (MyISAM)';
+ case 'INNODB':
+ return $this->DBParameter('InnoDB cache hit ratio').' (InnoDB)';
+ default:
+ return $type.' not supported';
+ }
+
+ }
+
+ function GetQHitRatio()
+ {
+ //Total number of queries = Qcache_inserts + Qcache_hits + Qcache_not_cached
+ $hits = $this->_DBParameter(array("show status","Qcache_hits"));
+ $total = $this->_DBParameter(array("show status","Qcache_inserts"));
+ $total += $this->_DBParameter(array("show status","Qcache_not_cached"));
+
+ $total += $hits;
+ if ($total) return round(($hits*100)/$total,2);
+ return 0;
+ }
+
+ /*
+ Use session variable to store Hit percentage, because MySQL
+ does not remember last value of SHOW INNODB STATUS hit ratio
+
+ # 1st query to SHOW INNODB STATUS
+ 0.00 reads/s, 0.00 creates/s, 0.00 writes/s
+ Buffer pool hit rate 1000 / 1000
+
+ # 2nd query to SHOW INNODB STATUS
+ 0.00 reads/s, 0.00 creates/s, 0.00 writes/s
+ No buffer pool activity since the last printout
+ */
+ function GetInnoDBHitRatio()
+ {
+ global $ADODB_FETCH_MODE;
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $rs = $this->conn->Execute('show engine innodb status');
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_FETCH_MODE = $save;
+
+ if (!$rs || $rs->EOF) return 0;
+ $stat = $rs->fields[0];
+ $rs->Close();
+ $at = strpos($stat,'Buffer pool hit rate');
+ $stat = substr($stat,$at,200);
+ if (preg_match('!Buffer pool hit rate\s*([0-9]*) / ([0-9]*)!',$stat,$arr)) {
+ $val = 100*$arr[1]/$arr[2];
+ $_SESSION['INNODB_HIT_PCT'] = $val;
+ return round($val,2);
+ } else {
+ if (isset($_SESSION['INNODB_HIT_PCT'])) return $_SESSION['INNODB_HIT_PCT'];
+ return 0;
+ }
+ return 0;
+ }
+
+ function GetKeyHitRatio()
+ {
+ $hits = $this->_DBParameter(array("show status","Key_read_requests"));
+ $reqs = $this->_DBParameter(array("show status","Key_reads"));
+ if ($reqs == 0) return 0;
+
+ return round(($hits/($reqs+$hits))*100,2);
+ }
+
+ // start hack
+ var $optimizeTableLow = 'CHECK TABLE %s FAST QUICK';
+ var $optimizeTableHigh = 'OPTIMIZE TABLE %s';
+
+ /**
+ * @see adodb_perf#optimizeTable
+ */
+ function optimizeTable( $table, $mode = ADODB_OPT_LOW)
+ {
+ if ( !is_string( $table)) return false;
+
+ $conn = $this->conn;
+ if ( !$conn) return false;
+
+ $sql = '';
+ switch( $mode) {
+ case ADODB_OPT_LOW : $sql = $this->optimizeTableLow; break;
+ case ADODB_OPT_HIGH : $sql = $this->optimizeTableHigh; break;
+ default :
+ {
+ // May dont use __FUNCTION__ constant for BC (__FUNCTION__ Added in PHP 4.3.0)
+ ADOConnection::outp( sprintf( "<p>%s: '%s' using of undefined mode '%s'</p>", __CLASS__, __FUNCTION__, $mode));
+ return false;
+ }
+ }
+ $sql = sprintf( $sql, $table);
+
+ return $conn->Execute( $sql) !== false;
+ }
+ // end hack
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-oci8.inc.php b/vendor/adodb/adodb-php/perf/perf-oci8.inc.php
new file mode 100644
index 0000000..8830e6d
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-oci8.inc.php
@@ -0,0 +1,703 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+
+class perf_oci8 extends ADODB_perf{
+
+ var $noShowIxora = 15; // if the sql for suspicious sql is taking too long, then disable ixora
+
+ var $tablesSQL = "select segment_name as \"tablename\", sum(bytes)/1024 as \"size_in_k\",tablespace_name as \"tablespace\",count(*) \"extents\" from sys.user_extents
+ group by segment_name,tablespace_name";
+
+ var $version;
+
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created date NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 varchar(4000) NOT NULL,
+ params varchar(4000),
+ tracer varchar(4000),
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'data cache hit ratio' => array('RATIOH',
+ "select round((1-(phy.value / (cur.value + con.value)))*100,2)
+ from v\$sysstat cur, v\$sysstat con, v\$sysstat phy
+ where cur.name = 'db block gets' and
+ con.name = 'consistent gets' and
+ phy.name = 'physical reads'",
+ '=WarnCacheRatio'),
+
+ 'sql cache hit ratio' => array( 'RATIOH',
+ 'select round(100*(sum(pins)-sum(reloads))/sum(pins),2) from v$librarycache',
+ 'increase <i>shared_pool_size</i> if too ratio low'),
+
+ 'datadict cache hit ratio' => array('RATIOH',
+ "select
+ round((1 - (sum(getmisses) / (sum(gets) +
+ sum(getmisses))))*100,2)
+ from v\$rowcache",
+ 'increase <i>shared_pool_size</i> if too ratio low'),
+
+ 'memory sort ratio' => array('RATIOH',
+ "SELECT ROUND((100 * b.VALUE) /DECODE ((a.VALUE + b.VALUE),
+ 0,1,(a.VALUE + b.VALUE)),2)
+FROM v\$sysstat a,
+ v\$sysstat b
+WHERE a.name = 'sorts (disk)'
+AND b.name = 'sorts (memory)'",
+ "% of memory sorts compared to disk sorts - should be over 95%"),
+
+ 'IO',
+ 'data reads' => array('IO',
+ "select value from v\$sysstat where name='physical reads'"),
+
+ 'data writes' => array('IO',
+ "select value from v\$sysstat where name='physical writes'"),
+
+ 'Data Cache',
+
+ 'data cache buffers' => array( 'DATAC',
+ "select a.value/b.value from v\$parameter a, v\$parameter b
+ where a.name = 'db_cache_size' and b.name= 'db_block_size'",
+ 'Number of cache buffers. Tune <i>db_cache_size</i> if the <i>data cache hit ratio</i> is too low.'),
+ 'data cache blocksize' => array('DATAC',
+ "select value from v\$parameter where name='db_block_size'",
+ '' ),
+
+ 'Memory Pools',
+ 'Mem Max Target (11g+)' => array( 'DATAC',
+ "select value from v\$parameter where name = 'memory_max_target'",
+ 'The memory_max_size is the maximum value to which memory_target can be set.' ),
+ 'Memory target (11g+)' => array( 'DATAC',
+ "select value from v\$parameter where name = 'memory_target'",
+ 'If memory_target is defined then SGA and PGA targets are consolidated into one memory_target.' ),
+ 'SGA Max Size' => array( 'DATAC',
+ "select nvl(value,0)/1024.0/1024 || 'M' from v\$parameter where name = 'sga_max_size'",
+ 'The sga_max_size is the maximum value to which sga_target can be set.' ),
+ 'SGA target' => array( 'DATAC',
+ "select nvl(value,0)/1024.0/1024 || 'M' from v\$parameter where name = 'sga_target'",
+ 'If sga_target is defined then data cache, shared, java and large pool size can be 0. This is because all these pools are consolidated into one sga_target.' ),
+ 'PGA aggr target' => array( 'DATAC',
+ "select nvl(value,0)/1024.0/1024 || 'M' from v\$parameter where name = 'pga_aggregate_target'",
+ 'If pga_aggregate_target is defined then this is the maximum memory that can be allocated for cursor operations such as sorts, group by, joins, merges. When in doubt, set it to 20% of sga_target.' ),
+ 'data cache size' => array('DATAC',
+ "select value from v\$parameter where name = 'db_cache_size'",
+ 'db_cache_size' ),
+ 'shared pool size' => array('DATAC',
+ "select value from v\$parameter where name = 'shared_pool_size'",
+ 'shared_pool_size, which holds shared sql, stored procedures, dict cache and similar shared structs' ),
+ 'java pool size' => array('DATAJ',
+ "select value from v\$parameter where name = 'java_pool_size'",
+ 'java_pool_size' ),
+ 'large pool buffer size' => array('CACHE',
+ "select value from v\$parameter where name='large_pool_size'",
+ 'this pool is for large mem allocations (not because it is larger than shared pool), for MTS sessions, parallel queries, io buffers (large_pool_size) ' ),
+
+ 'dynamic memory usage' => array('CACHE', "select '-' from dual", '=DynMemoryUsage'),
+
+ 'Connections',
+ 'current connections' => array('SESS',
+ 'select count(*) from sys.v_$session where username is not null',
+ ''),
+ 'max connections' => array( 'SESS',
+ "select value from v\$parameter where name='sessions'",
+ ''),
+
+ 'Memory Utilization',
+ 'data cache utilization ratio' => array('RATIOU',
+ "select round((1-bytes/sgasize)*100, 2)
+ from (select sum(bytes) sgasize from sys.v_\$sgastat) s, sys.v_\$sgastat f
+ where name = 'free memory' and pool = 'shared pool'",
+ 'Percentage of data cache actually in use - should be over 85%'),
+
+ 'shared pool utilization ratio' => array('RATIOU',
+ 'select round((sga.bytes/case when p.value=0 then sga.bytes else to_number(p.value) end)*100,2)
+ from v$sgastat sga, v$parameter p
+ where sga.name = \'free memory\' and sga.pool = \'shared pool\'
+ and p.name = \'shared_pool_size\'',
+ 'Percentage of shared pool actually used - too low is bad, too high is worse'),
+
+ 'large pool utilization ratio' => array('RATIOU',
+ "select round((1-bytes/sgasize)*100, 2)
+ from (select sum(bytes) sgasize from sys.v_\$sgastat) s, sys.v_\$sgastat f
+ where name = 'free memory' and pool = 'large pool'",
+ 'Percentage of large_pool actually in use - too low is bad, too high is worse'),
+ 'sort buffer size' => array('CACHE',
+ "select value from v\$parameter where name='sort_area_size'",
+ 'max in-mem sort_area_size (per query), uses memory in pga' ),
+
+ /*'pga usage at peak' => array('RATIOU',
+ '=PGA','Mb utilization at peak transactions (requires Oracle 9i+)'),*/
+ 'Transactions',
+ 'rollback segments' => array('ROLLBACK',
+ "select count(*) from sys.v_\$rollstat",
+ ''),
+
+ 'peak transactions' => array('ROLLBACK',
+ "select max_utilization tx_hwm
+ from sys.v_\$resource_limit
+ where resource_name = 'transactions'",
+ 'Taken from high-water-mark'),
+ 'max transactions' => array('ROLLBACK',
+ "select value from v\$parameter where name = 'transactions'",
+ 'max transactions / rollback segments < 3.5 (or transactions_per_rollback_segment)'),
+ 'Parameters',
+ 'cursor sharing' => array('CURSOR',
+ "select value from v\$parameter where name = 'cursor_sharing'",
+ 'Cursor reuse strategy. Recommended is FORCE (8i+) or SIMILAR (9i+). See <a href=http://www.praetoriate.com/oracle_tips_cursor_sharing.htm>cursor_sharing</a>.'),
+ /*
+ 'cursor reuse' => array('CURSOR',
+ "select count(*) from (select sql_text_wo_constants, count(*)
+ from t1
+ group by sql_text_wo_constants
+having count(*) > 100)",'These are sql statements that should be using bind variables'),*/
+ 'index cache cost' => array('COST',
+ "select value from v\$parameter where name = 'optimizer_index_caching'",
+ '=WarnIndexCost'),
+ 'random page cost' => array('COST',
+ "select value from v\$parameter where name = 'optimizer_index_cost_adj'",
+ '=WarnPageCost'),
+ 'Waits',
+ 'Recent wait events' => array('WAITS','select \'Top 5 events\' from dual','=TopRecentWaits'),
+// 'Historical wait SQL' => array('WAITS','select \'Last 2 days\' from dual','=TopHistoricalWaits'), -- requires AWR license
+ 'Backup',
+ 'Achivelog Mode' => array('BACKUP', 'select log_mode from v$database', '=LogMode'),
+
+ 'DBID' => array('BACKUP','select dbid from v$database','Primary key of database, used for recovery with an RMAN Recovery Catalog'),
+ 'Archive Log Dest' => array('BACKUP', "SELECT NVL(v1.value,v2.value)
+FROM v\$parameter v1, v\$parameter v2 WHERE v1.name='log_archive_dest' AND v2.name='log_archive_dest_10'", ''),
+
+ 'Flashback Area' => array('BACKUP', "select nvl(value,'Flashback Area not used') from v\$parameter where name=lower('DB_RECOVERY_FILE_DEST')", 'Flashback area is a folder where all backup data and logs can be stored and managed by Oracle. If Error: message displayed, then it is not in use.'),
+
+ 'Flashback Usage' => array('BACKUP', "select nvl('-','Flashback Area not used') from v\$parameter where name=lower('DB_RECOVERY_FILE_DEST')", '=FlashUsage', 'Flashback area usage.'),
+
+ 'Control File Keep Time' => array('BACKUP', "select value from v\$parameter where name='control_file_record_keep_time'",'No of days to keep RMAN info in control file. Recommended set to x2 or x3 times the frequency of your full backup.'),
+ 'Recent RMAN Jobs' => array('BACKUP', "select '-' from dual", "=RMAN"),
+
+ // 'Control File Keep Time' => array('BACKUP', "select value from v\$parameter where name='control_file_record_keep_time'",'No of days to keep RMAN info in control file. I recommend it be set to x2 or x3 times the frequency of your full backup.'),
+ 'Storage', 'Tablespaces' => array('TABLESPACE', "select '-' from dual", "=TableSpace"),
+ false
+
+ );
+
+
+ function __construct(&$conn)
+ {
+ global $gSQLBlockRows;
+
+ $gSQLBlockRows = 1000;
+ $savelog = $conn->LogSQL(false);
+ $this->version = $conn->ServerInfo();
+ $conn->LogSQL($savelog);
+ $this->conn = $conn;
+ }
+
+ function LogMode()
+ {
+ $mode = $this->conn->GetOne("select log_mode from v\$database");
+
+ if ($mode == 'ARCHIVELOG') return 'To turn off archivelog:<br>
+ <pre><font size=-2>
+ SQLPLUS> connect sys as sysdba;
+ SQLPLUS> shutdown immediate;
+
+ SQLPLUS> startup mount exclusive;
+ SQLPLUS> alter database noarchivelog;
+ SQLPLUS> alter database open;
+</font></pre>';
+
+ return 'To turn on archivelog:<br>
+ <pre><font size=-2>
+ SQLPLUS> connect sys as sysdba;
+ SQLPLUS> shutdown immediate;
+
+ SQLPLUS> startup mount exclusive;
+ SQLPLUS> alter database archivelog;
+ SQLPLUS> archive log start;
+ SQLPLUS> alter database open;
+</font></pre>';
+ }
+
+ function TopRecentWaits()
+ {
+
+ $rs = $this->conn->Execute("select * from (
+ select event, round(100*time_waited/(select sum(time_waited) from v\$system_event where wait_class <> 'Idle'),1) \"% Wait\",
+ total_waits,time_waited, average_wait,wait_class from v\$system_event where wait_class <> 'Idle' order by 2 desc
+ ) where rownum <=5");
+
+ $ret = rs2html($rs,false,false,false,false);
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+
+ }
+
+ function TopHistoricalWaits()
+ {
+ $days = 2;
+
+ $rs = $this->conn->Execute("select * from ( SELECT
+ b.wait_class,B.NAME,
+ round(sum(wait_time+TIME_WAITED)/1000000) waitsecs,
+ parsing_schema_name,
+ C.SQL_TEXT, a.sql_id
+FROM V\$ACTIVE_SESSION_HISTORY A
+ join V\$EVENT_NAME B on A.EVENT# = B.EVENT#
+ join V\$SQLAREA C on A.SQL_ID = C.SQL_ID
+WHERE A.SAMPLE_TIME BETWEEN sysdate-$days and sysdate
+ and parsing_schema_name not in ('SYS','SYSMAN','DBSNMP','SYSTEM')
+GROUP BY b.wait_class,parsing_schema_name,C.SQL_TEXT, B.NAME,A.sql_id
+order by 3 desc) where rownum <=10");
+
+ $ret = rs2html($rs,false,false,false,false);
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+
+ }
+
+ function TableSpace()
+ {
+
+ $rs = $this->conn->Execute(
+ "select tablespace_name,round(sum(bytes)/1024/1024) as Used_MB,round(sum(maxbytes)/1024/1024) as Max_MB, round(sum(bytes)/sum(maxbytes),4) * 100 as PCT
+ from dba_data_files
+ group by tablespace_name order by 2 desc");
+
+ $ret = "<p><b>Tablespace</b>".rs2html($rs,false,false,false,false);
+
+ $rs = $this->conn->Execute("select * from dba_data_files order by tablespace_name, 1");
+ $ret .= "<p><b>Datafile</b>".rs2html($rs,false,false,false,false);
+
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+ }
+
+ function RMAN()
+ {
+ $rs = $this->conn->Execute("select * from (select start_time, end_time, operation, status, mbytes_processed, output_device_type
+ from V\$RMAN_STATUS order by start_time desc) where rownum <=10");
+
+ $ret = rs2html($rs,false,false,false,false);
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+
+ }
+
+ function DynMemoryUsage()
+ {
+ if (@$this->version['version'] >= 11) {
+ $rs = $this->conn->Execute("select component, current_size/1024./1024 as \"CurrSize (M)\" from V\$MEMORY_DYNAMIC_COMPONENTS");
+
+ } else
+ $rs = $this->conn->Execute("select name, round(bytes/1024./1024,2) as \"CurrSize (M)\" from V\$sgainfo");
+
+
+ $ret = rs2html($rs,false,false,false,false);
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+ }
+
+ function FlashUsage()
+ {
+ $rs = $this->conn->Execute("select * from V\$FLASH_RECOVERY_AREA_USAGE");
+ $ret = rs2html($rs,false,false,false,false);
+ return "&nbsp;<p>".$ret."&nbsp;</p>";
+ }
+
+ function WarnPageCost($val)
+ {
+ if ($val == 100 && $this->version['version'] < 10) $s = '<font color=red><b>Too High</b>. </font>';
+ else $s = '';
+
+ return $s.'Recommended is 20-50 for TP, and 50 for data warehouses. Default is 100. See <a href=http://www.dba-oracle.com/oracle_tips_cost_adj.htm>optimizer_index_cost_adj</a>. ';
+ }
+
+ function WarnIndexCost($val)
+ {
+ if ($val == 0 && $this->version['version'] < 10) $s = '<font color=red><b>Too Low</b>. </font>';
+ else $s = '';
+
+ return $s.'Percentage of indexed data blocks expected in the cache.
+ Recommended is 20 (fast disk array) to 30 (slower hard disks). Default is 0.
+ See <a href=http://www.dba-oracle.com/oracle_tips_cbo_part1.htm>optimizer_index_caching</a>.';
+ }
+
+ function PGA()
+ {
+
+ //if ($this->version['version'] < 9) return 'Oracle 9i or later required';
+ }
+
+ function PGA_Advice()
+ {
+ $t = "<h3>PGA Advice Estimate</h3>";
+ if ($this->version['version'] < 9) return $t.'Oracle 9i or later required';
+
+ $rs = $this->conn->Execute('select a.MB,
+ case when a.targ = 1 then \'<<= Current \'
+ when a.targ < 1 or a.pct <= b.pct then null
+ else
+ \'- BETTER than Current by \'||round(a.pct/b.pct*100-100,2)||\'%\' end as "Percent Improved",
+ a.targ as "PGA Size Factor",a.pct "% Perf"
+ from
+ (select round(pga_target_for_estimate/1024.0/1024.0,0) MB,
+ pga_target_factor targ,estd_pga_cache_hit_percentage pct,rownum as r
+ from v$pga_target_advice) a left join
+ (select round(pga_target_for_estimate/1024.0/1024.0,0) MB,
+ pga_target_factor targ,estd_pga_cache_hit_percentage pct,rownum as r
+ from v$pga_target_advice) b on
+ a.r = b.r+1 where
+ b.pct < 100');
+ if (!$rs) return $t."Only in 9i or later";
+ // $rs->Close();
+ if ($rs->EOF) return $t."PGA could be too big";
+
+ return $t.rs2html($rs,false,false,true,false);
+ }
+
+ function Explain($sql,$partial=false)
+ {
+ $savelog = $this->conn->LogSQL(false);
+ $rs = $this->conn->SelectLimit("select ID FROM PLAN_TABLE");
+ if (!$rs) {
+ echo "<p><b>Missing PLAN_TABLE</b></p>
+<pre>
+CREATE TABLE PLAN_TABLE (
+ STATEMENT_ID VARCHAR2(30),
+ TIMESTAMP DATE,
+ REMARKS VARCHAR2(80),
+ OPERATION VARCHAR2(30),
+ OPTIONS VARCHAR2(30),
+ OBJECT_NODE VARCHAR2(128),
+ OBJECT_OWNER VARCHAR2(30),
+ OBJECT_NAME VARCHAR2(30),
+ OBJECT_INSTANCE NUMBER(38),
+ OBJECT_TYPE VARCHAR2(30),
+ OPTIMIZER VARCHAR2(255),
+ SEARCH_COLUMNS NUMBER,
+ ID NUMBER(38),
+ PARENT_ID NUMBER(38),
+ POSITION NUMBER(38),
+ COST NUMBER(38),
+ CARDINALITY NUMBER(38),
+ BYTES NUMBER(38),
+ OTHER_TAG VARCHAR2(255),
+ PARTITION_START VARCHAR2(255),
+ PARTITION_STOP VARCHAR2(255),
+ PARTITION_ID NUMBER(38),
+ OTHER LONG,
+ DISTRIBUTION VARCHAR2(30)
+);
+</pre>";
+ return false;
+ }
+
+ $rs->Close();
+ // $this->conn->debug=1;
+
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+
+ $s = "<p><b>Explain</b>: ".htmlspecialchars($sql)."</p>";
+
+ $this->conn->BeginTrans();
+ $id = "ADODB ".microtime();
+
+ $rs = $this->conn->Execute("EXPLAIN PLAN SET STATEMENT_ID='$id' FOR $sql");
+ $m = $this->conn->ErrorMsg();
+ if ($m) {
+ $this->conn->RollbackTrans();
+ $this->conn->LogSQL($savelog);
+ $s .= "<p>$m</p>";
+ return $s;
+ }
+ $rs = $this->conn->Execute("
+ select
+ '<pre>'||lpad('--', (level-1)*2,'-') || trim(operation) || ' ' || trim(options)||'</pre>' as Operation,
+ object_name,COST,CARDINALITY,bytes
+ FROM plan_table
+START WITH id = 0 and STATEMENT_ID='$id'
+CONNECT BY prior id=parent_id and statement_id='$id'");
+
+ $s .= rs2html($rs,false,false,false,false);
+ $this->conn->RollbackTrans();
+ $this->conn->LogSQL($savelog);
+ $s .= $this->Tracer($sql,$partial);
+ return $s;
+ }
+
+ function CheckMemory()
+ {
+ if ($this->version['version'] < 9) return 'Oracle 9i or later required';
+
+ $rs = $this->conn->Execute("
+select a.name Buffer_Pool, b.size_for_estimate as cache_mb_estimate,
+ case when b.size_factor=1 then
+ '&lt;&lt;= Current'
+ when a.estd_physical_read_factor-b.estd_physical_read_factor > 0.001 and b.estd_physical_read_factor<1 then
+ '- BETTER than current by ' || round((1-b.estd_physical_read_factor)/b.estd_physical_read_factor*100,2) || '%'
+ else ' ' end as RATING,
+ b.estd_physical_read_factor \"Phys. Reads Factor\",
+ round((a.estd_physical_read_factor-b.estd_physical_read_factor)/b.estd_physical_read_factor*100,2) as \"% Improve\"
+ from (select size_for_estimate,size_factor,estd_physical_read_factor,rownum r,name from v\$db_cache_advice order by name,1) a ,
+ (select size_for_estimate,size_factor,estd_physical_read_factor,rownum r,name from v\$db_cache_advice order by name,1) b
+ where a.r = b.r-1 and a.name = b.name
+ ");
+ if (!$rs) return false;
+
+ /*
+ The v$db_cache_advice utility show the marginal changes in physical data block reads for different sizes of db_cache_size
+ */
+ $s = "<h3>Data Cache Advice Estimate</h3>";
+ if ($rs->EOF) {
+ $s .= "<p>Cache that is 50% of current size is still too big</p>";
+ } else {
+ $s .= "Ideal size of Data Cache is when %BETTER gets close to zero.";
+ $s .= rs2html($rs,false,false,false,false);
+ }
+ return $s.$this->PGA_Advice();
+ }
+
+ /*
+ Generate html for suspicious/expensive sql
+ */
+ function tohtml(&$rs,$type)
+ {
+ $o1 = $rs->FetchField(0);
+ $o2 = $rs->FetchField(1);
+ $o3 = $rs->FetchField(2);
+ if ($rs->EOF) return '<p>None found</p>';
+ $check = '';
+ $sql = '';
+ $s = "\n\n<table border=1 bgcolor=white><tr><td><b>".$o1->name.'</b></td><td><b>'.$o2->name.'</b></td><td><b>'.$o3->name.'</b></td></tr>';
+ while (!$rs->EOF) {
+ if ($check != $rs->fields[0].'::'.$rs->fields[1]) {
+ if ($check) {
+ $carr = explode('::',$check);
+ $prefix = "<a href=\"?$type=1&sql=".rawurlencode($sql).'&x#explain">';
+ $suffix = '</a>';
+ if (strlen($prefix)>2000) {
+ $prefix = '';
+ $suffix = '';
+ }
+
+ $s .= "\n<tr><td align=right>".$carr[0].'</td><td align=right>'.$carr[1].'</td><td>'.$prefix.$sql.$suffix.'</td></tr>';
+ }
+ $sql = $rs->fields[2];
+ $check = $rs->fields[0].'::'.$rs->fields[1];
+ } else
+ $sql .= $rs->fields[2];
+ if (substr($sql,strlen($sql)-1) == "\0") $sql = substr($sql,0,strlen($sql)-1);
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $carr = explode('::',$check);
+ $prefix = "<a target=".rand()." href=\"?&hidem=1&$type=1&sql=".rawurlencode($sql).'&x#explain">';
+ $suffix = '</a>';
+ if (strlen($prefix)>2000) {
+ $prefix = '';
+ $suffix = '';
+ }
+ $s .= "\n<tr><td align=right>".$carr[0].'</td><td align=right>'.$carr[1].'</td><td>'.$prefix.$sql.$suffix.'</td></tr>';
+
+ return $s."</table>\n\n";
+ }
+
+ // code thanks to Ixora.
+ // http://www.ixora.com.au/scripts/query_opt.htm
+ // requires oracle 8.1.7 or later
+ function SuspiciousSQL($numsql=10)
+ {
+ $sql = "
+select
+ substr(to_char(s.pct, '99.00'), 2) || '%' load,
+ s.executions executes,
+ p.sql_text
+from
+ (
+ select
+ address,
+ buffer_gets,
+ executions,
+ pct,
+ rank() over (order by buffer_gets desc) ranking
+ from
+ (
+ select
+ address,
+ buffer_gets,
+ executions,
+ 100 * ratio_to_report(buffer_gets) over () pct
+ from
+ sys.v_\$sql
+ where
+ command_type != 47 and module != 'T.O.A.D.'
+ )
+ where
+ buffer_gets > 50 * executions
+ ) s,
+ sys.v_\$sqltext p
+where
+ s.ranking <= $numsql and
+ p.address = s.address
+order by
+ 1 desc, s.address, p.piece";
+
+ global $ADODB_CACHE_MODE;
+ if (isset($_GET['expsixora']) && isset($_GET['sql'])) {
+ $partial = empty($_GET['part']);
+ echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
+ }
+
+ if (isset($_GET['sql'])) return $this->_SuspiciousSQL($numsql);
+
+ $s = '';
+ $timer = time();
+ $s .= $this->_SuspiciousSQL($numsql);
+ $timer = time() - $timer;
+
+ if ($timer > $this->noShowIxora) return $s;
+ $s .= '<p>';
+
+ $save = $ADODB_CACHE_MODE;
+ $ADODB_CACHE_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $savelog = $this->conn->LogSQL(false);
+ $rs = $this->conn->SelectLimit($sql);
+ $this->conn->LogSQL($savelog);
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_CACHE_MODE = $save;
+ if ($rs) {
+ $s .= "\n<h3>Ixora Suspicious SQL</h3>";
+ $s .= $this->tohtml($rs,'expsixora');
+ }
+
+ return $s;
+ }
+
+ // code thanks to Ixora.
+ // http://www.ixora.com.au/scripts/query_opt.htm
+ // requires oracle 8.1.7 or later
+ function ExpensiveSQL($numsql = 10)
+ {
+ $sql = "
+select
+ substr(to_char(s.pct, '99.00'), 2) || '%' load,
+ s.executions executes,
+ p.sql_text
+from
+ (
+ select
+ address,
+ disk_reads,
+ executions,
+ pct,
+ rank() over (order by disk_reads desc) ranking
+ from
+ (
+ select
+ address,
+ disk_reads,
+ executions,
+ 100 * ratio_to_report(disk_reads) over () pct
+ from
+ sys.v_\$sql
+ where
+ command_type != 47 and module != 'T.O.A.D.'
+ )
+ where
+ disk_reads > 50 * executions
+ ) s,
+ sys.v_\$sqltext p
+where
+ s.ranking <= $numsql and
+ p.address = s.address
+order by
+ 1 desc, s.address, p.piece
+";
+ global $ADODB_CACHE_MODE;
+ if (isset($_GET['expeixora']) && isset($_GET['sql'])) {
+ $partial = empty($_GET['part']);
+ echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
+ }
+ if (isset($_GET['sql'])) {
+ $var = $this->_ExpensiveSQL($numsql);
+ return $var;
+ }
+
+ $s = '';
+ $timer = time();
+ $s .= $this->_ExpensiveSQL($numsql);
+ $timer = time() - $timer;
+ if ($timer > $this->noShowIxora) return $s;
+
+ $s .= '<p>';
+ $save = $ADODB_CACHE_MODE;
+ $ADODB_CACHE_MODE = ADODB_FETCH_NUM;
+ if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
+
+ $savelog = $this->conn->LogSQL(false);
+ $rs = $this->conn->Execute($sql);
+ $this->conn->LogSQL($savelog);
+
+ if (isset($savem)) $this->conn->SetFetchMode($savem);
+ $ADODB_CACHE_MODE = $save;
+
+ if ($rs) {
+ $s .= "\n<h3>Ixora Expensive SQL</h3>";
+ $s .= $this->tohtml($rs,'expeixora');
+ }
+
+ return $s;
+ }
+
+ function clearsql()
+ {
+ $perf_table = adodb_perf::table();
+ // using the naive "delete from $perf_table where created<".$this->conn->sysTimeStamp will cause the table to lock, possibly
+ // for a long time
+ $sql =
+"DECLARE cnt pls_integer;
+BEGIN
+ cnt := 0;
+ FOR rec IN (SELECT ROWID AS rr FROM $perf_table WHERE created<SYSDATE)
+ LOOP
+ cnt := cnt + 1;
+ DELETE FROM $perf_table WHERE ROWID=rec.rr;
+ IF cnt = 1000 THEN
+ COMMIT;
+ cnt := 0;
+ END IF;
+ END LOOP;
+ commit;
+END;";
+
+ $ok = $this->conn->Execute($sql);
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/perf/perf-postgres.inc.php b/vendor/adodb/adodb-php/perf/perf-postgres.inc.php
new file mode 100644
index 0000000..f136c35
--- /dev/null
+++ b/vendor/adodb/adodb-php/perf/perf-postgres.inc.php
@@ -0,0 +1,154 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence. See License.txt.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+
+ Library for basic performance monitoring and tuning
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+ Notice that PostgreSQL has no sql query cache
+*/
+class perf_postgres extends adodb_perf{
+
+ var $tablesSQL =
+ "select a.relname as tablename,(a.relpages+CASE WHEN b.relpages is null THEN 0 ELSE b.relpages END+CASE WHEN c.relpages is null THEN 0 ELSE c.relpages END)*8 as size_in_K,a.relfilenode as \"OID\" from pg_class a left join pg_class b
+ on b.relname = 'pg_toast_'||trim(a.relfilenode)
+ left join pg_class c on c.relname = 'pg_toast_'||trim(a.relfilenode)||'_index'
+ where a.relname in (select tablename from pg_tables where tablename not like 'pg_%')";
+
+ var $createTableSQL = "CREATE TABLE adodb_logsql (
+ created timestamp NOT NULL,
+ sql0 varchar(250) NOT NULL,
+ sql1 text NOT NULL,
+ params text NOT NULL,
+ tracer text NOT NULL,
+ timer decimal(16,6) NOT NULL
+ )";
+
+ var $settings = array(
+ 'Ratios',
+ 'statistics collector' => array('RATIO',
+ "select case when count(*)=3 then 'TRUE' else 'FALSE' end from pg_settings where (name='stats_block_level' or name='stats_row_level' or name='stats_start_collector') and setting='on' ",
+ 'Value must be TRUE to enable hit ratio statistics (<i>stats_start_collector</i>,<i>stats_row_level</i> and <i>stats_block_level</i> must be set to true in postgresql.conf)'),
+ 'data cache hit ratio' => array('RATIO',
+ "select case when blks_hit=0 then 0 else round( ((1-blks_read::float/blks_hit)*100)::numeric, 2) end from pg_stat_database where datname='\$DATABASE'",
+ '=WarnCacheRatio'),
+ 'IO',
+ 'data reads' => array('IO',
+ 'select sum(heap_blks_read+toast_blks_read) from pg_statio_user_tables',
+ ),
+ 'data writes' => array('IO',
+ 'select round((sum(n_tup_ins/4.0+n_tup_upd/8.0+n_tup_del/4.0)/16)::numeric,2) from pg_stat_user_tables',
+ 'Count of inserts/updates/deletes * coef'),
+
+ 'Data Cache',
+ 'data cache buffers' => array('DATAC',
+ "select setting from pg_settings where name='shared_buffers'",
+ 'Number of cache buffers. <a href=http://www.varlena.com/GeneralBits/Tidbits/perf.html#basic>Tuning</a>'),
+ 'cache blocksize' => array('DATAC',
+ 'select 8192',
+ '(estimate)' ),
+ 'data cache size' => array( 'DATAC',
+ "select setting::integer*8192 from pg_settings where name='shared_buffers'",
+ '' ),
+ 'operating system cache size' => array( 'DATA',
+ "select setting::integer*8192 from pg_settings where name='effective_cache_size'",
+ '(effective cache size)' ),
+ 'Memory Usage',
+ # Postgres 7.5 changelog: Rename server parameters SortMem and VacuumMem to work_mem and maintenance_work_mem;
+ 'sort/work buffer size' => array('CACHE',
+ "select setting::integer*1024 from pg_settings where name='sort_mem' or name = 'work_mem' order by name",
+ 'Size of sort buffer (per query)' ),
+ 'Connections',
+ 'current connections' => array('SESS',
+ 'select count(*) from pg_stat_activity',
+ ''),
+ 'max connections' => array('SESS',
+ "select setting from pg_settings where name='max_connections'",
+ ''),
+ 'Parameters',
+ 'rollback buffers' => array('COST',
+ "select setting from pg_settings where name='wal_buffers'",
+ 'WAL buffers'),
+ 'random page cost' => array('COST',
+ "select setting from pg_settings where name='random_page_cost'",
+ 'Cost of doing a seek (default=4). See <a href=http://www.varlena.com/GeneralBits/Tidbits/perf.html#less>random_page_cost</a>'),
+ false
+ );
+
+ function __construct(&$conn)
+ {
+ $this->conn = $conn;
+ }
+
+ var $optimizeTableLow = 'VACUUM %s';
+ var $optimizeTableHigh = 'VACUUM ANALYZE %s';
+
+/**
+ * @see adodb_perf#optimizeTable
+ */
+
+ function optimizeTable($table, $mode = ADODB_OPT_LOW)
+ {
+ if(! is_string($table)) return false;
+
+ $conn = $this->conn;
+ if (! $conn) return false;
+
+ $sql = '';
+ switch($mode) {
+ case ADODB_OPT_LOW : $sql = $this->optimizeTableLow; break;
+ case ADODB_OPT_HIGH: $sql = $this->optimizeTableHigh; break;
+ default :
+ {
+ ADOConnection::outp(sprintf("<p>%s: '%s' using of undefined mode '%s'</p>", __CLASS__, 'optimizeTable', $mode));
+ return false;
+ }
+ }
+ $sql = sprintf($sql, $table);
+
+ return $conn->Execute($sql) !== false;
+ }
+
+ function Explain($sql,$partial=false)
+ {
+ $save = $this->conn->LogSQL(false);
+
+ if ($partial) {
+ $sqlq = $this->conn->qstr($sql.'%');
+ $arr = $this->conn->GetArray("select distinct distinct sql1 from adodb_logsql where sql1 like $sqlq");
+ if ($arr) {
+ foreach($arr as $row) {
+ $sql = reset($row);
+ if (crc32($sql) == $partial) break;
+ }
+ }
+ }
+ $sql = str_replace('?',"''",$sql);
+ $s = '<p><b>Explain</b>: '.htmlspecialchars($sql).'</p>';
+ $rs = $this->conn->Execute('EXPLAIN '.$sql);
+ $this->conn->LogSQL($save);
+ $s .= '<pre>';
+ if ($rs)
+ while (!$rs->EOF) {
+ $s .= reset($rs->fields)."\n";
+ $rs->MoveNext();
+ }
+ $s .= '</pre>';
+ $s .= $this->Tracer($sql,$partial);
+ return $s;
+ }
+}
diff --git a/vendor/adodb/adodb-php/pivottable.inc.php b/vendor/adodb/adodb-php/pivottable.inc.php
new file mode 100644
index 0000000..b589e04
--- /dev/null
+++ b/vendor/adodb/adodb-php/pivottable.inc.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+*/
+
+/*
+ * Concept from daniel.lucazeau@ajornet.com.
+ *
+ * @param db Adodb database connection
+ * @param tables List of tables to join
+ * @rowfields List of fields to display on each row
+ * @colfield Pivot field to slice and display in columns, if we want to calculate
+ * ranges, we pass in an array (see example2)
+ * @where Where clause. Optional.
+ * @aggfield This is the field to sum. Optional.
+ * Since 2.3.1, if you can use your own aggregate function
+ * instead of SUM, eg. $aggfield = 'fieldname'; $aggfn = 'AVG';
+ * @sumlabel Prefix to display in sum columns. Optional.
+ * @aggfn Aggregate function to use (could be AVG, SUM, COUNT)
+ * @showcount Show count of records
+ *
+ * @returns Sql generated
+ */
+
+ function PivotTableSQL(&$db,$tables,$rowfields,$colfield, $where=false,
+ $aggfield = false,$sumlabel='Sum ',$aggfn ='SUM', $showcount = true)
+ {
+ if ($aggfield) $hidecnt = true;
+ else $hidecnt = false;
+
+ $iif = strpos($db->databaseType,'access') !== false;
+ // note - vfp 6 still doesn' work even with IIF enabled || $db->databaseType == 'vfp';
+
+ //$hidecnt = false;
+
+ if ($where) $where = "\nWHERE $where";
+ if (!is_array($colfield)) $colarr = $db->GetCol("select distinct $colfield from $tables $where order by 1");
+ if (!$aggfield) $hidecnt = false;
+
+ $sel = "$rowfields, ";
+ if (is_array($colfield)) {
+ foreach ($colfield as $k => $v) {
+ $k = trim($k);
+ if (!$hidecnt) {
+ $sel .= $iif ?
+ "\n\t$aggfn(IIF($v,1,0)) AS \"$k\", "
+ :
+ "\n\t$aggfn(CASE WHEN $v THEN 1 ELSE 0 END) AS \"$k\", ";
+ }
+ if ($aggfield) {
+ $sel .= $iif ?
+ "\n\t$aggfn(IIF($v,$aggfield,0)) AS \"$sumlabel$k\", "
+ :
+ "\n\t$aggfn(CASE WHEN $v THEN $aggfield ELSE 0 END) AS \"$sumlabel$k\", ";
+ }
+ }
+ } else {
+ foreach ($colarr as $v) {
+ if (!is_numeric($v)) $vq = $db->qstr($v);
+ else $vq = $v;
+ $v = trim($v);
+ if (strlen($v) == 0 ) $v = 'null';
+ if (!$hidecnt) {
+ $sel .= $iif ?
+ "\n\t$aggfn(IIF($colfield=$vq,1,0)) AS \"$v\", "
+ :
+ "\n\t$aggfn(CASE WHEN $colfield=$vq THEN 1 ELSE 0 END) AS \"$v\", ";
+ }
+ if ($aggfield) {
+ if ($hidecnt) $label = $v;
+ else $label = "{$v}_$aggfield";
+ $sel .= $iif ?
+ "\n\t$aggfn(IIF($colfield=$vq,$aggfield,0)) AS \"$label\", "
+ :
+ "\n\t$aggfn(CASE WHEN $colfield=$vq THEN $aggfield ELSE 0 END) AS \"$label\", ";
+ }
+ }
+ }
+ if ($aggfield && $aggfield != '1'){
+ $agg = "$aggfn($aggfield)";
+ $sel .= "\n\t$agg as \"$sumlabel$aggfield\", ";
+ }
+
+ if ($showcount)
+ $sel .= "\n\tSUM(1) as Total";
+ else
+ $sel = substr($sel,0,strlen($sel)-2);
+
+
+ // Strip aliases
+ $rowfields = preg_replace('/ AS (\w+)/i', '', $rowfields);
+
+ $sql = "SELECT $sel \nFROM $tables $where \nGROUP BY $rowfields";
+
+ return $sql;
+ }
+
+/* EXAMPLES USING MS NORTHWIND DATABASE */
+if (0) {
+
+# example1
+#
+# Query the main "product" table
+# Set the rows to CompanyName and QuantityPerUnit
+# and the columns to the Categories
+# and define the joins to link to lookup tables
+# "categories" and "suppliers"
+#
+
+ $sql = PivotTableSQL(
+ $gDB, # adodb connection
+ 'products p ,categories c ,suppliers s', # tables
+ 'CompanyName,QuantityPerUnit', # row fields
+ 'CategoryName', # column fields
+ 'p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID' # joins/where
+);
+ print "<pre>$sql";
+ $rs = $gDB->Execute($sql);
+ rs2html($rs);
+
+/*
+Generated SQL:
+
+SELECT CompanyName,QuantityPerUnit,
+ SUM(CASE WHEN CategoryName='Beverages' THEN 1 ELSE 0 END) AS "Beverages",
+ SUM(CASE WHEN CategoryName='Condiments' THEN 1 ELSE 0 END) AS "Condiments",
+ SUM(CASE WHEN CategoryName='Confections' THEN 1 ELSE 0 END) AS "Confections",
+ SUM(CASE WHEN CategoryName='Dairy Products' THEN 1 ELSE 0 END) AS "Dairy Products",
+ SUM(CASE WHEN CategoryName='Grains/Cereals' THEN 1 ELSE 0 END) AS "Grains/Cereals",
+ SUM(CASE WHEN CategoryName='Meat/Poultry' THEN 1 ELSE 0 END) AS "Meat/Poultry",
+ SUM(CASE WHEN CategoryName='Produce' THEN 1 ELSE 0 END) AS "Produce",
+ SUM(CASE WHEN CategoryName='Seafood' THEN 1 ELSE 0 END) AS "Seafood",
+ SUM(1) as Total
+FROM products p ,categories c ,suppliers s WHERE p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID
+GROUP BY CompanyName,QuantityPerUnit
+*/
+//=====================================================================
+
+# example2
+#
+# Query the main "product" table
+# Set the rows to CompanyName and QuantityPerUnit
+# and the columns to the UnitsInStock for diiferent ranges
+# and define the joins to link to lookup tables
+# "categories" and "suppliers"
+#
+ $sql = PivotTableSQL(
+ $gDB, # adodb connection
+ 'products p ,categories c ,suppliers s', # tables
+ 'CompanyName,QuantityPerUnit', # row fields
+ # column ranges
+array(
+' 0 ' => 'UnitsInStock <= 0',
+"1 to 5" => '0 < UnitsInStock and UnitsInStock <= 5',
+"6 to 10" => '5 < UnitsInStock and UnitsInStock <= 10',
+"11 to 15" => '10 < UnitsInStock and UnitsInStock <= 15',
+"16+" =>'15 < UnitsInStock'
+),
+ ' p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID', # joins/where
+ 'UnitsInStock', # sum this field
+ 'Sum' # sum label prefix
+);
+ print "<pre>$sql";
+ $rs = $gDB->Execute($sql);
+ rs2html($rs);
+ /*
+ Generated SQL:
+
+SELECT CompanyName,QuantityPerUnit,
+ SUM(CASE WHEN UnitsInStock <= 0 THEN UnitsInStock ELSE 0 END) AS "Sum 0 ",
+ SUM(CASE WHEN 0 < UnitsInStock and UnitsInStock <= 5 THEN UnitsInStock ELSE 0 END) AS "Sum 1 to 5",
+ SUM(CASE WHEN 5 < UnitsInStock and UnitsInStock <= 10 THEN UnitsInStock ELSE 0 END) AS "Sum 6 to 10",
+ SUM(CASE WHEN 10 < UnitsInStock and UnitsInStock <= 15 THEN UnitsInStock ELSE 0 END) AS "Sum 11 to 15",
+ SUM(CASE WHEN 15 < UnitsInStock THEN UnitsInStock ELSE 0 END) AS "Sum 16+",
+ SUM(UnitsInStock) AS "Sum UnitsInStock",
+ SUM(1) as Total
+FROM products p ,categories c ,suppliers s WHERE p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID
+GROUP BY CompanyName,QuantityPerUnit
+ */
+}
diff --git a/vendor/adodb/adodb-php/replicate/adodb-replicate.inc.php b/vendor/adodb/adodb-php/replicate/adodb-replicate.inc.php
new file mode 100644
index 0000000..f13e4af
--- /dev/null
+++ b/vendor/adodb/adodb-php/replicate/adodb-replicate.inc.php
@@ -0,0 +1,1180 @@
+<?php
+
+define('ADODB_REPLICATE',1.2);
+
+include_once(ADODB_DIR.'/adodb-datadict.inc.php');
+
+/*
+1.2 9 June 2009
+Minor patches
+
+1.1 8 June 2009
+Added $lastUpdateFld to replicatedata
+Added $rep->compat. If compat set to 1.0, then $lastUpdateFld not used during MergeData.
+
+1.0 Apr 2009
+Added support for MFFA
+
+0.9 ? 2008
+First release
+
+
+ Note: this code assumes that comments such as / * * / ar`e allowed which works with:
+ Note: this code assumes that comments such as / * * / are allowed which works with:
+ mssql, postgresql, oracle, mssql
+
+ Replication engine to
+ - copy table structures and data from different databases (e.g. mysql to oracle)
+ for replication purposes
+ - generate CREATE TABLE, CREATE INDEX, INSERT ... for installation scripts
+
+ Table Structure copying includes
+ - fields and limited subset of types
+ - optional default values
+ - indexes
+ - but not constraints
+
+
+ Two modes of data copy:
+
+ ReplicateData
+ - Copy from src to dest, with update of status of copy back to src,
+ with configurable src SELECT where clause
+
+ MergeData
+ - Copy from src to dest based on last mod date field and/or copied flag field
+
+ Default settings are
+ - do not execute, generate sql ($rep->execute = false)
+ - do not delete records in dest table first ($rep->deleteFirst = false).
+ if $rep->deleteFirst is true and primary keys are defined,
+ then no deletion will occur unless *INSERTONLY* is defined in pkey array
+ - only commit once at the end of every ReplicateData ($rep->commitReplicate = true)
+ - do not autocommit every x records processed ($rep->commitRecs = -1)
+ - even if error occurs on one record, continue copying remaining records ($rep->neverAbort = true)
+ - debugging turned off ($rep->debug = false)
+*/
+
+class ADODB_Replicate {
+ var $connSrc;
+ var $connDest;
+
+ var $connSrc2 = false;
+ var $connDest2 = false;
+ var $ddSrc;
+ var $ddDest;
+
+ var $execute = false;
+ var $debug = false;
+ var $deleteFirst = false;
+ var $commitReplicate = true; // commit at end of replicatedata
+ var $commitRecs = -1; // only commit at end of ReplicateData()
+
+ var $selFilter = false;
+ var $fieldFilter = false;
+ var $indexFilter = false;
+ var $updateFilter = false;
+ var $insertFilter = false;
+ var $updateSrcFn = false;
+
+ var $limitRecs = false;
+
+ var $neverAbort = true;
+ var $copyTableDefaults = false; // turn off because functions defined as defaults will not work when copied
+ var $errHandler = false; // name of error handler function, if used.
+ var $htmlSpecialChars = true; // if execute false, then output with htmlspecialchars enabled.
+ // Will autoconfigure itself. No need to modify
+
+ var $trgSuffix = '_mrgTr';
+ var $idxSuffix = '_mrgidx';
+ var $trLogic = '1 = 1';
+ var $datesAreTimeStamps = false;
+
+ var $oracleSequence = false;
+ var $readUncommitted = false; // read without obeying shared locks for fast select (mssql)
+
+ var $compat = false;
+ // connSrc2 and connDest2 are only required if the db driver
+ // does not allow updates back to src db in first connection (the select connection),
+ // so we need 2nd connection
+ function __construct($connSrc, $connDest, $connSrc2=false, $connDest2=false)
+ {
+
+ if (strpos($connSrc->databaseType,'odbtp') !== false) {
+ $connSrc->_bindInputArray = false; # bug in odbtp, binding fails
+ }
+
+ if (strpos($connDest->databaseType,'odbtp') !== false) {
+ $connDest->_bindInputArray = false; # bug in odbtp, binding fails
+ }
+
+ $this->connSrc = $connSrc;
+ $this->connDest = $connDest;
+
+ $this->connSrc2 = ($connSrc2) ? $connSrc2 : $connSrc;
+ $this->connDest2 = ($connDest2) ? $connDest2 : $connDest;
+
+ $this->ddSrc = NewDataDictionary($connSrc);
+ $this->ddDest = NewDataDictionary($connDest);
+ $this->htmlSpecialChars = isset($_SERVER['HTTP_HOST']);
+ }
+
+ function ExecSQL($sql)
+ {
+ if (!is_array($sql)) $sql[] = $sql;
+
+ $ret = true;
+ foreach($sql as $s)
+ if (!$this->execute) echo "<pre>",$s.";\n</pre>";
+ else {
+ $ok = $this->connDest->Execute($s);
+ if (!$ok)
+ if ($this->neverAbort) $ret = false;
+ else return false;
+ }
+
+ return $ret;
+ }
+
+ /*
+ We assume replication between $table and $desttable only works if the field names and types match for both tables.
+
+ Also $table and desttable can have different names.
+ */
+
+ function CopyTableStruct($table,$desttable='')
+ {
+ $sql = $this->CopyTableStructSQL($table,$desttable);
+ if (empty($sql)) return false;
+ return $this->ExecSQL($sql);
+ }
+
+ function RunFieldFilter(&$fld, $mode = '')
+ {
+ if ($this->fieldFilter) {
+ $fn = $this->fieldFilter;
+ return $fn($fld, $mode);
+ } else
+ return $fld;
+ }
+
+ function RunUpdateFilter($table, $fld, $val)
+ {
+ if ($this->updateFilter) {
+ $fn = $this->updateFilter;
+ return $fn($table, $fld, $val);
+ } else
+ return $val;
+ }
+
+ function RunInsertFilter($table, $fld, &$val)
+ {
+ if ($this->insertFilter) {
+ $fn = $this->insertFilter;
+ return $fn($table, $fld, $val);
+ } else
+ return $fld;
+ }
+
+ /*
+ $mode = INS or UPD
+
+ The lastUpdateFld holds the field that counts the number of updates or the date of last mod. This ensures that
+ if the rec was modified after replicatedata retrieves the data but before we update back the src record,
+ we don't set the copiedflag='Y' yet.
+ */
+ function RunUpdateSrcFn($srcdb, $table, $fldoffsets, $row, $where, $mode, $dest_insertid=null, $lastUpdateFld='')
+ {
+ if (!$this->updateSrcFn) return;
+
+ $bindarr = array();
+ foreach($fldoffsets as $k) {
+ $bindarr[$k] = $row[$k];
+ }
+ $last = sizeof($row);
+
+ if ($lastUpdateFld && $row[$last-1]) {
+ $ds = $row[$last-1];
+ if (strpos($ds,':') !== false) $s = $srcdb->DBTimeStamp($ds);
+ else $s = $srcdb->qstr($ds);
+ $where = "WHERE $lastUpdateFld = $s and $where";
+ } else
+ $where = "WHERE $where";
+ $fn = $this->updateSrcFn;
+ if (is_array($fn)) {
+ if (sizeof($fn) == 1) $set = reset($fn);
+ else $set = @$fn[$mode];
+ if ($set) {
+
+ if (strlen($dest_insertid) == 0) $dest_insertid = 'null';
+ $set = str_replace('$INSERT_ID',$dest_insertid,$set);
+
+ $sql = "UPDATE $table SET $set $where ";
+ $ok = $srcdb->Execute($sql,$bindarr);
+ if (!$ok) {
+ echo $srcdb->ErrorMsg(),"<br>\n";
+ die();
+ }
+ }
+ } else $fn($srcdb, $table, $row, $where, $bindarr, $mode, $dest_insertid);
+
+ }
+
+ function CopyTableStructSQL($table, $desttable='',$dropdest =false)
+ {
+ if (!$desttable) {
+ $desttable = $table;
+ $prefixidx = '';
+ } else
+ $prefixidx = $desttable;
+
+ $conn = $this->connSrc;
+ $types = $conn->MetaColumns($table);
+ if (!$types) {
+ echo "$table does not exist in source db<br>\n";
+ return array();
+ }
+ if (!$dropdest && $this->connDest->MetaColumns($desttable)) {
+ echo "$desttable already exists in dest db<br>\n";
+ return array();
+ }
+ if ($this->debug) var_dump($types);
+ $sa = array();
+ $idxcols = array();
+
+ foreach($types as $name => $t) {
+ $s = '';
+ $mt = $this->ddSrc->MetaType($t->type);
+ $len = $t->max_length;
+ $fldname = $this->RunFieldFilter($t->name,'TABLE');
+ if (!$fldname) continue;
+
+ $s .= $fldname . ' '.$mt;
+ if (isset($t->scale)) $precision = '.'.$t->scale;
+ else $precision = '';
+ if ($mt == 'C' or $mt == 'X') $s .= "($len)";
+ else if ($mt == 'N' && $precision) $s .= "($len$precision)";
+
+ if ($mt == 'R') $idxcols[] = $fldname;
+
+ if ($this->copyTableDefaults) {
+ if (isset($t->default_value)) {
+ $v = $t->default_value;
+ if ($mt == 'C' or $mt == 'X') $v = $this->connDest->qstr($v); // might not work as this could be function
+ $s .= ' DEFAULT '.$v;
+ }
+ }
+
+ $sa[] = $s;
+ }
+
+ $s = implode(",\n",$sa);
+
+ // dump adodb intermediate data dictionary format
+ if ($this->debug) echo '<pre>'.$s.'</pre>';
+
+ $sqla = $this->ddDest->CreateTableSQL($desttable,$s);
+
+ /*
+ if ($idxcols) {
+ $idxoptions = array('UNIQUE'=>1);
+ $sqla2 = $this->ddDest->_IndexSQL($table.'_'.$fldname.'_SERIAL', $desttable, $idxcols,$idxoptions);
+ $sqla = array_merge($sqla,$sqla2);
+ }*/
+
+ $idxs = $conn->MetaIndexes($table);
+ if ($idxs)
+ foreach($idxs as $name => $iarr) {
+ $idxoptions = array();
+ $fldnames = array();
+
+ if(!empty($iarr['unique'])) {
+ $idxoptions['UNIQUE'] = 1;
+ }
+
+ foreach($iarr['columns'] as $fld) {
+ $fldnames[] = $this->RunFieldFilter($fld,'TABLE');
+ }
+
+ $idxname = $prefixidx.str_replace($table,$desttable,$name);
+
+ if (!empty($this->indexFilter)) {
+ $fn = $this->indexFilter;
+ $idxname = $fn($desttable,$idxname,$fldnames,$idxoptions);
+ }
+ $sqla2 = $this->ddDest->_IndexSQL($idxname, $desttable, $fldnames,$idxoptions);
+ $sqla = array_merge($sqla,$sqla2);
+ }
+
+ return $sqla;
+ }
+
+ function _clearcache()
+ {
+
+ }
+
+ function _concat($v)
+ {
+ return $this->connDest->concat("' ","chr(".ord($v).")","'");
+ }
+
+ function fixupbinary($v)
+ {
+ return str_replace(
+ array("\r","\n"),
+ array($this->_concat("\r"),$this->_concat("\n")),
+ $v );
+ }
+
+ function SwapDBs()
+ {
+ $o = $this->connSrc;
+ $this->connSrc = $this->connDest;
+ $this->connDest = $o;
+
+
+ $o = $this->connSrc2;
+ $this->connSrc2 = $this->connDest2;
+ $this->connDest2 = $o;
+
+ $o = $this->ddSrc;
+ $this->ddSrc = $this->ddDest;
+ $this->ddDest = $o;
+ }
+
+ /*
+ // if no uniqflds defined, then all desttable recs will be deleted before insert
+ // $where clause must include the WHERE word if used
+ // if $this->commitRecs is set to a +ve value, then it will autocommit every $this->commitRecs records
+ // -- this should never be done with 7x24 db's
+
+ Returns an array:
+ $arr[0] = true if no error, false if error
+ $arr[1] = number of recs processed
+ $arr[2] = number of successful inserts
+ $arr[3] = number of successful updates
+
+ ReplicateData() params:
+
+ $table = src table name
+ $desttable = dest table name, leave blank to use src table name
+ $uniqflds = array() = an array. If set, then inserts and updates will occur. eg. array('PK1', 'PK2');
+ To prevent updates to desttable (allow only to src table), add '*INSERTONLY*' or '*ONLYINSERT*' to array.
+
+ Sometimes you are replicating a src table with an autoinc primary key.
+ You sometimes create recs in the dest table. The dest table has to retrieve the
+ src table's autoinc key (stored in a 2nd field) so you can match the two tables.
+
+ To define this, and the uniqflds contains nested arrays. Copying from autoinc table to other table:
+ array(array($destpkey), array($destfld_holds_src_autoinc_pkey))
+
+ Copying from normal table to autoinc table:
+ array(array($destpkey), array(), array($srcfld_holds_dest_autoinc_pkey))
+
+ $where = where clause for SELECT from $table $where. Include the WHERE reserved word in beginning.
+ You can put ORDER BY at the end also
+ $ignoreflds = array(), list of fields to ignore. e.g. array('FLD1',FLD2');
+ $dstCopyDateFld = date field on $desttable to update with current date
+ $extraflds allows you to add additional flds to insert/update. Format
+ array(fldname => $fldval)
+ $fldval itself can be an array or a string. If an array, then
+ $extraflds = array($fldname => array($insertval, $updateval))
+
+ Thus we have the following behaviours:
+
+ a. Delete all data in $desttable then insert from src $table
+
+ $rep->execute = true;
+ $rep->ReplicateData($table, $desttable)
+
+ b. Update $desttable if record exists (based on $uniqflds), otherwise insert.
+
+ $rep->execute = true;
+ $rep->ReplicateData($table, $desttable, $array($pkey1, $pkey2))
+
+ c. Select from src $table all data modified since a date. Then update $desttable
+ if record exists (based on $uniqflds), otherwise insert
+
+ $rep->execute = true;
+ $rep->ReplicateData($table, $desttable, array($pkey1, $pkey2), "WHERE update_datetime_fld > $LAST_REFRESH")
+
+ d. Insert all records into $desttable modified after a certain id (or time) in src $table:
+
+ $rep->execute = true;
+ $rep->ReplicateData($table, $desttable, false, "WHERE id_fld > $LAST_ID_SAVED", true);
+
+
+ For (a) to (d), returns array: array($boolean_ok_fail, $no_recs_selected_from_src_db, $no_recs_inserted, $no_recs_updated);
+
+ e. Generate sample SQL:
+
+ $rep->execute = false;
+ $rep->ReplicateData(....);
+
+ This returns $array, which contains:
+
+ $array['SEL'] = select stmt from src db
+ $array['UPD'] = update stmt to dest db
+ $array['INS'] = insert stmt to dest db
+
+
+ Error-handling
+ ==============
+ Default is never abort if error occurs. You can set $rep->neverAbort = false; to force replication to abort if an error occurs.
+
+
+ Value Filtering
+ ========
+ Sometimes you might need to modify/massage the data before the code works. Assume that the value used for True and False is
+ 'T' and 'F' in src DB, but is 'Y' and 'N' in dest DB for field[2] in select stmt. You can do this by
+
+ $rep->filterSelect = 'filter';
+ $rep->ReplicateData(...);
+
+ function filter($table,& $fields, $deleteFirst)
+ {
+ if ($table == 'SOMETABLE') {
+ if ($fields[2] == 'T') $fields[2] = 'Y';
+ else if ($fields[2] == 'F') $fields[2] = 'N';
+ }
+ }
+
+ We pass in $deleteFirst as that determines the order of the fields (which are numeric-based):
+ TRUE: the order of fields matches the src table order
+ FALSE: the order of fields is all non-primary key fields first, followed by primary key fields. This is because it needs
+ to match the UPDATE statement, which is UPDATE $table SET f2 = ?, f3 = ? ... WHERE f1 = ?
+
+ Name Filtering
+ =========
+ Sometimes field names that are legal in one RDBMS can be illegal in another.
+ We allow you to handle this using a field filter.
+ Also if you don't want to replicate certain fields, just return false.
+
+ $rep->fieldFilter = 'ffilter';
+
+ function ffilter(&$fld,$mode)
+ {
+ $uf = strtoupper($fld);
+ switch($uf) {
+ case 'GROUP':
+ if ($mode == 'SELECT') $fld = '"Group"';
+ return 'GroupFld';
+
+ case 'PRIVATEFLD': # do not replicate
+ return false;
+ }
+ return $fld;
+ }
+
+
+ UPDATE FILTERING
+ ================
+ Sometimes, when we want to update
+ UPDATE table SET fld = val WHERE ....
+
+ we want to modify val. To do so, define
+
+ $rep->updateFilter = 'ufilter';
+
+ function ufilter($table, $fld, $val)
+ {
+ return "nvl($fld, $val)";
+ }
+
+
+ Sending back audit info back to src Table
+ =========================================
+
+ Use $rep->updateSrcFn. This can be an array of strings, or the name of a php function to call.
+
+ If an array of strings is defined, then it will perform an update statement...
+
+ UPDATE srctable SET $string WHERE ....
+
+ With $string set to the array you define. If a new record was inserted into desttable, then the
+ 'INS' string is used ($INSERT_ID will be replaced with the real INSERT_ID, if any),
+ and if an update then use the 'UPD' string.
+
+ array(
+ 'INS' => 'insertid = $INSERT_ID, copieddate=getdate(), copied = 1',
+ 'UPD' => 'copieddate=getdate(), copied = 1'
+ )
+
+ If a single string array is defined, then it will be used for both insert and update.
+ array('copieddate=getdate(), copied = 1')
+
+ Note that the where clause is automatically defined by the system.
+
+ If $rep->updateSrcFn is a PHP function name, then it will be called with the following params:
+
+ $fn($srcConnection, $tableName, $row, $where, $bindarr, $mode, $dest_insertid)
+
+ $srcConnection - source db connection
+ $tableName - source tablename
+ $row - array holding records updated into dest
+ $where - where clause to be used (uses bind vars)
+ $bindarr - array holding bind variables for where clause
+ $mode - INS or UPD
+ $dest_insertid - when mode=INS, then the insert_id is stored here.
+
+
+ oracle mssql
+ ---> insert
+ mssqlid <--- insert_id
+ ----> update with mssqlid
+ <---- update with mssqlid
+
+
+ TODO: add src pkey and dest pkey for updates. Also sql stmt needs to be tuned, so dest pkey, src pkey
+ */
+
+
+ function ReplicateData($table, $desttable = '', $uniqflds = array(), $where = '',$ignore_flds = array(),
+ $dstCopyDateFld='', $extraflds = array(), $lastUpdateFld = '')
+ {
+ if (is_array($where)) {
+ $wheresrc = $where[0];
+ $wheredest = $where[1];
+ } else {
+ $wheresrc = $wheredest = $where;
+ }
+ $dstCopyDateName = $dstCopyDateFld;
+ $dstCopyDateFld = strtoupper($dstCopyDateFld);
+
+ $this->_clearcache();
+ if (is_string($uniqflds) && strlen($uniqflds)) $uniqflds = array($uniqflds);
+ if (!$desttable) $desttable = $table;
+
+ $uniq = array();
+ if ($uniqflds) {
+ if (is_array(reset($uniqflds))) {
+ /*
+ primary key of src and dest tables differ. This means when we perform the select stmts
+ we retrieve both keys. Then any insert statement will have to ignore one array element.
+ Any update statement will need to use a different where clause
+ */
+ $destuniqflds = $uniqflds[0];
+ if (sizeof($uniqflds)>1 && $uniqflds[1]) // srckey field name in dest table
+ $srcuniqflds = $uniqflds[1];
+ else
+ $srcuniqflds = array();
+
+ if (sizeof($uniqflds)>2)
+ $srcPKDest = reset($uniqflds[2]);
+
+ } else {
+ $destuniqflds = $uniqflds;
+ $srcuniqflds = array();
+ }
+ $onlyInsert = false;
+ foreach($destuniqflds as $k => $u) {
+ if ($u == '*INSERTONLY*' || $u == '*ONLYINSERT*') {
+ $onlyInsert = true;
+ continue;
+ }
+ $uniq[strtoupper($u)] = $k;
+ }
+ $deleteFirst = $this->deleteFirst;
+ } else {
+ $deleteFirst = true;
+ }
+
+ if ($deleteFirst) $onlyInsert = true;
+
+ if ($ignore_flds) {
+ foreach($ignore_flds as $u) {
+ $ignoreflds[strtoupper($u)] = 1;
+ }
+ } else
+ $ignoreflds = array();
+
+ $src = $this->connSrc;
+ $dest = $this->connDest;
+ $src2 = $this->connSrc2;
+
+ $dest->noNullStrings = false;
+ $src->noNullStrings = false;
+ $src2->noNullStrings = false;
+
+ if ($src === $dest) $this->execute = false;
+
+ $types = $src->MetaColumns($table);
+ if (!$types) {
+ echo "Source $table does not exist<br>\n";
+ return array();
+ }
+ $dtypes = $dest->MetaColumns($desttable);
+ if (!$dtypes) {
+ echo "Destination $desttable does not exist<br>\n";
+ return array();
+ }
+ $sa = array();
+ $selflds = array();
+ $wheref = array();
+ $wheres = array();
+ $srcwheref = array();
+ $fldoffsets = array();
+ $k = 0;
+ foreach($types as $name => $t) {
+ $name2 = strtoupper($this->RunFieldFilter($name,'SELECT'));
+ // handle quotes
+ if ($name2 && $name2[0] == '"' && $name2[strlen($name2)-1] == '"') $name22 = substr($name2,1,strlen($name2)-2);
+ elseif ($name2 && $name2[0] == '`' && $name2[strlen($name2)-1] == '`') $name22 = substr($name2,1,strlen($name2)-2);
+ else $name22 = $name2;
+
+ //else $name22 = $name2; // this causes problem for quotes strip above
+
+ if (!isset($dtypes[($name22)]) || !$name2) {
+ if ($this->debug) echo " Skipping $name ==> $name2 as not in destination $desttable<br>";
+ continue;
+ }
+
+ if ($name2 == $dstCopyDateFld) {
+ $dstCopyDateName = $t->name;
+ continue;
+ }
+
+ $fld = $t->name;
+ $fldval = $t->name;
+ $mt = $src->MetaType($t->type);
+ if ($this->datesAreTimeStamps && $mt == 'D') $mt = 'T';
+ if ($mt == 'D') $fldval = $dest->DBDate($fldval);
+ elseif ($mt == 'T') $fldval = $dest->DBTimeStamp($fldval);
+ $ufld = strtoupper($fld);
+
+ if (isset($ignoreflds[($name2)]) && !isset($uniq[$ufld])) {
+ continue;
+ }
+
+ if ($this->debug) echo " field=$fld type=$mt fldval=$fldval<br>";
+
+ if (!isset($uniq[$ufld])) {
+
+ $selfld = $fld;
+ $fld = $this->RunFieldFilter($selfld,'SELECT');
+ $selflds[] = $selfld;
+
+ $p = $dest->Param($k);
+
+ if ($mt == 'D') $p = $dest->DBDate($p, true);
+ else if ($mt == 'T') $p = $dest->DBTimeStamp($p, true);
+
+ # UPDATES
+ $sets[] = "$fld = ".$this->RunUpdateFilter($desttable, $fld, $p);
+
+ # INSERTS
+ $insflds[] = $this->RunInsertFilter($desttable,$fld, $p); $params[] = $p;
+ $k++;
+ } else {
+ $fld = $this->RunFieldFilter($fld);
+ $wheref[] = $fld;
+ if (!empty($srcuniqflds)) $srcwheref[] = $srcuniqflds[$uniq[$ufld]];
+ if ($mt == 'C') { # normally we don't include the primary key in the insert if it is numeric, but ok if varchar
+ $insertpkey = true;
+ }
+ }
+ }
+
+
+ foreach($extraflds as $fld => $evals) {
+ if (!is_array($evals)) $evals = array($evals, $evals);
+ $insflds[] = $this->RunInsertFilter($desttable,$fld, $p); $params[] = $evals[0];
+ $sets[] = "$fld = ".$evals[1];
+ }
+
+ if ($dstCopyDateFld) {
+ $sets[] = "$dstCopyDateName = ".$dest->sysTimeStamp;
+ $insflds[] = $this->RunInsertFilter($desttable,$dstCopyDateName, $p); $params[] = $dest->sysTimeStamp;
+ }
+
+
+ if (!empty($srcPKDest)) {
+ $selflds[] = $srcPKDest;
+ $fldoffsets = array($k+1);
+ }
+
+ foreach($wheref as $uu => $fld) {
+
+ $p = $dest->Param($k);
+ $sp = $src->Param($k);
+ if (!empty($srcuniqflds)) {
+ if ($uu > 1) die("Only one primary key for srcuniqflds allowed currently");
+ $destsrckey = reset($srcuniqflds);
+ $wheres[] = reset($srcuniqflds).' = '.$p;
+
+ $insflds[] = $this->RunInsertFilter($desttable,$destsrckey, $p);
+ $params[] = $p;
+ } else {
+ $wheres[] = $fld.' = '.$p;
+ if (!isset($ignoreflds[strtoupper($fld)]) || !empty($insertpkey)) {
+ $insflds[] = $this->RunInsertFilter($desttable,$fld, $p);
+ $params[] = $p;
+ }
+ }
+
+ $selflds[] = $fld;
+ $srcwheres[] = $fld.' = '.$sp;
+ $fldoffsets[] = $k;
+
+ $k++;
+ }
+
+ if (!empty($srcPKDest)) {
+ $fldoffsets = array($k);
+ $srcwheres = array($fld.'='.$src->Param($k));
+ $k++;
+ }
+
+ if ($lastUpdateFld) {
+ $selflds[] = $lastUpdateFld;
+ } else
+ $selflds[] = 'null as Z55_DUMMY_LA5TUPD';
+
+ $insfldss = implode(', ', $insflds);
+ $fldss = implode(', ', $selflds);
+ $setss = implode(', ', $sets);
+ $paramss = implode(', ', $params);
+ $wheress = implode(' AND ', $wheres);
+ if (isset($srcwheres))
+ $srcwheress = implode(' AND ',$srcwheres);
+
+
+ $seltable = $table;
+ if ($this->readUncommitted && strpos($src->databaseType,'mssql')) $seltable .= ' with (NOLOCK)';
+
+ $sa['SEL'] = "SELECT $fldss FROM $seltable $wheresrc";
+ $sa['INS'] = "INSERT INTO $desttable ($insfldss) VALUES ($paramss) /**INS**/";
+ $sa['UPD'] = "UPDATE $desttable SET $setss WHERE $wheress /**UPD**/";
+
+
+
+ $DB1 = "/* <font color=green> Source DB - sample sql in case you need to adapt code\n\n";
+ $DB2 = "/* <font color=green> Dest DB - sample sql in case you need to adapt code\n\n";
+
+ if (!$this->execute) echo '/*<style>
+pre {
+white-space: pre-wrap; /* css-3 */
+white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
+white-space: -pre-wrap; /* Opera 4-6 */
+white-space: -o-pre-wrap; /* Opera 7 */
+word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
+</style><pre>*/
+';
+ if ($deleteFirst && $this->deleteFirst) {
+ $where = preg_replace('/[ \n\r\t]+order[ \n\r\t]+by.*$/i', '', $where);
+ $sql = "DELETE FROM $desttable $wheredest\n";
+ if (!$this->execute) echo $DB2,'</font>*/',$sql,"\n";
+ else $dest->Execute($sql);
+ }
+
+ global $ADODB_COUNTRECS;
+ $err = false;
+ $savemode = $src->setFetchMode(ADODB_FETCH_NUM);
+ $ADODB_COUNTRECS = false;
+
+ if (!$this->execute) {
+ echo $DB1,$sa['SEL'],"</font>\n*/\n\n";
+ echo $DB2,$sa['INS'],"</font>\n*/\n\n";
+ $suffix = ($onlyInsert) ? ' PRIMKEY=?' : '';
+ echo $DB2,$sa['UPD'],"$suffix</font>\n*/\n\n";
+
+ $rs = $src->Execute($sa['SEL']);
+ $cnt = 1;
+ $upd = 0;
+ $ins = 0;
+
+ $sqlarr = explode('?',$sa['INS']);
+ $nparams = sizeof($sqlarr)-1;
+
+ $useQmark = $dest && ($dest->dataProvider != 'oci8');
+
+ while ($rs && !$rs->EOF) {
+ if ($useQmark) {
+ $sql = ''; $i = 0;
+ $arr = array_reverse($rs->fields);
+ foreach ($arr as $v) {
+ $sql .= $sqlarr[$i];
+ // from Ron Baldwin <ron.baldwin#sourceprose.com>
+ // Only quote string types
+ $typ = gettype($v);
+ if ($typ == 'string')
+ //New memory copy of input created here -mikefedyk
+ $sql .= $dest->qstr($v);
+ else if ($typ == 'double')
+ $sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1
+ else if ($typ == 'boolean')
+ $sql .= $v ? $dest->true : $dest->false;
+ else if ($typ == 'object') {
+ if (method_exists($v, '__toString')) $sql .= $dest->qstr($v->__toString());
+ else $sql .= $dest->qstr((string) $v);
+ } else if ($v === null)
+ $sql .= 'NULL';
+ else
+ $sql .= $v;
+ $i += 1;
+
+ if ($i == $nparams) break;
+ } // while
+
+ if (isset($sqlarr[$i])) {
+ $sql .= $sqlarr[$i];
+ }
+ $INS = $sql;
+ } else {
+ $INS = $sa['INS'];
+ $arr = array_reverse($rs->fields);
+ foreach($arr as $k => $v) { // only works on oracle currently
+ $k = sizeof($arr)-$k-1;
+ $v = str_replace(":","%~%COLON%!%",$v);
+ $INS = str_replace(':'.$k,$this->fixupbinary($dest->qstr($v)),$INS);
+ }
+ $INS = str_replace("%~%COLON%!%",":",$INS);
+ if ($this->htmlSpecialChars) $INS = htmlspecialchars($INS);
+ }
+ echo "-- $cnt\n",$INS,";\n\n";
+ $cnt += 1;
+ $ins += 1;
+ $rs->MoveNext();
+ }
+ $src->setFetchMode($savemode);
+ return $sa;
+ } else {
+ $saved = $src->debug;
+ #$src->debug=1;
+ if ($this->limitRecs>100)
+ $rs = $src->SelectLimit($sa['SEL'],$this->limitRecs);
+ else
+ $rs = $src->Execute($sa['SEL']);
+ $src->debug = $saved;
+ if (!$rs) {
+ if ($this->errHandler) $this->_doerr('SEL',array());
+ return array(0,0,0,0);
+ }
+
+
+ if ($this->commitReplicate || $commitRecs > 0) {
+ $dest->BeginTrans();
+ if ($this->updateSrcFn) $src2->BeginTrans();
+ }
+
+ if ($this->updateSrcFn && strpos($src2->databaseType,'mssql') !== false) {
+ # problem is writers interfere with readers in mssql
+ $rs = $src->_rs2rs($rs);
+ }
+ $cnt = 0;
+ $upd = 0;
+ $ins = 0;
+
+ $sizeofrow = sizeof($selflds);
+
+ $fn = $this->selFilter;
+ $commitRecs = $this->commitRecs;
+
+ $saved = $dest->debug;
+
+ if ($this->deleteFirst) $onlyInsert = true;
+ while ($origrow = $rs->FetchRow()) {
+
+ if ($dest->debug) {flush(); @ob_flush();}
+
+ if ($fn) {
+ if (!$fn($desttable, $origrow, $deleteFirst, $this, $selflds)) continue;
+ }
+ $doinsert = true;
+ $row = array_slice($origrow,0,$sizeofrow-1);
+
+ if (!$onlyInsert) {
+ $doinsert = false;
+ $upderr = false;
+
+ if (isset($srcPKDest)) {
+ if (is_null($origrow[$sizeofrow-3])) {
+ $doinsert = true;
+ $upderr = true;
+ }
+ }
+ if (!$upderr && !$dest->Execute($sa['UPD'],$row)) {
+ $err = true;
+ $upderr = true;
+ if ($this->errHandler) $this->_doerr('UPD',$row);
+ if (!$this->neverAbort) break;
+ }
+
+ if ($upderr || $dest->Affected_Rows() == 0) {
+ $doinsert = true;
+ } else {
+ if (!empty($uniqflds)) $this->RunUpdateSrcFn($src2, $table, $fldoffsets, $origrow, $srcwheress, 'UPD', null, $lastUpdateFld);
+ $upd += 1;
+ }
+ }
+
+ if ($doinsert) {
+ $inserr = false;
+ if (isset($srcPKDest)) {
+ $row = array_slice($origrow,0,$sizeofrow-2);
+ }
+
+ if (! $dest->Execute($sa['INS'],$row)) {
+ $err = true;
+ $inserr = true;
+ if ($this->errHandler) $this->_doerr('INS',$row);
+ if ($this->neverAbort) continue;
+ else break;
+ } else {
+ if ($dest->dataProvider == 'oci8') {
+ if ($this->oracleSequence) $lastid = $dest->GetOne("select ".$this->oracleSequence.".currVal from dual");
+ else $lastid = 'null';
+ } else {
+ $lastid = $dest->Insert_ID();
+ }
+
+ if (!$inserr && !empty($uniqflds)) {
+ $this->RunUpdateSrcFn($src2, $table, $fldoffsets, $origrow, $srcwheress, 'INS', $lastid,$lastUpdateFld);
+ }
+ $ins += 1;
+ }
+ }
+ $cnt += 1;
+
+ if ($commitRecs > 0 && ($cnt % $commitRecs) == 0) {
+ $dest->CommitTrans();
+ $dest->BeginTrans();
+
+ if ($this->updateSrcFn) {
+ $src2->CommitTrans();
+ $src2->BeginTrans();
+ }
+ }
+
+ } // while
+
+
+ if ($this->commitReplicate || $commitRecs > 0) {
+ if (!$this->neverAbort && $err) {
+ $dest->RollbackTrans();
+ if ($this->updateSrcFn) $src2->RollbackTrans();
+ } else {
+ $dest->CommitTrans();
+ if ($this->updateSrcFn) $src2->CommitTrans();
+ }
+ }
+ }
+ if ($cnt != $ins + $upd) echo "<p>ERROR: $cnt != INS $ins + UPD $upd</p>";
+ $src->setFetchMode($savemode);
+ return array(!$err, $cnt, $ins, $upd);
+ }
+ // trigger support only for sql server and oracle
+ // need to add
+ function MergeSrcSetup($srcTable, $pkeys, $srcUpdateDateFld, $srcCopyDateFld, $srcCopyFlagFld,
+ $srcCopyFlagType='C(1)', $srcCopyFlagVals = array('Y','N','P','='))
+ {
+ $sqla = array();
+ $src = $this->connSrc;
+ $idx = $srcTable.'_mrgIdx';
+ $cols = $src->MetaColumns($srcTable);
+ #adodb_pr($cols);
+ if (!isset($cols[strtoupper($srcUpdateDateFld)])) {
+ $sqla = $this->ddSrc->AddColumnSQL($srcTable, "$srcUpdateDateFld TS DEFTIMESTAMP");
+ foreach($sqla as $sql) $src->Execute($sql);
+ }
+
+ if ($srcCopyDateFld && !isset($cols[strtoupper($srcCopyDateFld)])) {
+ $sqla = $this->ddSrc->AddColumnSQL($srcTable, "$srcCopyDateFld TS DEFTIMESTAMP");
+ foreach($sqla as $sql) $src->Execute($sql);
+ }
+
+ $sysdate = $src->sysTimeStamp;
+ $arrv0 = $src->qstr($srcCopyFlagVals[0]);
+ $arrv1 = $src->qstr($srcCopyFlagVals[1]);
+ $arrv2 = $src->qstr($srcCopyFlagVals[2]);
+ $arrv3 = $src->qstr($srcCopyFlagVals[3]);
+
+ if ($srcCopyFlagFld && !isset($cols[strtoupper($srcCopyFlagFld)])) {
+ $sqla = $this->ddSrc->AddColumnSQL($srcTable, "$srcCopyFlagFld $srcCopyFlagType DEFAULT $arrv1");
+ foreach($sqla as $sql) $src->Execute($sql);
+ }
+
+ $sqla = array();
+
+
+ $name = "{$srcTable}_mrgTr";
+ if (is_array($pkeys) && strpos($src->databaseType,'mssql') !== false) {
+ $pk = reset($pkeys);
+
+ #$sqla[] = "DROP TRIGGER $name";
+ $sqltr = "
+ TRIGGER $name
+ ON $srcTable /* for data replication and merge */
+ AFTER UPDATE
+ AS
+ UPDATE $srcTable
+ SET
+ $srcUpdateDateFld = case when I.$srcCopyFlagFld = $arrv2 or I.$srcCopyFlagFld = $arrv3 then I.$srcUpdateDateFld
+ else $sysdate end,
+ $srcCopyFlagFld = case
+ when I.$srcCopyFlagFld = $arrv2 then $arrv0
+ when I.$srcCopyFlagFld = $arrv3 then D.$srcCopyFlagFld
+ else $arrv1 end
+ FROM $srcTable S Join Inserted AS I on I.$pk = S.$pk
+ JOIN Deleted as D ON I.$pk = D.$pk
+ WHERE I.$srcCopyFlagFld = D.$srcCopyFlagFld or I.$srcCopyFlagFld = $arrv2
+ or I.$srcCopyFlagFld = $arrv3 or I.$srcCopyFlagFld is null
+ ";
+ $sqla[] = 'CREATE '.$sqltr; // first if does not exists
+ $sqla[] = 'ALTER '.$sqltr; // second if it already exists
+ } else if (strpos($src->databaseType,'oci') !== false) {
+
+ if (strlen($srcTable)>22) $tableidx = substr($srcTable,0,16).substr(crc32($srcTable),6);
+ else $tableidx = $srcTable;
+
+ $name = $tableidx.$this->trgSuffix;
+ $idx = $tableidx.$this->idxSuffix;
+ $sqla[] = "
+CREATE OR REPLACE TRIGGER $name /* for data replication and merge */
+BEFORE UPDATE ON $srcTable REFERENCING NEW AS NEW OLD AS OLD
+FOR EACH ROW
+BEGIN
+ if :new.$srcCopyFlagFld = $arrv2 then
+ :new.$srcCopyFlagFld := $arrv0;
+ elsif :new.$srcCopyFlagFld = $arrv3 then
+ :new.$srcCopyFlagFld := :old.$srcCopyFlagFld;
+ elsif :old.$srcCopyFlagFld = :new.$srcCopyFlagFld or :new.$srcCopyFlagFld is null then
+ if $this->trLogic then
+ :new.$srcUpdateDateFld := $sysdate;
+ :new.$srcCopyFlagFld := $arrv1;
+ end if;
+ end if;
+END;
+";
+ }
+ foreach($sqla as $sql) $src->Execute($sql);
+
+ if ($srcCopyFlagFld) $srcCopyFlagFld .= ', ';
+ $src->Execute("CREATE INDEX {$idx} on $srcTable ($srcCopyFlagFld$srcUpdateDateFld)");
+ }
+
+
+ /*
+ Perform Merge by copying all data modified from src to dest
+ then update src copied flag if present.
+
+ Returns array taken from ReplicateData:
+
+ Returns an array:
+ $arr[0] = true if no error, false if error
+ $arr[1] = number of recs processed
+ $arr[2] = number of successful inserts
+ $arr[3] = number of successful updates
+
+ $srcTable = src table
+ $dstTable = dest table
+ $pkeys = primary keys array. if empty, then only inserts will occur
+ $srcignoreflds = ignore these flds (must be upper cased)
+ $setsrc = updateSrcFn string
+ $srcUpdateDateFld = field in src with the last update date
+ $srcCopyFlagFld = false = optional field that holds the copied indicator
+ $flagvals=array('Y','N','P','=') = array of values indicating array(copied, not copied).
+ Null is assumed to mean not copied. The 3rd value 'P' indicates that we want to force 'Y', bypassing
+ default trigger behaviour to reset the COPIED='N' when the record is replicated from other side.
+ The last value '=' is don't change copyflag.
+ $srcCopyDateFld = field that holds last copy date in src table, which will be updated on Merge()
+ $dstCopyDateFld = field that holds last copy date in dst table, which will be updated on Merge()
+ $defaultDestRaiseErrorFn = The adodb raiseErrorFn handler. Default is to not raise an error.
+ Just output error message to stdout
+
+ */
+
+
+ function Merge($srcTable, $dstTable, $pkeys, $srcignoreflds, $setsrc,
+ $srcUpdateDateFld,
+ $srcCopyFlagFld, $flagvals=array('Y','N','P','='),
+ $srcCopyDateFld = false,
+ $dstCopyDateFld = false,
+ $whereClauses = '',
+ $orderBy = '', # MUST INCLUDE THE "ORDER BY" suffix
+ $copyDoneFlagIdx = 3,
+ $defaultDestRaiseErrorFn = '')
+ {
+ $src = $this->connSrc;
+ $dest = $this->connDest;
+
+ $time = $src->Time();
+
+ $delfirst = $this->deleteFirst;
+ $upd = $this->updateSrcFn;
+
+ $this->deleteFirst = false;
+ //$this->updateFirst = true;
+
+ $srcignoreflds[] = $srcUpdateDateFld;
+ $srcignoreflds[] = $srcCopyFlagFld;
+ $srcignoreflds[] = $srcCopyDateFld;
+
+ if (empty($whereClauses)) $whereClauses = '1=1';
+ $where = " WHERE ($whereClauses) and ($srcCopyFlagFld = ".$src->qstr($flagvals[1]).')';
+ if ($orderBy) $where .= ' '.$orderBy;
+ else $where .= ' ORDER BY '.$srcUpdateDateFld;
+
+ if ($setsrc) $set[] = $setsrc;
+ else $set = array();
+
+ if ($srcCopyFlagFld) $set[] = "$srcCopyFlagFld = ".$src->qstr($flagvals[2]);
+ if ($srcCopyDateFld) $set[]= "$srcCopyDateFld = ".$src->sysTimeStamp;
+ if ($set) $this->updateSrcFn = array(implode(', ',$set));
+ else $this->updateSrcFn = '';
+
+
+ $extra[$srcCopyFlagFld] = array($dest->qstr($flagvals[0]),$dest->qstr($flagvals[$copyDoneFlagIdx]));
+
+ $saveraise = $dest->raiseErrorFn;
+ $dest->raiseErrorFn = '';
+
+ if ($this->compat && $this->compat == 1.0) $srcUpdateDateFld = '';
+ $arr = $this->ReplicateData($srcTable, $dstTable, $pkeys, $where, $srcignoreflds,
+ $dstCopyDateFld,$extra,$srcUpdateDateFld);
+
+ $dest->raiseErrorFn = $saveraise;
+
+ $this->updateSrcFn = $upd;
+ $this->deleteFirst = $delfirst;
+
+ return $arr;
+ }
+ /*
+ If doing a 2 way merge, then call
+ $rep->Merge()
+ to save without modifying the COPIEDFLAG ('=').
+
+ Then can the following to set the COPIEDFLAG to 'P' which forces the COPIEDFLAG = 'Y'
+ $rep->MergeDone()
+ */
+
+ function MergeDone($srcTable, $dstTable, $pkeys, $srcignoreflds, $setsrc,
+ $srcUpdateDateFld,
+ $srcCopyFlagFld, $flagvals=array('Y','N','P','='),
+ $srcCopyDateFld = false,
+ $dstCopyDateFld = false,
+ $whereClauses = '',
+ $orderBy = '', # MUST INCLUDE THE "ORDER BY" suffix
+ $copyDoneFlagIdx = 2,
+ $defaultDestRaiseErrorFn = '')
+ {
+ return $this->Merge($srcTable, $dstTable, $pkeys, $srcignoreflds, $setsrc,
+ $srcUpdateDateFld,
+ $srcCopyFlagFld, $flagvals,
+ $srcCopyDateFld,
+ $dstCopyDateFld,
+ $whereClauses,
+ $orderBy, # MUST INCLUDE THE "ORDER BY" suffix
+ $copyDoneFlagIdx,
+ $defaultDestRaiseErrorFn);
+ }
+
+ function _doerr($reason, $selflds)
+ {
+ $fn = $this->errHandler;
+ if ($fn) $fn($this, $reason, $selflds); // set $this->neverAbort to true or false as required inside $fn
+ }
+}
diff --git a/vendor/adodb/adodb-php/replicate/replicate-steps.php b/vendor/adodb/adodb-php/replicate/replicate-steps.php
new file mode 100644
index 0000000..5b69656
--- /dev/null
+++ b/vendor/adodb/adodb-php/replicate/replicate-steps.php
@@ -0,0 +1,137 @@
+<?php
+
+# CONFIG
+
+if (empty($USER)) {
+ $BA = "LOAN"; ## -- leave $BA as empty string to copy all BA. Otherwise enter 1 BA (no need to quote BA)
+ $STAGES = ""; ## $STAGES = "STGCAT1,STGCAT2" -- leave $STAGES as empty string to run all stages. No need to quote stgcats.
+
+ $HOST='192.168.0.2';
+ $USER='JCOLLECT_BKRM';
+ $PWD='natsoft';
+ $DBASE='RAPTOR';
+}
+# =================================== INCLUDES
+
+include_once('../adodb.inc.php');
+include_once('adodb-replicate.inc.php');
+
+# ==================================== CONNECTION
+$DB = ADONewConnection('oci8');
+$ok = $DB->Connect($HOST,$USER,$PWD,$DBASE);
+if (!$ok) return;
+
+
+#$DB->debug=1;
+
+$bkup = 'tmp'.date('ymd_His');
+
+
+if ($BA) {
+ $QTY_BA = " and qu_bacode='$BA'";
+ if (1) $STP_BA = " and s_stagecat in (select stg_stagecat from kbstage where stg_bacode='$BA')"; # OLDER KBSTEP
+ else $STP_BA = " and s_bacode='$BA'"; # LATEST KBSTEP format
+ $STG_BA = " and stg_bacode='$BA'";
+} else {
+ $QTY_BA = "";
+ $STP_BA = "";
+ $STG_BA = "";
+}
+
+if ($STAGES) {
+
+ $STAGES = explode(',',$STAGES);
+ $STAGES = "'".implode("','",$STAGES)."'";
+ $QTY_STG = " and qu_stagecat in ($STAGES)";
+ $STP_STG = " and s_stagecat in ($STAGES)";
+ $STG_STG = " and stg_stagecat in ($STAGES)";
+} else {
+ $QTY_STG = "";
+ $STP_STG = "";
+ $STG_STG = "";
+}
+
+echo "<pre>
+
+/******************************************************************************
+<font color=green>
+ Migrate stages, steps and qtypes for the following
+
+ business area: $BA
+ and stages: $STAGES
+
+
+ WARNING: DO NOT 'Ignore All Errors'.
+ If any error occurs, make sure you stop and check the reason and fix it.
+ Otherwise you could corrupt everything!!!
+
+ Connected to $USER@$DBASE $HOST;
+</font>
+*******************************************************************************/
+
+-- BACKUP
+create table kbstage_$bkup as select * from kbstage;
+create table kbstep_$bkup as select * from kbstep;
+create table kbqtype_$bkup as select * from kbqtype;
+
+
+-- IF CODE FAILS, REMEMBER TO RENABLE ALL TRIGGERS and following CONSTRAINT
+ALTER TABLE kbstage DISABLE all triggers;
+ALTER TABLE kbstep DISABLE all triggers;
+ALTER TABLE kbqtype DISABLE all triggers;
+ALTER TABLE jqueue DISABLE CONSTRAINT QUEUE_MUST_HAVE_TYPE;
+
+
+-- NOW DELETE OLD STEPS/STAGES/QUEUES
+delete from kbqtype where qu_mode in ('STAGE','STEP') $QTY_BA $QTY_STG;
+delete from kbstep where (1=1) $STP_BA$STP_STG;
+delete from kbstage where (1=1)$STG_BA$STG_STG;
+
+
+
+SET DEFINE OFF; -- disable variable handling by sqlplus
+/
+/* Assume kbstrategy and business areas are compatible for steps and stages to be copied */
+</pre>
+
+";
+
+
+$rep = new ADODB_Replicate($DB,$DB);
+$rep->execute = false;
+$rep->deleteFirst = false;
+
+ // src table name, dst table name, primary key, where condition
+$rep->ReplicateData('KBSTAGE', 'KBSTAGE', array(), " where (1=1)$STG_BA$STG_STG");
+$rep->ReplicateData('KBSTEP', 'KBSTEP', array(), " where (1=1)$STP_BA$STP_STG");
+$rep->ReplicateData('KBQTYPE','KBQTYPE',array()," where qu_mode in ('STAGE','STEP')$QTY_BA$QTY_STG");
+
+echo "
+
+-- Check for QUEUES not in KBQTYPE and FIX by copying from kbqtype_$bkup
+begin
+for rec in (select distinct q_type from jqueue where q_type not in (select qu_code from kbqtype)) loop
+ insert into kbqtype select * from kbqtype_$bkup where qu_code = rec.q_type;
+ update kbqtype set qu_name=substr('MISSING.'||qu_name,1,64) where qu_code=rec.q_type;
+end loop;
+end;
+/
+
+commit;
+
+
+ALTER TABLE kbstage ENABLE all triggers;
+ALTER TABLE kbstep ENABLE all triggers;
+ALTER TABLE kbqtype ENABLE all triggers;
+ALTER TABLE jqueue ENABLE CONSTRAINT QUEUE_MUST_HAVE_TYPE;
+
+/*
+-- REMEMBER TO COMMIT
+ commit;
+ begin Juris.UpdateQCounts; end;
+
+-- To check for bad queues after conversion, run this
+ select * from kbqtype where qu_name like 'MISSING%'
+*/
+/
+";
diff --git a/vendor/adodb/adodb-php/replicate/test-tnb.php b/vendor/adodb/adodb-php/replicate/test-tnb.php
new file mode 100644
index 0000000..f163ff4
--- /dev/null
+++ b/vendor/adodb/adodb-php/replicate/test-tnb.php
@@ -0,0 +1,421 @@
+<?php
+include_once('../adodb.inc.php');
+include_once('adodb-replicate.inc.php');
+
+set_time_limit(0);
+
+function IndexFilter($dtable, $idxname,$flds,$options)
+{
+ if (strlen($idxname) > 28) $idxname = substr($idxname,0,24).rand(1000,9999);
+ return $idxname;
+}
+
+function SelFilter($table, &$arr, $delfirst)
+{
+ return true;
+}
+
+function updatefilter($table, $fld, $val)
+{
+ return "nvl($fld, $val)";
+}
+
+
+function FieldFilter(&$fld,$mode)
+{
+ $uf = strtoupper($fld);
+ switch($uf) {
+ case 'SIZEFLD':
+ return 'Size';
+
+ case 'GROUPFLD':
+ return 'Group';
+
+ case 'GROUP':
+ if ($mode == 'SELECT') $fld = '"Group"';
+ return 'GroupFld';
+ case 'SIZE':
+ if ($mode == 'SELECT') $fld = '"Size"';
+ return 'SizeFld';
+ }
+ return $fld;
+}
+
+function ParseTable(&$table, &$pkey)
+{
+ $table = trim($table);
+ if (strlen($table) == 0) return false;
+ if (strpos($table, '#') !== false) {
+ $at = strpos($table, '#');
+ $table = trim(substr($table,0,$at));
+ if (strlen($table) == 0) return false;
+ }
+
+ $tabarr = explode(',',$table);
+ if (sizeof($tabarr) == 1) {
+ $table = $tabarr[0];
+ $pkey = '';
+ echo "No primary key for $table **** **** <br>";
+ } else {
+ $table = trim($tabarr[0]);
+ $pkey = trim($tabarr[1]);
+ if (strpos($pkey,' ') !== false) echo "Bad PKEY for $table $pkey<br>";
+ }
+
+ return true;
+}
+
+global $TARR;
+
+function TableStats($rep, $table, $pkey)
+{
+global $TARR;
+
+ if (empty($TARR)) $TARR = array();
+ $cnt = $rep->connSrc->GetOne("select count(*) from $table");
+ if (isset($TARR[$table])) echo "<h1>Table $table repeated twice</h1>";
+ $TARR[$table] = $cnt;
+
+ if ($pkey) {
+ $ok = $rep->connSrc->SelectLimit("select $pkey from $table",1);
+ if (!$ok) echo "<h1>$table: $pkey does not exist</h1>";
+ } else
+ echo "<h1>$table: no primary key</h1>";
+}
+
+function CreateTable($rep, $table)
+{
+## CREATE TABLE
+ #$DB2->Execute("drop table $table");
+
+ $rep->execute = true;
+ $ok = $rep->CopyTableStruct($table);
+ if ($ok) echo "Table Created<br>\n";
+ else {
+ echo "<hr>Error: Cannot Create Table<hr>\n";
+ }
+ flush();@ob_flush();
+}
+
+function CopyData($rep, $table, $pkey)
+{
+ $dtable = $table;
+
+ $rep->execute = true;
+ $rep->deleteFirst = true;
+
+ $secs = time();
+ $rows = $rep->ReplicateData($table,$dtable,array($pkey));
+ $secs = time() - $secs;
+ if (!$rows || !$rows[0] || !$rows[1] || $rows[1] != $rows[2]+$rows[3]) {
+ echo "<hr>Error: "; var_dump($rows); echo " (secs=$secs) <hr>\n";
+ } else
+ echo date('H:i:s'),': ',$rows[1]," record(s) copied, ",$rows[2]," inserted, ",$rows[3]," updated (secs=$secs)<br>\n";
+ flush();@ob_flush();
+}
+
+function MergeDataJohnTest($rep, $table, $pkey)
+{
+ $rep->SwapDBs();
+
+ $dtable = $table;
+ $rep->oracleSequence = 'LGBSEQUENCE';
+
+# $rep->MergeSrcSetup($table, array($pkey),'UpdatedOn','CopiedFlag');
+ if (strpos($rep->connDest->databaseType,'mssql') !== false) { # oracle ==> mssql
+ $ignoreflds = array($pkey);
+ $ignoreflds[] = 'MSSQL_ID';
+ $set = 'MSSQL_ID=nvl($INSERT_ID,MSSQL_ID)';
+ $pkeyarr = array(array($pkey),false,array('MSSQL_ID'));# array('MSSQL_ID', 'ORA_ID'));
+ } else { # mssql ==> oracle
+ $ignoreflds = array($pkey);
+ $ignoreflds[] = 'ORA_ID';
+ $set = '';
+ #$set = 'ORA_ID=isnull($INSERT_ID,ORA_ID)';
+ $pkeyarr = array(array($pkey),array('MSSQL_ID'));
+ }
+ $rep->execute = true;
+ #$rep->updateFirst = false;
+ $ok = $rep->Merge($table, $dtable, $pkeyarr, $ignoreflds, $set, 'UpdatedOn','CopiedFlag',array('Y','N','P','='), 'CopyDate');
+ var_dump($ok);
+
+ #$rep->connSrc->Execute("update JohnTest set name='Apple' where id=4");
+}
+
+$DB = ADONewConnection('odbtp');
+#$ok = $DB->Connect('localhost','root','','northwind');
+$ok = $DB->Connect('192.168.0.1','DRIVER={SQL Server};SERVER=(local);UID=sa;PWD=natsoft;DATABASE=OIR;','','');
+$DB->_bindInputArray = false;
+
+$DB2 = ADONewConnection('oci8');
+$ok2 = $DB2->Connect('192.168.0.2','tnb','natsoft','RAPTOR','');
+
+if (!$ok || !$ok2) die("Failed connection DB=$ok DB2=$ok2<br>");
+
+$tables =
+"
+JohnTest,id
+";
+
+# net* are ERMS, need last updated field from LGBnet
+# tblRep* are tables insert or update from Juris, need last updated field also
+# The rest are lookup tables, can copy all from LGBnet
+
+$tablesOrig =
+"
+SysVoltSubLevel,id
+# Lookup table for Restoration Details screen
+sysefi,ID # (not identity)
+sysgenkva,ID #(not identity)
+sysrestoredby,ID #(not identity)
+# Sel* table added on 24 Oct
+SELSGManufacturer,ID
+SelABCCondSizeLV,ID
+SelABCCondSizeMV,ID
+SelArchingHornSize,ID
+SelBallastSize,ID
+SelBallastType,ID
+SelBatteryType,ID #(not identity)
+SelBreakerCapacity,ID
+SelBreakerType,ID #(not identity)
+SelCBreakerManuf,ID
+SelCTRatio,ID #(not identity)
+SelCableBrand,ID
+SelCableSize,ID
+SelCableSizeLV,ID # (not identity)
+SelCapacitorSize,ID
+SelCapacitorType,ID
+SelColourCode,ID
+SelCombineSealingChamberSize,ID
+SelConductorBrand,ID
+SelConductorSize4,ID
+SelConductorSizeLV,ID
+SelConductorSizeMV,ID
+SelContactorSize,ID
+SelContractor,ID
+SelCoverType,ID
+SelCraddleSize,ID
+SelDeadEndClampBrand,ID
+SelDeadEndClampSize,ID
+SelDevTermination,ID
+SelFPManuf,ID
+SelFPillarRating,ID
+SelFalseTrue,ID
+SelFuseManuf,ID
+SelFuseType,ID
+SelIPCBrand,ID
+SelIPCSize,ID
+SelIgnitorSize,ID
+SelIgnitorType,ID
+SelInsulatorBrand,ID
+SelJoint,ID
+SelJointBrand,ID
+SelJunctionBoxBrand,ID
+SelLVBoardBrand,ID
+SelLVBoardSize,ID
+SelLVOHManuf,ID
+SelLVVoltage,ID
+SelLightningArresterBrand,ID
+SelLightningShieldwireSize,ID
+SelLineTapSize,ID
+SelLocation,ID
+SelMVVoltage,ID
+SelMidSpanConnectorsSize,ID
+SelMidSpanJointSize,ID
+SelNERManuf,ID
+SelNERType,ID
+SelNLinkSize,ID
+SelPVCCondSizeLV,ID
+SelPoleBrand,ID
+SelPoleConcreteSize,ID
+SelPoleSize,ID
+SelPoleSpunConcreteSize,ID
+SelPoleSteelSize,ID
+SelPoleType,ID
+SelPoleWoodSize,ID
+SelPorcelainFuseSize,ID
+SelRatedFaultCurrentBreaker,ID
+SelRatedVoltageSG,ID #(not identity)
+SelRelayType,ID # (not identity)
+SelResistanceValue,ID
+SelSGEquipmentType,ID # (not identity)
+SelSGInsulationType,ID # (not identity)
+SelSGManufacturer,ID
+SelStayInsulatorSize,ID
+SelSuspensionClampBrand,ID
+SelSuspensionClampSize,ID
+SelTSwitchType,ID
+SelTowerType,ID
+SelTransformerCapacity,ID
+SelTransformerManuf,ID
+SelTransformerType,ID #(not identity)
+SelTypeOfArchingHorn,ID
+SelTypeOfCable,ID #(not identity)
+SelTypeOfConductor,ID # (not identity)
+SelTypeOfInsulationCB,ID # (not identity)
+SelTypeOfMidSpanJoint,ID
+SelTypeOfSTJoint,ID
+SelTypeSTCable,ID
+SelUGVoltage,ID # (not identity)
+SelVoltageInOut,ID
+SelWireSize,ID
+SelWireType,ID
+SelWonpieceBrand,ID
+#
+# Net* tables added on 24 Oct
+NetArchingHorn,Idx
+NetBatteryBank,Idx # identity, FunctLocation Pri
+NetBiMetal,Idx
+NetBoxFuse,Idx
+NetCable,Idx # identity, FunctLocation Pri
+NetCapacitorBank,Idx # identity, FunctLocation Pri
+NetCircuitBreaker,Idx # identity, FunctLocation Pri
+NetCombineSealingChamber,Idx
+NetCommunication,Idx
+NetCompInfras,Idx
+NetControl,Idx
+NetCraddle,Idx
+NetDeadEndClamp,Idx
+NetEarthing,Idx
+NetFaultIndicator,Idx
+NetFeederPillar,Idx # identity, FunctLocation Pri
+NetGenCable,Idx # identity , FunctLocation Not Null
+NetGenerator,Idx
+NetGrid,Idx
+NetHVOverhead,Idx #identity, FunctLocation Pri
+NetHVUnderground,Idx #identity, FunctLocation Pri
+NetIPC,Idx
+NetInductorBank,Idx
+NetInsulator,Idx
+NetJoint,Idx
+NetJunctionBox,Idx
+NetLVDB,Idx #identity, FunctLocation Pri
+NetLVOverhead,Idx
+NetLVUnderground,Idx # identity, FunctLocation Not Null
+NetLightningArrester,Idx
+NetLineTap,Idx
+NetMidSpanConnectors,Idx
+NetMidSpanJoint,Idx
+NetNER,Idx # identity , FunctLocation Pri
+NetOilPump,Idx
+NetOtherComponent,Idx
+NetPole,Idx
+NetRMU,Idx # identity, FunctLocation Pri
+NetStreetLight,Idx
+NetStrucSupp,Idx
+NetSuspensionClamp,Idx
+NetSwitchGear,Idx # identity, FunctLocation Pri
+NetTermination,Idx
+NetTransition,Idx
+NetWonpiece,Idx
+#
+# comment1
+SelMVFuseType,ID
+selFuseSize,ID
+netRelay,Idx # identity, FunctLocation Pri
+SysListVolt,ID
+sysVoltLevel,ID_SVL
+sysRestoration,ID_SRE
+sysRepairMethod,ID_SRM # (not identity)
+
+sysInterruptionType,ID_SIN
+netTransformer,Idx # identity, FunctLocation Pri
+#
+#
+sysComponent,ID_SC
+sysCodecibs #-- no idea, UpdatedOn(the only column is unique),Ermscode,Cibscode is unique but got null value
+sysCodeno,id
+sysProtection,ID_SP
+sysEquipment,ID_SEQ
+sysAddress #-- no idea, ID_SAD(might be auto gen No)
+sysWeather,ID_SW
+sysEnvironment,ID_SE
+sysPhase,ID_SPH
+sysFailureCause,ID_SFC
+sysFailureMode,ID_SFM
+SysSchOutageMode,ID_SSM
+SysOutageType,ID_SOT
+SysInstallation,ID_SI
+SysInstallationCat,ID_SIC
+SysInstallationType,ID_SIT
+SysFaultCategory,ID_SF #(not identity)
+SysResponsible,ID_SR
+SysProtectionOperation,ID_SPO #(not identity)
+netCodename,CodeNo #(not identity)
+netSubstation,Idx #identity, FunctLocation Pri
+netLvFeeder,Idx # identity, FunctLocation Pri
+#
+#
+tblReport,ReportNo
+tblRepRestoration,ID_RR
+tblRepResdetail,ID_RRD
+tblRepFailureMode,ID_RFM
+tblRepFailureCause,ID_RFC
+tblRepRepairMethod,ReportNo # (not identity)
+tblInterruptionType,ID_TIN
+tblProtType,ID_PT #--capital letter
+tblRepProtection,ID_RP
+tblRepComponent,ID_RC
+tblRepWeather,ID_RW
+tblRepEnvironment,ID_RE
+tblRepSubstation,ID_RSS
+tblInstallationType,ID_TIT
+tblInstallationCat,ID_TIC
+tblFailureCause,ID_TFC
+tblFailureMode,ID_TFM
+tblProtection,ID_TP
+tblComponent,ID_TC
+tblProtdetail,Id # (Id)--capital letter for I
+tblInstallation,ID_TI
+#
+";
+
+
+$tables = explode("\n",$tables);
+
+$rep = new ADODB_Replicate($DB,$DB2);
+$rep->fieldFilter = 'FieldFilter';
+$rep->selFilter = 'SELFILTER';
+$rep->indexFilter = 'IndexFilter';
+
+if (1) {
+ $rep->debug = 1;
+ $DB->debug=1;
+ $DB2->debug=1;
+}
+
+# $rep->SwapDBs();
+
+$cnt = sizeof($tables);
+foreach($tables as $k => $table) {
+ $pkey = '';
+ if (!ParseTable($table, $pkey)) continue;
+
+ #######################
+
+ $kcnt = $k+1;
+ echo "<h1>($kcnt/$cnt) $table -- $pkey</h1>\n";
+ flush();@ob_flush();
+
+ CreateTable($rep,$table);
+
+
+ # COPY DATA
+
+
+ TableStats($rep, $table, $pkey);
+
+ if ($table == 'JohnTest') MergeDataJohnTest($rep, $table, $pkey);
+ else CopyData($rep, $table, $pkey);
+
+}
+
+
+if (!empty($TARR)) {
+ ksort($TARR);
+ adodb_pr($TARR);
+ asort($TARR);
+ adodb_pr($TARR);
+}
+
+echo "<hr>",date('H:i:s'),": Done</hr>";
diff --git a/vendor/adodb/adodb-php/rsfilter.inc.php b/vendor/adodb/adodb-php/rsfilter.inc.php
new file mode 100644
index 0000000..d877c83
--- /dev/null
+++ b/vendor/adodb/adodb-php/rsfilter.inc.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * Requires PHP4.01pl2 or later because it uses include_once
+*/
+
+/*
+ Filter all fields and all rows in a recordset and returns the
+ processed recordset. We scroll to the beginning of the new recordset
+ after processing.
+
+ We pass a recordset and function name to RSFilter($rs,'rowfunc');
+ and the function will be called multiple times, once
+ for each row in the recordset. The function will be passed
+ an array containing one row repeatedly.
+
+ Example:
+
+ // ucwords() every element in the recordset
+ function do_ucwords(&$arr,$rs)
+ {
+ foreach($arr as $k => $v) {
+ $arr[$k] = ucwords($v);
+ }
+ }
+ $rs = RSFilter($rs,'do_ucwords');
+ */
+function RSFilter($rs,$fn)
+{
+ if ($rs->databaseType != 'array') {
+ if (!$rs->connection) return false;
+
+ $rs = $rs->connection->_rs2rs($rs);
+ }
+ $rows = $rs->RecordCount();
+ for ($i=0; $i < $rows; $i++) {
+ if (is_array ($fn)) {
+ $obj = $fn[0];
+ $method = $fn[1];
+ $obj->$method ($rs->_array[$i],$rs);
+ } else {
+ $fn($rs->_array[$i],$rs);
+ }
+
+ }
+ if (!$rs->EOF) {
+ $rs->_currentRow = 0;
+ $rs->fields = $rs->_array[0];
+ }
+
+ return $rs;
+}
diff --git a/vendor/adodb/adodb-php/scripts/.gitignore b/vendor/adodb/adodb-php/scripts/.gitignore
new file mode 100644
index 0000000..3e0e62b
--- /dev/null
+++ b/vendor/adodb/adodb-php/scripts/.gitignore
@@ -0,0 +1,2 @@
+# Python byte code
+*.pyc
diff --git a/vendor/adodb/adodb-php/scripts/TARADO5.BAT b/vendor/adodb/adodb-php/scripts/TARADO5.BAT
new file mode 100644
index 0000000..bf25ef9
--- /dev/null
+++ b/vendor/adodb/adodb-php/scripts/TARADO5.BAT
@@ -0,0 +1,49 @@
+@rem REQUIRES P:\INSTALLS\CMDUTILS
+
+echo Don't forget to strip LF's !!!!!!!!!!!
+pause
+
+
+set VER=518a
+
+d:
+cd \inetpub\wwwroot\php
+
+@del /s /q zadodb\*.*
+@mkdir zadodb
+
+@REM not for release -- make sure in VSS
+attrib -r adodb5\drivers\adodb-text.inc.php
+del adodb5\*.bak
+del adodb5\drivers\*.bak
+del adodb5\hs~*.*
+del adodb5\drivers\hs~*.*
+del adodb5\tests\hs~*.*
+del adodb5\drivers\adodb-text.inc.php
+del adodb5\.#*
+del adodb5\replicate\replicate-steps.php
+del adodb5\replicate\test*.php
+del adodb5\adodb-lite.inc.php
+attrib -r adodb5\*.php
+del adodb5\cute_icons_for_site\*.png
+
+del tmp.tar
+del adodb5*.tgz
+del adodb5*.zip
+
+@mkdir adodb5\docs
+move /y adodb5\*.htm adodb5\docs
+
+@rem CREATE TAR FILE
+tar -f adodb%VER%.tar -c adodb5/*.* adodb5/perf/*.* adodb5/session/*.* adodb5/pear/*.txt adodb5/pear/Auth/Container/ADOdb.php adodb5/session/old/*.* adodb5/drivers/*.* adodb5/lang/*.* adodb5/tests/*.* adodb5/cute_icons_for_site/*.* adodb5/datadict/*.* adodb5/contrib/*.* adodb5/xsl/*.* adodb5/docs/*.*
+
+@rem CREATE ZIP FILE
+cd zadodb
+tar -xf ..\adodb%VER%.TAR
+zip -r ..\adodb%VER%.zip adodb5
+cd ..
+
+@rem CREATE TGZ FILE, THE RENAME CHANGES UPPERCASE TO LOWERCASE
+gzip -v ADODB%VER%.tar -S .tgz -9
+rename ADODB%VER%.tar.TGZ adodb%VER%.tgz
+
diff --git a/vendor/adodb/adodb-php/server.php b/vendor/adodb/adodb-php/server.php
new file mode 100644
index 0000000..a989e16
--- /dev/null
+++ b/vendor/adodb/adodb-php/server.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ */
+
+/* Documentation on usage is at http://adodb.org/dokuwiki/doku.php?id=v5:proxy:proxy_index
+ *
+ * Legal query string parameters:
+ *
+ * sql = holds sql string
+ * nrows = number of rows to return
+ * offset = skip offset rows of data
+ * fetch = $ADODB_FETCH_MODE
+ *
+ * example:
+ *
+ * http://localhost/php/server.php?select+*+from+table&nrows=10&offset=2
+ */
+
+
+/*
+ * Define the IP address you want to accept requests from
+ * as a security measure. If blank we accept anyone promisciously!
+ */
+$ACCEPTIP = '127.0.0.1';
+
+/*
+ * Connection parameters
+ */
+$driver = 'mysql';
+$host = 'localhost'; // DSN for odbc
+$uid = 'root';
+$pwd = 'garbase-it-is';
+$database = 'test';
+
+/*============================ DO NOT MODIFY BELOW HERE =================================*/
+// $sep must match csv2rs() in adodb.inc.php
+$sep = ' :::: ';
+
+include('./adodb.inc.php');
+include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
+
+function err($s)
+{
+ die('**** '.$s.' ');
+}
+
+// undo stupid magic quotes
+function undomq(&$m)
+{
+ if (get_magic_quotes_gpc()) {
+ // undo the damage
+ $m = str_replace('\\\\','\\',$m);
+ $m = str_replace('\"','"',$m);
+ $m = str_replace('\\\'','\'',$m);
+
+ }
+ return $m;
+}
+
+///////////////////////////////////////// DEFINITIONS
+
+
+$remote = $_SERVER["REMOTE_ADDR"];
+
+
+if (!empty($ACCEPTIP))
+ if ($remote != '127.0.0.1' && $remote != $ACCEPTIP)
+ err("Unauthorised client: '$remote'");
+
+
+if (empty($_REQUEST['sql'])) err('No SQL');
+
+
+$conn = ADONewConnection($driver);
+
+if (!$conn->Connect($host,$uid,$pwd,$database)) err($conn->ErrorNo(). $sep . $conn->ErrorMsg());
+$sql = undomq($_REQUEST['sql']);
+
+if (isset($_REQUEST['fetch']))
+ $ADODB_FETCH_MODE = $_REQUEST['fetch'];
+
+if (isset($_REQUEST['nrows'])) {
+ $nrows = $_REQUEST['nrows'];
+ $offset = isset($_REQUEST['offset']) ? $_REQUEST['offset'] : -1;
+ $rs = $conn->SelectLimit($sql,$nrows,$offset);
+} else
+ $rs = $conn->Execute($sql);
+if ($rs){
+ //$rs->timeToLive = 1;
+ echo _rs2serialize($rs,$conn,$sql);
+ $rs->Close();
+} else
+ err($conn->ErrorNo(). $sep .$conn->ErrorMsg());
diff --git a/vendor/adodb/adodb-php/session/adodb-compress-bzip2.php b/vendor/adodb/adodb-php/session/adodb-compress-bzip2.php
new file mode 100644
index 0000000..5b2458d
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-compress-bzip2.php
@@ -0,0 +1,118 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+*/
+
+if (!function_exists('bzcompress')) {
+ trigger_error('bzip2 functions are not available', E_USER_ERROR);
+ return 0;
+}
+
+/*
+*/
+class ADODB_Compress_Bzip2 {
+ /**
+ */
+ var $_block_size = null;
+
+ /**
+ */
+ var $_work_level = null;
+
+ /**
+ */
+ var $_min_length = 1;
+
+ /**
+ */
+ function getBlockSize() {
+ return $this->_block_size;
+ }
+
+ /**
+ */
+ function setBlockSize($block_size) {
+ assert('$block_size >= 1');
+ assert('$block_size <= 9');
+ $this->_block_size = (int) $block_size;
+ }
+
+ /**
+ */
+ function getWorkLevel() {
+ return $this->_work_level;
+ }
+
+ /**
+ */
+ function setWorkLevel($work_level) {
+ assert('$work_level >= 0');
+ assert('$work_level <= 250');
+ $this->_work_level = (int) $work_level;
+ }
+
+ /**
+ */
+ function getMinLength() {
+ return $this->_min_length;
+ }
+
+ /**
+ */
+ function setMinLength($min_length) {
+ assert('$min_length >= 0');
+ $this->_min_length = (int) $min_length;
+ }
+
+ /**
+ */
+ function __construct($block_size = null, $work_level = null, $min_length = null) {
+ if (!is_null($block_size)) {
+ $this->setBlockSize($block_size);
+ }
+
+ if (!is_null($work_level)) {
+ $this->setWorkLevel($work_level);
+ }
+
+ if (!is_null($min_length)) {
+ $this->setMinLength($min_length);
+ }
+ }
+
+ /**
+ */
+ function write($data, $key) {
+ if (strlen($data) < $this->_min_length) {
+ return $data;
+ }
+
+ if (!is_null($this->_block_size)) {
+ if (!is_null($this->_work_level)) {
+ return bzcompress($data, $this->_block_size, $this->_work_level);
+ } else {
+ return bzcompress($data, $this->_block_size);
+ }
+ }
+
+ return bzcompress($data);
+ }
+
+ /**
+ */
+ function read($data, $key) {
+ return $data ? bzdecompress($data) : $data;
+ }
+
+}
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-compress-gzip.php b/vendor/adodb/adodb-php/session/adodb-compress-gzip.php
new file mode 100644
index 0000000..40e906a
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-compress-gzip.php
@@ -0,0 +1,93 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+*/
+
+if (!function_exists('gzcompress')) {
+ trigger_error('gzip functions are not available', E_USER_ERROR);
+ return 0;
+}
+
+/*
+*/
+class ADODB_Compress_Gzip {
+ /**
+ */
+ var $_level = null;
+
+ /**
+ */
+ var $_min_length = 1;
+
+ /**
+ */
+ function getLevel() {
+ return $this->_level;
+ }
+
+ /**
+ */
+ function setLevel($level) {
+ assert('$level >= 0');
+ assert('$level <= 9');
+ $this->_level = (int) $level;
+ }
+
+ /**
+ */
+ function getMinLength() {
+ return $this->_min_length;
+ }
+
+ /**
+ */
+ function setMinLength($min_length) {
+ assert('$min_length >= 0');
+ $this->_min_length = (int) $min_length;
+ }
+
+ /**
+ */
+ function __construct($level = null, $min_length = null) {
+ if (!is_null($level)) {
+ $this->setLevel($level);
+ }
+
+ if (!is_null($min_length)) {
+ $this->setMinLength($min_length);
+ }
+ }
+
+ /**
+ */
+ function write($data, $key) {
+ if (strlen($data) < $this->_min_length) {
+ return $data;
+ }
+
+ if (!is_null($this->_level)) {
+ return gzcompress($data, $this->_level);
+ } else {
+ return gzcompress($data);
+ }
+ }
+
+ /**
+ */
+ function read($data, $key) {
+ return $data ? gzuncompress($data) : $data;
+ }
+
+}
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-cryptsession.php b/vendor/adodb/adodb-php/session/adodb-cryptsession.php
new file mode 100644
index 0000000..07f818a
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-cryptsession.php
@@ -0,0 +1,27 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+*/
+
+/*
+
+This file is provided for backwards compatibility purposes
+
+*/
+
+if (!defined('ADODB_SESSION')) {
+ require_once dirname(__FILE__) . '/adodb-session.php';
+}
+
+require_once ADODB_SESSION . '/adodb-encrypt-md5.php';
+
+ADODB_Session::filter(new ADODB_Encrypt_MD5());
diff --git a/vendor/adodb/adodb-php/session/adodb-cryptsession2.php b/vendor/adodb/adodb-php/session/adodb-cryptsession2.php
new file mode 100644
index 0000000..92e1bd5
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-cryptsession2.php
@@ -0,0 +1,27 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+*/
+
+/*
+
+This file is provided for backwards compatibility purposes
+
+*/
+
+if (!defined('ADODB_SESSION')) {
+ require_once dirname(__FILE__) . '/adodb-session2.php';
+}
+
+require_once ADODB_SESSION . '/adodb-encrypt-md5.php';
+
+ADODB_Session::filter(new ADODB_Encrypt_MD5());
diff --git a/vendor/adodb/adodb-php/session/adodb-encrypt-mcrypt.php b/vendor/adodb/adodb-php/session/adodb-encrypt-mcrypt.php
new file mode 100644
index 0000000..7e30de7
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-encrypt-mcrypt.php
@@ -0,0 +1,109 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+*/
+
+if (!function_exists('mcrypt_encrypt')) {
+ trigger_error('Mcrypt functions are not available', E_USER_ERROR);
+ return 0;
+}
+
+/**
+ */
+class ADODB_Encrypt_MCrypt {
+ /**
+ */
+ var $_cipher;
+
+ /**
+ */
+ var $_mode;
+
+ /**
+ */
+ var $_source;
+
+ /**
+ */
+ function getCipher() {
+ return $this->_cipher;
+ }
+
+ /**
+ */
+ function setCipher($cipher) {
+ $this->_cipher = $cipher;
+ }
+
+ /**
+ */
+ function getMode() {
+ return $this->_mode;
+ }
+
+ /**
+ */
+ function setMode($mode) {
+ $this->_mode = $mode;
+ }
+
+ /**
+ */
+ function getSource() {
+ return $this->_source;
+ }
+
+ /**
+ */
+ function setSource($source) {
+ $this->_source = $source;
+ }
+
+ /**
+ */
+ function __construct($cipher = null, $mode = null, $source = null) {
+ if (!$cipher) {
+ $cipher = MCRYPT_RIJNDAEL_256;
+ }
+ if (!$mode) {
+ $mode = MCRYPT_MODE_ECB;
+ }
+ if (!$source) {
+ $source = MCRYPT_RAND;
+ }
+
+ $this->_cipher = $cipher;
+ $this->_mode = $mode;
+ $this->_source = $source;
+ }
+
+ /**
+ */
+ function write($data, $key) {
+ $iv_size = mcrypt_get_iv_size($this->_cipher, $this->_mode);
+ $iv = mcrypt_create_iv($iv_size, $this->_source);
+ return mcrypt_encrypt($this->_cipher, $key, $data, $this->_mode, $iv);
+ }
+
+ /**
+ */
+ function read($data, $key) {
+ $iv_size = mcrypt_get_iv_size($this->_cipher, $this->_mode);
+ $iv = mcrypt_create_iv($iv_size, $this->_source);
+ $rv = mcrypt_decrypt($this->_cipher, $key, $data, $this->_mode, $iv);
+ return rtrim($rv, "\0");
+ }
+
+}
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-encrypt-md5.php b/vendor/adodb/adodb-php/session/adodb-encrypt-md5.php
new file mode 100644
index 0000000..fd59743
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-encrypt-md5.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+*/
+
+// security - hide paths
+if (!defined('ADODB_SESSION')) die();
+
+include_once ADODB_SESSION . '/crypt.inc.php';
+
+/**
+ */
+class ADODB_Encrypt_MD5 {
+ /**
+ */
+ function write($data, $key) {
+ $md5crypt = new MD5Crypt();
+ return $md5crypt->encrypt($data, $key);
+ }
+
+ /**
+ */
+ function read($data, $key) {
+ $md5crypt = new MD5Crypt();
+ return $md5crypt->decrypt($data, $key);
+ }
+
+}
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-encrypt-secret.php b/vendor/adodb/adodb-php/session/adodb-encrypt-secret.php
new file mode 100644
index 0000000..7df6aa8
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-encrypt-secret.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+*/
+
+@define('HORDE_BASE', dirname(dirname(dirname(__FILE__))) . '/horde');
+
+if (!is_dir(HORDE_BASE)) {
+ trigger_error(sprintf('Directory not found: \'%s\'', HORDE_BASE), E_USER_ERROR);
+ return 0;
+}
+
+include_once HORDE_BASE . '/lib/Horde.php';
+include_once HORDE_BASE . '/lib/Secret.php';
+
+/**
+
+NOTE: On Windows 2000 SP4 with PHP 4.3.1, MCrypt 2.4.x, and Apache 1.3.28,
+the session didn't work properly.
+
+This may be resolved with 4.3.3.
+
+ */
+class ADODB_Encrypt_Secret {
+ /**
+ */
+ function write($data, $key) {
+ return Secret::write($key, $data);
+ }
+
+ /**
+ */
+ function read($data, $key) {
+ return Secret::read($key, $data);
+ }
+
+}
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-encrypt-sha1.php b/vendor/adodb/adodb-php/session/adodb-encrypt-sha1.php
new file mode 100644
index 0000000..7065515
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-encrypt-sha1.php
@@ -0,0 +1,31 @@
+<?php
+if (!defined('ADODB_SESSION')) die();
+
+include_once ADODB_SESSION . '/crypt.inc.php';
+
+
+/**
+
+ */
+
+class ADODB_Encrypt_SHA1 {
+
+ function write($data, $key)
+ {
+ $sha1crypt = new SHA1Crypt();
+ return $sha1crypt->encrypt($data, $key);
+
+ }
+
+
+ function read($data, $key)
+ {
+ $sha1crypt = new SHA1Crypt();
+ return $sha1crypt->decrypt($data, $key);
+
+ }
+}
+
+
+
+return 1;
diff --git a/vendor/adodb/adodb-php/session/adodb-sess.txt b/vendor/adodb/adodb-php/session/adodb-sess.txt
new file mode 100644
index 0000000..c6c7685
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-sess.txt
@@ -0,0 +1,131 @@
+John,
+
+I have been an extremely satisfied ADODB user for several years now.
+
+To give you something back for all your hard work, I've spent the last 3
+days rewriting the adodb-session.php code.
+
+----------
+What's New
+----------
+
+Here's a list of the new code's benefits:
+
+* Combines the functionality of the three files:
+
+adodb-session.php
+adodb-session-clob.php
+adodb-cryptsession.php
+
+each with very similar functionality, into a single file adodb-session.php.
+This will ease maintenance and support issues.
+
+* Supports multiple encryption and compression schemes.
+ Currently, we support:
+
+ MD5Crypt (crypt.inc.php)
+ MCrypt
+ Secure (Horde's emulation of MCrypt, if MCrypt module is not available.)
+ GZip
+ BZip2
+
+These can be stacked, so if you want to compress and then encrypt your
+session data, it's easy.
+Also, the built-in MCrypt functions will be *much* faster, and more secure,
+than the MD5Crypt code.
+
+* adodb-session.php contains a single class ADODB_Session that encapsulates
+all functionality.
+ This eliminates the use of global vars and defines (though they are
+supported for backwards compatibility).
+
+* All user defined parameters are now static functions in the ADODB_Session
+class.
+
+New parameters include:
+
+* encryptionKey(): Define the encryption key used to encrypt the session.
+Originally, it was a hard coded string.
+
+* persist(): Define if the database will be opened in persistent mode.
+Originally, the user had to call adodb_sess_open().
+
+* dataFieldName(): Define the field name used to store the session data, as
+'DATA' appears to be a reserved word in the following cases:
+ ANSI SQL
+ IBM DB2
+ MS SQL Server
+ Postgres
+ SAP
+
+* filter(): Used to support multiple, simulataneous encryption/compression
+schemes.
+
+* Debug support is improved thru _rsdump() function, which is called after
+every database call.
+
+------------
+What's Fixed
+------------
+
+The new code includes several bug fixes and enhancements:
+
+* sesskey is compared in BINARY mode for MySQL, to avoid problems with
+session keys that differ only by case.
+ Of course, the user should define the sesskey field as BINARY, to
+correctly fix this problem, otherwise performance will suffer.
+
+* In ADODB_Session::gc(), if $expire_notify is true, the multiple DELETES in
+the original code have been optimized to a single DELETE.
+
+* In ADODB_Session::destroy(), since "SELECT expireref, sesskey FROM $table
+WHERE sesskey = $qkey" will only return a single value, we don't loop on the
+result, we simply process the row, if any.
+
+* We close $rs after every use.
+
+---------------
+What's the Same
+---------------
+
+I know backwards compatibility is *very* important to you. Therefore, the
+new code is 100% backwards compatible.
+
+If you like my code, but don't "trust" it's backwards compatible, maybe we
+offer it as beta code, in a new directory for a release or two?
+
+------------
+What's To Do
+------------
+
+I've vascillated over whether to use a single function to get/set
+parameters:
+
+$user = ADODB_Session::user(); // get
+ADODB_Session::user($user); // set
+
+or to use separate functions (which is the PEAR/Java way):
+
+$user = ADODB_Session::getUser();
+ADODB_Session::setUser($user);
+
+I've chosen the former as it's makes for a simpler API, and reduces the
+amount of code, but I'd be happy to change it to the latter.
+
+Also, do you think the class should be a singleton class, versus a static
+class?
+
+Let me know if you find this code useful, and will be including it in the
+next release of ADODB.
+
+If so, I will modify the current documentation to detail the new
+functionality. To that end, what file(s) contain the documentation? Please
+send them to me if they are not publically available.
+
+Also, if there is *anything* in the code that you like to see changed, let
+me know.
+
+Thanks,
+
+Ross
+
diff --git a/vendor/adodb/adodb-php/session/adodb-session-clob.php b/vendor/adodb/adodb-php/session/adodb-session-clob.php
new file mode 100644
index 0000000..437a524
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-session-clob.php
@@ -0,0 +1,24 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+*/
+
+/*
+
+This file is provided for backwards compatibility purposes
+
+*/
+
+if (!defined('ADODB_SESSION')) {
+ require_once dirname(__FILE__) . '/adodb-session.php';
+}
+ADODB_Session::clob('CLOB');
diff --git a/vendor/adodb/adodb-php/session/adodb-session-clob2.php b/vendor/adodb/adodb-php/session/adodb-session-clob2.php
new file mode 100644
index 0000000..365d9ea
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-session-clob2.php
@@ -0,0 +1,24 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+*/
+
+/*
+
+This file is provided for backwards compatibility purposes
+
+*/
+
+if (!defined('ADODB_SESSION')) {
+ require_once dirname(__FILE__) . '/adodb-session2.php';
+}
+ADODB_Session::clob('CLOB');
diff --git a/vendor/adodb/adodb-php/session/adodb-session.php b/vendor/adodb/adodb-php/session/adodb-session.php
new file mode 100644
index 0000000..b832608
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-session.php
@@ -0,0 +1,934 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+*/
+
+/*
+ You may want to rename the 'data' field to 'session_data' as
+ 'data' appears to be a reserved word for one or more of the following:
+ ANSI SQL
+ IBM DB2
+ MS SQL Server
+ Postgres
+ SAP
+
+ If you do, then execute:
+
+ ADODB_Session::dataFieldName('session_data');
+
+*/
+
+if (!defined('_ADODB_LAYER')) {
+ require realpath(dirname(__FILE__) . '/../adodb.inc.php');
+}
+
+if (defined('ADODB_SESSION')) return 1;
+
+define('ADODB_SESSION', dirname(__FILE__));
+
+
+/*
+ Unserialize session data manually. See http://phplens.com/lens/lensforum/msgs.php?id=9821
+
+ From Kerr Schere, to unserialize session data stored via ADOdb.
+ 1. Pull the session data from the db and loop through it.
+ 2. Inside the loop, you will need to urldecode the data column.
+ 3. After urldecode, run the serialized string through this function:
+
+*/
+function adodb_unserialize( $serialized_string )
+{
+ $variables = array( );
+ $a = preg_split( "/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
+ for( $i = 0; $i < count( $a ); $i = $i+2 ) {
+ $variables[$a[$i]] = unserialize( $a[$i+1] );
+ }
+ return( $variables );
+}
+
+/*
+ Thanks Joe Li. See http://phplens.com/lens/lensforum/msgs.php?id=11487&x=1
+ Since adodb 4.61.
+*/
+function adodb_session_regenerate_id()
+{
+ $conn = ADODB_Session::_conn();
+ if (!$conn) return false;
+
+ $old_id = session_id();
+ if (function_exists('session_regenerate_id')) {
+ session_regenerate_id();
+ } else {
+ session_id(md5(uniqid(rand(), true)));
+ $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ //@session_start();
+ }
+ $new_id = session_id();
+ $ok = $conn->Execute('UPDATE '. ADODB_Session::table(). ' SET sesskey='. $conn->qstr($new_id). ' WHERE sesskey='.$conn->qstr($old_id));
+
+ /* it is possible that the update statement fails due to a collision */
+ if (!$ok) {
+ session_id($old_id);
+ if (empty($ck)) $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ Generate database table for session data
+ @see http://phplens.com/lens/lensforum/msgs.php?id=12280
+ @return 0 if failure, 1 if errors, 2 if successful.
+ @author Markus Staab http://www.public-4u.de
+*/
+function adodb_session_create_table($schemaFile=null,$conn = null)
+{
+ // set default values
+ if ($schemaFile===null) $schemaFile = ADODB_SESSION . '/session_schema.xml';
+ if ($conn===null) $conn = ADODB_Session::_conn();
+
+ if (!$conn) return 0;
+
+ $schema = new adoSchema($conn);
+ $schema->ParseSchema($schemaFile);
+ return $schema->ExecuteSchema();
+}
+
+/*!
+ \static
+*/
+class ADODB_Session {
+ /////////////////////
+ // getter/setter methods
+ /////////////////////
+
+ /*
+
+ function Lock($lock=null)
+ {
+ static $_lock = false;
+
+ if (!is_null($lock)) $_lock = $lock;
+ return $lock;
+ }
+ */
+ /*!
+ */
+ function driver($driver = null) {
+ static $_driver = 'mysql';
+ static $set = false;
+
+ if (!is_null($driver)) {
+ $_driver = trim($driver);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_DRIVER'])) {
+ return $GLOBALS['ADODB_SESSION_DRIVER'];
+ }
+ }
+
+ return $_driver;
+ }
+
+ /*!
+ */
+ function host($host = null) {
+ static $_host = 'localhost';
+ static $set = false;
+
+ if (!is_null($host)) {
+ $_host = trim($host);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_CONNECT'])) {
+ return $GLOBALS['ADODB_SESSION_CONNECT'];
+ }
+ }
+
+ return $_host;
+ }
+
+ /*!
+ */
+ function user($user = null) {
+ static $_user = 'root';
+ static $set = false;
+
+ if (!is_null($user)) {
+ $_user = trim($user);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_USER'])) {
+ return $GLOBALS['ADODB_SESSION_USER'];
+ }
+ }
+
+ return $_user;
+ }
+
+ /*!
+ */
+ function password($password = null) {
+ static $_password = '';
+ static $set = false;
+
+ if (!is_null($password)) {
+ $_password = $password;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_PWD'])) {
+ return $GLOBALS['ADODB_SESSION_PWD'];
+ }
+ }
+
+ return $_password;
+ }
+
+ /*!
+ */
+ function database($database = null) {
+ static $_database = 'xphplens_2';
+ static $set = false;
+
+ if (!is_null($database)) {
+ $_database = trim($database);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_DB'])) {
+ return $GLOBALS['ADODB_SESSION_DB'];
+ }
+ }
+
+ return $_database;
+ }
+
+ /*!
+ */
+ function persist($persist = null)
+ {
+ static $_persist = true;
+
+ if (!is_null($persist)) {
+ $_persist = trim($persist);
+ }
+
+ return $_persist;
+ }
+
+ /*!
+ */
+ function lifetime($lifetime = null) {
+ static $_lifetime;
+ static $set = false;
+
+ if (!is_null($lifetime)) {
+ $_lifetime = (int) $lifetime;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESS_LIFE'])) {
+ return $GLOBALS['ADODB_SESS_LIFE'];
+ }
+ }
+ if (!$_lifetime) {
+ $_lifetime = ini_get('session.gc_maxlifetime');
+ if ($_lifetime <= 1) {
+ // bug in PHP 4.0.3 pl 1 -- how about other versions?
+ //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $lifetime</h3>";
+ $_lifetime = 1440;
+ }
+ }
+
+ return $_lifetime;
+ }
+
+ /*!
+ */
+ function debug($debug = null) {
+ static $_debug = false;
+ static $set = false;
+
+ if (!is_null($debug)) {
+ $_debug = (bool) $debug;
+
+ $conn = ADODB_Session::_conn();
+ if ($conn) {
+ $conn->debug = $_debug;
+ }
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESS_DEBUG'])) {
+ return $GLOBALS['ADODB_SESS_DEBUG'];
+ }
+ }
+
+ return $_debug;
+ }
+
+ /*!
+ */
+ function expireNotify($expire_notify = null) {
+ static $_expire_notify;
+ static $set = false;
+
+ if (!is_null($expire_notify)) {
+ $_expire_notify = $expire_notify;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_EXPIRE_NOTIFY'])) {
+ return $GLOBALS['ADODB_SESSION_EXPIRE_NOTIFY'];
+ }
+ }
+
+ return $_expire_notify;
+ }
+
+ /*!
+ */
+ function table($table = null) {
+ static $_table = 'sessions';
+ static $set = false;
+
+ if (!is_null($table)) {
+ $_table = trim($table);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_TBL'])) {
+ return $GLOBALS['ADODB_SESSION_TBL'];
+ }
+ }
+
+ return $_table;
+ }
+
+ /*!
+ */
+ function optimize($optimize = null) {
+ static $_optimize = false;
+ static $set = false;
+
+ if (!is_null($optimize)) {
+ $_optimize = (bool) $optimize;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (defined('ADODB_SESSION_OPTIMIZE')) {
+ return true;
+ }
+ }
+
+ return $_optimize;
+ }
+
+ /*!
+ */
+ function syncSeconds($sync_seconds = null) {
+ static $_sync_seconds = 60;
+ static $set = false;
+
+ if (!is_null($sync_seconds)) {
+ $_sync_seconds = (int) $sync_seconds;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (defined('ADODB_SESSION_SYNCH_SECS')) {
+ return ADODB_SESSION_SYNCH_SECS;
+ }
+ }
+
+ return $_sync_seconds;
+ }
+
+ /*!
+ */
+ function clob($clob = null) {
+ static $_clob = false;
+ static $set = false;
+
+ if (!is_null($clob)) {
+ $_clob = strtolower(trim($clob));
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_USE_LOBS'])) {
+ return $GLOBALS['ADODB_SESSION_USE_LOBS'];
+ }
+ }
+
+ return $_clob;
+ }
+
+ /*!
+ */
+ function dataFieldName($data_field_name = null) {
+ static $_data_field_name = 'data';
+
+ if (!is_null($data_field_name)) {
+ $_data_field_name = trim($data_field_name);
+ }
+
+ return $_data_field_name;
+ }
+
+ /*!
+ */
+ function filter($filter = null) {
+ static $_filter = array();
+
+ if (!is_null($filter)) {
+ if (!is_array($filter)) {
+ $filter = array($filter);
+ }
+ $_filter = $filter;
+ }
+
+ return $_filter;
+ }
+
+ /*!
+ */
+ function encryptionKey($encryption_key = null) {
+ static $_encryption_key = 'CRYPTED ADODB SESSIONS ROCK!';
+
+ if (!is_null($encryption_key)) {
+ $_encryption_key = $encryption_key;
+ }
+
+ return $_encryption_key;
+ }
+
+ /////////////////////
+ // private methods
+ /////////////////////
+
+ /*!
+ */
+ function _conn($conn=null) {
+ return $GLOBALS['ADODB_SESS_CONN'];
+ }
+
+ /*!
+ */
+ function _crc($crc = null) {
+ static $_crc = false;
+
+ if (!is_null($crc)) {
+ $_crc = $crc;
+ }
+
+ return $_crc;
+ }
+
+ /*!
+ */
+ function _init() {
+ session_module_name('user');
+ session_set_save_handler(
+ array('ADODB_Session', 'open'),
+ array('ADODB_Session', 'close'),
+ array('ADODB_Session', 'read'),
+ array('ADODB_Session', 'write'),
+ array('ADODB_Session', 'destroy'),
+ array('ADODB_Session', 'gc')
+ );
+ }
+
+
+ /*!
+ */
+ function _sessionKey() {
+ // use this function to create the encryption key for crypted sessions
+ // crypt the used key, ADODB_Session::encryptionKey() as key and session_id() as salt
+ return crypt(ADODB_Session::encryptionKey(), session_id());
+ }
+
+ /*!
+ */
+ function _dumprs($rs) {
+ $conn = ADODB_Session::_conn();
+ $debug = ADODB_Session::debug();
+
+ if (!$conn) {
+ return;
+ }
+
+ if (!$debug) {
+ return;
+ }
+
+ if (!$rs) {
+ echo "<br />\$rs is null or false<br />\n";
+ return;
+ }
+
+ //echo "<br />\nAffected_Rows=",$conn->Affected_Rows(),"<br />\n";
+
+ if (!is_object($rs)) {
+ return;
+ }
+
+ require_once ADODB_SESSION.'/../tohtml.inc.php';
+ rs2html($rs);
+ }
+
+ /////////////////////
+ // public methods
+ /////////////////////
+
+ function config($driver, $host, $user, $password, $database=false,$options=false)
+ {
+ ADODB_Session::driver($driver);
+ ADODB_Session::host($host);
+ ADODB_Session::user($user);
+ ADODB_Session::password($password);
+ ADODB_Session::database($database);
+
+ if ($driver == 'oci8' || $driver == 'oci8po') $options['lob'] = 'CLOB';
+
+ if (isset($options['table'])) ADODB_Session::table($options['table']);
+ if (isset($options['lob'])) ADODB_Session::clob($options['lob']);
+ if (isset($options['debug'])) ADODB_Session::debug($options['debug']);
+ }
+
+ /*!
+ Create the connection to the database.
+
+ If $conn already exists, reuse that connection
+ */
+ function open($save_path, $session_name, $persist = null)
+ {
+ $conn = ADODB_Session::_conn();
+
+ if ($conn) {
+ return true;
+ }
+
+ $database = ADODB_Session::database();
+ $debug = ADODB_Session::debug();
+ $driver = ADODB_Session::driver();
+ $host = ADODB_Session::host();
+ $password = ADODB_Session::password();
+ $user = ADODB_Session::user();
+
+ if (!is_null($persist)) {
+ ADODB_Session::persist($persist);
+ } else {
+ $persist = ADODB_Session::persist();
+ }
+
+# these can all be defaulted to in php.ini
+# assert('$database');
+# assert('$driver');
+# assert('$host');
+
+ $conn = ADONewConnection($driver);
+
+ if ($debug) {
+ $conn->debug = true;
+// ADOConnection::outp( " driver=$driver user=$user pwd=$password db=$database ");
+ }
+
+ if ($persist) {
+ switch($persist) {
+ default:
+ case 'P': $ok = $conn->PConnect($host, $user, $password, $database); break;
+ case 'C': $ok = $conn->Connect($host, $user, $password, $database); break;
+ case 'N': $ok = $conn->NConnect($host, $user, $password, $database); break;
+ }
+ } else {
+ $ok = $conn->Connect($host, $user, $password, $database);
+ }
+
+ if ($ok) $GLOBALS['ADODB_SESS_CONN'] = $conn;
+ else
+ ADOConnection::outp('<p>Session: connection failed</p>', false);
+
+
+ return $ok;
+ }
+
+ /*!
+ Close the connection
+ */
+ function close()
+ {
+/*
+ $conn = ADODB_Session::_conn();
+ if ($conn) $conn->Close();
+*/
+ return true;
+ }
+
+ /*
+ Slurp in the session variables and return the serialized string
+ */
+ function read($key)
+ {
+ $conn = ADODB_Session::_conn();
+ $data = ADODB_Session::dataFieldName();
+ $filter = ADODB_Session::filter();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return '';
+ }
+
+ //assert('$table');
+
+ $qkey = $conn->quote($key);
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ $sql = "SELECT $data FROM $table WHERE sesskey = $binary $qkey AND expiry >= " . time();
+ /* Lock code does not work as it needs to hold transaction within whole page, and we don't know if
+ developer has commited elsewhere... :(
+ */
+ #if (ADODB_Session::Lock())
+ # $rs = $conn->RowLock($table, "$binary sesskey = $qkey AND expiry >= " . time(), $data);
+ #else
+
+ $rs = $conn->Execute($sql);
+ //ADODB_Session::_dumprs($rs);
+ if ($rs) {
+ if ($rs->EOF) {
+ $v = '';
+ } else {
+ $v = reset($rs->fields);
+ $filter = array_reverse($filter);
+ foreach ($filter as $f) {
+ if (is_object($f)) {
+ $v = $f->read($v, ADODB_Session::_sessionKey());
+ }
+ }
+ $v = rawurldecode($v);
+ }
+
+ $rs->Close();
+
+ ADODB_Session::_crc(strlen($v) . crc32($v));
+ return $v;
+ }
+
+ return '';
+ }
+
+ /*!
+ Write the serialized data to a database.
+
+ If the data has not been modified since the last read(), we do not write.
+ */
+ function write($key, $val)
+ {
+ global $ADODB_SESSION_READONLY;
+
+ if (!empty($ADODB_SESSION_READONLY)) return;
+
+ $clob = ADODB_Session::clob();
+ $conn = ADODB_Session::_conn();
+ $crc = ADODB_Session::_crc();
+ $data = ADODB_Session::dataFieldName();
+ $debug = ADODB_Session::debug();
+ $driver = ADODB_Session::driver();
+ $expire_notify = ADODB_Session::expireNotify();
+ $filter = ADODB_Session::filter();
+ $lifetime = ADODB_Session::lifetime();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return false;
+ }
+ $qkey = $conn->qstr($key);
+
+ //assert('$table');
+
+ $expiry = time() + $lifetime;
+
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ // crc32 optimization since adodb 2.1
+ // now we only update expiry date, thx to sebastian thom in adodb 2.32
+ if ($crc !== false && $crc == (strlen($val) . crc32($val))) {
+ if ($debug) {
+ ADOConnection::outp( '<p>Session: Only updating date - crc32 not changed</p>');
+ }
+
+ $expirevar = '';
+ if ($expire_notify) {
+ $var = reset($expire_notify);
+ global $$var;
+ if (isset($$var)) {
+ $expirevar = $$var;
+ }
+ }
+
+
+ $sql = "UPDATE $table SET expiry = ".$conn->Param('0').",expireref=".$conn->Param('1')." WHERE $binary sesskey = ".$conn->Param('2')." AND expiry >= ".$conn->Param('3');
+ $rs = $conn->Execute($sql,array($expiry,$expirevar,$key,time()));
+ return true;
+ }
+ $val = rawurlencode($val);
+ foreach ($filter as $f) {
+ if (is_object($f)) {
+ $val = $f->write($val, ADODB_Session::_sessionKey());
+ }
+ }
+
+ $arr = array('sesskey' => $key, 'expiry' => $expiry, $data => $val, 'expireref' => '');
+ if ($expire_notify) {
+ $var = reset($expire_notify);
+ global $$var;
+ if (isset($$var)) {
+ $arr['expireref'] = $$var;
+ }
+ }
+
+ if (!$clob) { // no lobs, simply use replace()
+ $arr[$data] = $val;
+ $rs = $conn->Replace($table, $arr, 'sesskey', $autoQuote = true);
+
+ } else {
+ // what value shall we insert/update for lob row?
+ switch ($driver) {
+ // empty_clob or empty_lob for oracle dbs
+ case 'oracle':
+ case 'oci8':
+ case 'oci8po':
+ case 'oci805':
+ $lob_value = sprintf('empty_%s()', strtolower($clob));
+ break;
+
+ // null for all other
+ default:
+ $lob_value = 'null';
+ break;
+ }
+
+ $conn->StartTrans();
+ $expiryref = $conn->qstr($arr['expireref']);
+ // do we insert or update? => as for sesskey
+ $rs = $conn->Execute("SELECT COUNT(*) AS cnt FROM $table WHERE $binary sesskey = $qkey");
+ if ($rs && reset($rs->fields) > 0) {
+ $sql = "UPDATE $table SET expiry = $expiry, $data = $lob_value, expireref=$expiryref WHERE sesskey = $qkey";
+ } else {
+ $sql = "INSERT INTO $table (expiry, $data, sesskey,expireref) VALUES ($expiry, $lob_value, $qkey,$expiryref)";
+ }
+ if ($rs)$rs->Close();
+
+
+ $err = '';
+ $rs1 = $conn->Execute($sql);
+ if (!$rs1) $err = $conn->ErrorMsg()."\n";
+
+ $rs2 = $conn->UpdateBlob($table, $data, $val, " sesskey=$qkey", strtoupper($clob));
+ if (!$rs2) $err .= $conn->ErrorMsg()."\n";
+
+ $rs = ($rs && $rs2) ? true : false;
+ $conn->CompleteTrans();
+ }
+
+ if (!$rs) {
+ ADOConnection::outp('<p>Session Replace: ' . $conn->ErrorMsg() . '</p>', false);
+ return false;
+ } else {
+ // bug in access driver (could be odbc?) means that info is not committed
+ // properly unless select statement executed in Win2000
+ if ($conn->databaseType == 'access') {
+ $sql = "SELECT sesskey FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ if ($rs) {
+ $rs->Close();
+ }
+ }
+ }/*
+ if (ADODB_Session::Lock()) {
+ $conn->CommitTrans();
+ }*/
+ return $rs ? true : false;
+ }
+
+ /*!
+ */
+ function destroy($key) {
+ $conn = ADODB_Session::_conn();
+ $table = ADODB_Session::table();
+ $expire_notify = ADODB_Session::expireNotify();
+
+ if (!$conn) {
+ return false;
+ }
+
+ //assert('$table');
+
+ $qkey = $conn->quote($key);
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ if ($expire_notify) {
+ reset($expire_notify);
+ $fn = next($expire_notify);
+ $savem = $conn->SetFetchMode(ADODB_FETCH_NUM);
+ $sql = "SELECT expireref, sesskey FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ $conn->SetFetchMode($savem);
+ if (!$rs) {
+ return false;
+ }
+ if (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ //assert('$ref');
+ //assert('$key');
+ $fn($ref, $key);
+ }
+ $rs->Close();
+ }
+
+ $sql = "DELETE FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+
+ return $rs ? true : false;
+ }
+
+ /*!
+ */
+ function gc($maxlifetime)
+ {
+ $conn = ADODB_Session::_conn();
+ $debug = ADODB_Session::debug();
+ $expire_notify = ADODB_Session::expireNotify();
+ $optimize = ADODB_Session::optimize();
+ $sync_seconds = ADODB_Session::syncSeconds();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return false;
+ }
+
+
+ $time = time();
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ if ($expire_notify) {
+ reset($expire_notify);
+ $fn = next($expire_notify);
+ $savem = $conn->SetFetchMode(ADODB_FETCH_NUM);
+ $sql = "SELECT expireref, sesskey FROM $table WHERE expiry < $time";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ $conn->SetFetchMode($savem);
+ if ($rs) {
+ $conn->StartTrans();
+ $keys = array();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref, $key);
+ $del = $conn->Execute("DELETE FROM $table WHERE sesskey=".$conn->Param('0'),array($key));
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $conn->CompleteTrans();
+ }
+ } else {
+
+ if (1) {
+ $sql = "SELECT sesskey FROM $table WHERE expiry < $time";
+ $arr = $conn->GetAll($sql);
+ foreach ($arr as $row) {
+ $sql2 = "DELETE FROM $table WHERE sesskey=".$conn->Param('0');
+ $conn->Execute($sql2,array(reset($row)));
+ }
+ } else {
+ $sql = "DELETE FROM $table WHERE expiry < $time";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ if ($rs) $rs->Close();
+ }
+ if ($debug) {
+ ADOConnection::outp("<p><b>Garbage Collection</b>: $sql</p>");
+ }
+ }
+
+ // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
+ if ($optimize) {
+ $driver = ADODB_Session::driver();
+
+ if (preg_match('/mysql/i', $driver)) {
+ $sql = "OPTIMIZE TABLE $table";
+ }
+ if (preg_match('/postgres/i', $driver)) {
+ $sql = "VACUUM $table";
+ }
+ if (!empty($sql)) {
+ $conn->Execute($sql);
+ }
+ }
+
+ if ($sync_seconds) {
+ $sql = 'SELECT ';
+ if ($conn->dataProvider === 'oci8') {
+ $sql .= "TO_CHAR({$conn->sysTimeStamp}, 'RRRR-MM-DD HH24:MI:SS')";
+ } else {
+ $sql .= $conn->sysTimeStamp;
+ }
+ $sql .= " FROM $table";
+
+ $rs = $conn->SelectLimit($sql, 1);
+ if ($rs && !$rs->EOF) {
+ $dbts = reset($rs->fields);
+ $rs->Close();
+ $dbt = $conn->UnixTimeStamp($dbts);
+ $t = time();
+
+ if (abs($dbt - $t) >= $sync_seconds) {
+ $msg = __FILE__ .
+ ": Server time for webserver {$_SERVER['HTTP_HOST']} not in synch with database: " .
+ " database=$dbt ($dbts), webserver=$t (diff=". (abs($dbt - $t) / 60) . ' minutes)';
+ error_log($msg);
+ if ($debug) {
+ ADOConnection::outp("<p>$msg</p>");
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+}
+
+ADODB_Session::_init();
+if (empty($ADODB_SESSION_READONLY))
+ register_shutdown_function('session_write_close');
+
+// for backwards compatability only
+function adodb_sess_open($save_path, $session_name, $persist = true) {
+ return ADODB_Session::open($save_path, $session_name, $persist);
+}
+
+// for backwards compatability only
+function adodb_sess_gc($t)
+{
+ return ADODB_Session::gc($t);
+}
diff --git a/vendor/adodb/adodb-php/session/adodb-session2.php b/vendor/adodb/adodb-php/session/adodb-session2.php
new file mode 100644
index 0000000..bb82178
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-session2.php
@@ -0,0 +1,939 @@
+<?php
+
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Contributed by Ross Smith (adodb@netebb.com).
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+
+*/
+
+/*
+
+CREATE Table SCripts
+
+Oracle
+======
+
+CREATE TABLE SESSIONS2
+(
+ SESSKEY VARCHAR2(48 BYTE) NOT NULL,
+ EXPIRY DATE NOT NULL,
+ EXPIREREF VARCHAR2(200 BYTE),
+ CREATED DATE NOT NULL,
+ MODIFIED DATE NOT NULL,
+ SESSDATA CLOB,
+ PRIMARY KEY(SESSKEY)
+);
+
+
+CREATE INDEX SESS2_EXPIRY ON SESSIONS2(EXPIRY);
+CREATE UNIQUE INDEX SESS2_PK ON SESSIONS2(SESSKEY);
+CREATE INDEX SESS2_EXP_REF ON SESSIONS2(EXPIREREF);
+
+
+
+ MySQL
+ =====
+
+CREATE TABLE sessions2(
+ sesskey VARCHAR( 64 ) NOT NULL DEFAULT '',
+ expiry TIMESTAMP NOT NULL ,
+ expireref VARCHAR( 250 ) DEFAULT '',
+ created TIMESTAMP NOT NULL ,
+ modified TIMESTAMP NOT NULL ,
+ sessdata LONGTEXT DEFAULT '',
+ PRIMARY KEY ( sesskey ) ,
+ INDEX sess2_expiry( expiry ),
+ INDEX sess2_expireref( expireref )
+)
+
+
+*/
+
+if (!defined('_ADODB_LAYER')) {
+ require realpath(dirname(__FILE__) . '/../adodb.inc.php');
+}
+
+if (defined('ADODB_SESSION')) return 1;
+
+define('ADODB_SESSION', dirname(__FILE__));
+define('ADODB_SESSION2', ADODB_SESSION);
+
+/*
+ Unserialize session data manually. See http://phplens.com/lens/lensforum/msgs.php?id=9821
+
+ From Kerr Schere, to unserialize session data stored via ADOdb.
+ 1. Pull the session data from the db and loop through it.
+ 2. Inside the loop, you will need to urldecode the data column.
+ 3. After urldecode, run the serialized string through this function:
+
+*/
+function adodb_unserialize( $serialized_string )
+{
+ $variables = array( );
+ $a = preg_split( "/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
+ for( $i = 0; $i < count( $a ); $i = $i+2 ) {
+ $variables[$a[$i]] = unserialize( $a[$i+1] );
+ }
+ return( $variables );
+}
+
+/*
+ Thanks Joe Li. See http://phplens.com/lens/lensforum/msgs.php?id=11487&x=1
+ Since adodb 4.61.
+*/
+function adodb_session_regenerate_id()
+{
+ $conn = ADODB_Session::_conn();
+ if (!$conn) return false;
+
+ $old_id = session_id();
+ if (function_exists('session_regenerate_id')) {
+ session_regenerate_id();
+ } else {
+ session_id(md5(uniqid(rand(), true)));
+ $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ //@session_start();
+ }
+ $new_id = session_id();
+ $ok = $conn->Execute('UPDATE '. ADODB_Session::table(). ' SET sesskey='. $conn->qstr($new_id). ' WHERE sesskey='.$conn->qstr($old_id));
+
+ /* it is possible that the update statement fails due to a collision */
+ if (!$ok) {
+ session_id($old_id);
+ if (empty($ck)) $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ Generate database table for session data
+ @see http://phplens.com/lens/lensforum/msgs.php?id=12280
+ @return 0 if failure, 1 if errors, 2 if successful.
+ @author Markus Staab http://www.public-4u.de
+*/
+function adodb_session_create_table($schemaFile=null,$conn = null)
+{
+ // set default values
+ if ($schemaFile===null) $schemaFile = ADODB_SESSION . '/session_schema2.xml';
+ if ($conn===null) $conn = ADODB_Session::_conn();
+
+ if (!$conn) return 0;
+
+ $schema = new adoSchema($conn);
+ $schema->ParseSchema($schemaFile);
+ return $schema->ExecuteSchema();
+}
+
+/*!
+ \static
+*/
+class ADODB_Session {
+ /////////////////////
+ // getter/setter methods
+ /////////////////////
+
+ /*
+
+ function Lock($lock=null)
+ {
+ static $_lock = false;
+
+ if (!is_null($lock)) $_lock = $lock;
+ return $lock;
+ }
+ */
+ /*!
+ */
+ static function driver($driver = null)
+ {
+ static $_driver = 'mysql';
+ static $set = false;
+
+ if (!is_null($driver)) {
+ $_driver = trim($driver);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_DRIVER'])) {
+ return $GLOBALS['ADODB_SESSION_DRIVER'];
+ }
+ }
+
+ return $_driver;
+ }
+
+ /*!
+ */
+ static function host($host = null) {
+ static $_host = 'localhost';
+ static $set = false;
+
+ if (!is_null($host)) {
+ $_host = trim($host);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_CONNECT'])) {
+ return $GLOBALS['ADODB_SESSION_CONNECT'];
+ }
+ }
+
+ return $_host;
+ }
+
+ /*!
+ */
+ static function user($user = null)
+ {
+ static $_user = 'root';
+ static $set = false;
+
+ if (!is_null($user)) {
+ $_user = trim($user);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_USER'])) {
+ return $GLOBALS['ADODB_SESSION_USER'];
+ }
+ }
+
+ return $_user;
+ }
+
+ /*!
+ */
+ static function password($password = null)
+ {
+ static $_password = '';
+ static $set = false;
+
+ if (!is_null($password)) {
+ $_password = $password;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_PWD'])) {
+ return $GLOBALS['ADODB_SESSION_PWD'];
+ }
+ }
+
+ return $_password;
+ }
+
+ /*!
+ */
+ static function database($database = null)
+ {
+ static $_database = '';
+ static $set = false;
+
+ if (!is_null($database)) {
+ $_database = trim($database);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_DB'])) {
+ return $GLOBALS['ADODB_SESSION_DB'];
+ }
+ }
+ return $_database;
+ }
+
+ /*!
+ */
+ static function persist($persist = null)
+ {
+ static $_persist = true;
+
+ if (!is_null($persist)) {
+ $_persist = trim($persist);
+ }
+
+ return $_persist;
+ }
+
+ /*!
+ */
+ static function lifetime($lifetime = null)
+ {
+ static $_lifetime;
+ static $set = false;
+
+ if (!is_null($lifetime)) {
+ $_lifetime = (int) $lifetime;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESS_LIFE'])) {
+ return $GLOBALS['ADODB_SESS_LIFE'];
+ }
+ }
+ if (!$_lifetime) {
+ $_lifetime = ini_get('session.gc_maxlifetime');
+ if ($_lifetime <= 1) {
+ // bug in PHP 4.0.3 pl 1 -- how about other versions?
+ //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $lifetime</h3>";
+ $_lifetime = 1440;
+ }
+ }
+
+ return $_lifetime;
+ }
+
+ /*!
+ */
+ static function debug($debug = null)
+ {
+ static $_debug = false;
+ static $set = false;
+
+ if (!is_null($debug)) {
+ $_debug = (bool) $debug;
+
+ $conn = ADODB_Session::_conn();
+ if ($conn) {
+ #$conn->debug = $_debug;
+ }
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESS_DEBUG'])) {
+ return $GLOBALS['ADODB_SESS_DEBUG'];
+ }
+ }
+
+ return $_debug;
+ }
+
+ /*!
+ */
+ static function expireNotify($expire_notify = null)
+ {
+ static $_expire_notify;
+ static $set = false;
+
+ if (!is_null($expire_notify)) {
+ $_expire_notify = $expire_notify;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_EXPIRE_NOTIFY'])) {
+ return $GLOBALS['ADODB_SESSION_EXPIRE_NOTIFY'];
+ }
+ }
+
+ return $_expire_notify;
+ }
+
+ /*!
+ */
+ static function table($table = null)
+ {
+ static $_table = 'sessions2';
+ static $set = false;
+
+ if (!is_null($table)) {
+ $_table = trim($table);
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_TBL'])) {
+ return $GLOBALS['ADODB_SESSION_TBL'];
+ }
+ }
+
+ return $_table;
+ }
+
+ /*!
+ */
+ static function optimize($optimize = null)
+ {
+ static $_optimize = false;
+ static $set = false;
+
+ if (!is_null($optimize)) {
+ $_optimize = (bool) $optimize;
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (defined('ADODB_SESSION_OPTIMIZE')) {
+ return true;
+ }
+ }
+
+ return $_optimize;
+ }
+
+ /*!
+ */
+ static function syncSeconds($sync_seconds = null) {
+ //echo ("<p>WARNING: ADODB_SESSION::syncSeconds is longer used, please remove this function for your code</p>");
+
+ return 0;
+ }
+
+ /*!
+ */
+ static function clob($clob = null) {
+ static $_clob = false;
+ static $set = false;
+
+ if (!is_null($clob)) {
+ $_clob = strtolower(trim($clob));
+ $set = true;
+ } elseif (!$set) {
+ // backwards compatibility
+ if (isset($GLOBALS['ADODB_SESSION_USE_LOBS'])) {
+ return $GLOBALS['ADODB_SESSION_USE_LOBS'];
+ }
+ }
+
+ return $_clob;
+ }
+
+ /*!
+ */
+ static function dataFieldName($data_field_name = null) {
+ //echo ("<p>WARNING: ADODB_SESSION::dataFieldName() is longer used, please remove this function for your code</p>");
+ return '';
+ }
+
+ /*!
+ */
+ static function filter($filter = null) {
+ static $_filter = array();
+
+ if (!is_null($filter)) {
+ if (!is_array($filter)) {
+ $filter = array($filter);
+ }
+ $_filter = $filter;
+ }
+
+ return $_filter;
+ }
+
+ /*!
+ */
+ static function encryptionKey($encryption_key = null) {
+ static $_encryption_key = 'CRYPTED ADODB SESSIONS ROCK!';
+
+ if (!is_null($encryption_key)) {
+ $_encryption_key = $encryption_key;
+ }
+
+ return $_encryption_key;
+ }
+
+ /////////////////////
+ // private methods
+ /////////////////////
+
+ /*!
+ */
+ static function _conn($conn=null) {
+ return isset($GLOBALS['ADODB_SESS_CONN']) ? $GLOBALS['ADODB_SESS_CONN'] : false;
+ }
+
+ /*!
+ */
+ static function _crc($crc = null) {
+ static $_crc = false;
+
+ if (!is_null($crc)) {
+ $_crc = $crc;
+ }
+
+ return $_crc;
+ }
+
+ /*!
+ */
+ static function _init() {
+ session_module_name('user');
+ session_set_save_handler(
+ array('ADODB_Session', 'open'),
+ array('ADODB_Session', 'close'),
+ array('ADODB_Session', 'read'),
+ array('ADODB_Session', 'write'),
+ array('ADODB_Session', 'destroy'),
+ array('ADODB_Session', 'gc')
+ );
+ }
+
+
+ /*!
+ */
+ static function _sessionKey() {
+ // use this function to create the encryption key for crypted sessions
+ // crypt the used key, ADODB_Session::encryptionKey() as key and session_id() as salt
+ return crypt(ADODB_Session::encryptionKey(), session_id());
+ }
+
+ /*!
+ */
+ static function _dumprs(&$rs) {
+ $conn = ADODB_Session::_conn();
+ $debug = ADODB_Session::debug();
+
+ if (!$conn) {
+ return;
+ }
+
+ if (!$debug) {
+ return;
+ }
+
+ if (!$rs) {
+ echo "<br />\$rs is null or false<br />\n";
+ return;
+ }
+
+ //echo "<br />\nAffected_Rows=",$conn->Affected_Rows(),"<br />\n";
+
+ if (!is_object($rs)) {
+ return;
+ }
+ $rs = $conn->_rs2rs($rs);
+
+ require_once ADODB_SESSION.'/../tohtml.inc.php';
+ rs2html($rs);
+ $rs->MoveFirst();
+ }
+
+ /////////////////////
+ // public methods
+ /////////////////////
+
+ static function config($driver, $host, $user, $password, $database=false,$options=false)
+ {
+ ADODB_Session::driver($driver);
+ ADODB_Session::host($host);
+ ADODB_Session::user($user);
+ ADODB_Session::password($password);
+ ADODB_Session::database($database);
+
+ if (strncmp($driver, 'oci8', 4) == 0) $options['lob'] = 'CLOB';
+
+ if (isset($options['table'])) ADODB_Session::table($options['table']);
+ if (isset($options['lob'])) ADODB_Session::clob($options['lob']);
+ if (isset($options['debug'])) ADODB_Session::debug($options['debug']);
+ }
+
+ /*!
+ Create the connection to the database.
+
+ If $conn already exists, reuse that connection
+ */
+ static function open($save_path, $session_name, $persist = null)
+ {
+ $conn = ADODB_Session::_conn();
+
+ if ($conn) {
+ return true;
+ }
+
+ $database = ADODB_Session::database();
+ $debug = ADODB_Session::debug();
+ $driver = ADODB_Session::driver();
+ $host = ADODB_Session::host();
+ $password = ADODB_Session::password();
+ $user = ADODB_Session::user();
+
+ if (!is_null($persist)) {
+ ADODB_Session::persist($persist);
+ } else {
+ $persist = ADODB_Session::persist();
+ }
+
+# these can all be defaulted to in php.ini
+# assert('$database');
+# assert('$driver');
+# assert('$host');
+
+ $conn = ADONewConnection($driver);
+
+ if ($debug) {
+ $conn->debug = true;
+ ADOConnection::outp( " driver=$driver user=$user db=$database ");
+ }
+
+ if (empty($conn->_connectionID)) { // not dsn
+ if ($persist) {
+ switch($persist) {
+ default:
+ case 'P': $ok = $conn->PConnect($host, $user, $password, $database); break;
+ case 'C': $ok = $conn->Connect($host, $user, $password, $database); break;
+ case 'N': $ok = $conn->NConnect($host, $user, $password, $database); break;
+ }
+ } else {
+ $ok = $conn->Connect($host, $user, $password, $database);
+ }
+ } else {
+ $ok = true; // $conn->_connectionID is set after call to ADONewConnection
+ }
+
+ if ($ok) $GLOBALS['ADODB_SESS_CONN'] = $conn;
+ else
+ ADOConnection::outp('<p>Session: connection failed</p>', false);
+
+
+ return $ok;
+ }
+
+ /*!
+ Close the connection
+ */
+ static function close()
+ {
+/*
+ $conn = ADODB_Session::_conn();
+ if ($conn) $conn->Close();
+*/
+ return true;
+ }
+
+ /*
+ Slurp in the session variables and return the serialized string
+ */
+ static function read($key)
+ {
+ $conn = ADODB_Session::_conn();
+ $filter = ADODB_Session::filter();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return '';
+ }
+
+ //assert('$table');
+
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ global $ADODB_SESSION_SELECT_FIELDS;
+ if (!isset($ADODB_SESSION_SELECT_FIELDS)) $ADODB_SESSION_SELECT_FIELDS = 'sessdata';
+ $sql = "SELECT $ADODB_SESSION_SELECT_FIELDS FROM $table WHERE sesskey = $binary ".$conn->Param(0)." AND expiry >= " . $conn->sysTimeStamp;
+
+ /* Lock code does not work as it needs to hold transaction within whole page, and we don't know if
+ developer has commited elsewhere... :(
+ */
+ #if (ADODB_Session::Lock())
+ # $rs = $conn->RowLock($table, "$binary sesskey = $qkey AND expiry >= " . time(), sessdata);
+ #else
+ $rs = $conn->Execute($sql, array($key));
+ //ADODB_Session::_dumprs($rs);
+ if ($rs) {
+ if ($rs->EOF) {
+ $v = '';
+ } else {
+ $v = reset($rs->fields);
+ $filter = array_reverse($filter);
+ foreach ($filter as $f) {
+ if (is_object($f)) {
+ $v = $f->read($v, ADODB_Session::_sessionKey());
+ }
+ }
+ $v = rawurldecode($v);
+ }
+
+ $rs->Close();
+
+ ADODB_Session::_crc(strlen($v) . crc32($v));
+ return $v;
+ }
+
+ return '';
+ }
+
+ /*!
+ Write the serialized data to a database.
+
+ If the data has not been modified since the last read(), we do not write.
+ */
+ static function write($key, $oval)
+ {
+ global $ADODB_SESSION_READONLY;
+
+ if (!empty($ADODB_SESSION_READONLY)) return;
+
+ $clob = ADODB_Session::clob();
+ $conn = ADODB_Session::_conn();
+ $crc = ADODB_Session::_crc();
+ $debug = ADODB_Session::debug();
+ $driver = ADODB_Session::driver();
+ $expire_notify = ADODB_Session::expireNotify();
+ $filter = ADODB_Session::filter();
+ $lifetime = ADODB_Session::lifetime();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return false;
+ }
+ if ($debug) $conn->debug = 1;
+ $sysTimeStamp = $conn->sysTimeStamp;
+
+ //assert('$table');
+
+ $expiry = $conn->OffsetDate($lifetime/(24*3600),$sysTimeStamp);
+
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ // crc32 optimization since adodb 2.1
+ // now we only update expiry date, thx to sebastian thom in adodb 2.32
+ if ($crc !== '00' && $crc !== false && $crc == (strlen($oval) . crc32($oval))) {
+ if ($debug) {
+ echo '<p>Session: Only updating date - crc32 not changed</p>';
+ }
+
+ $expirevar = '';
+ if ($expire_notify) {
+ $var = reset($expire_notify);
+ global $$var;
+ if (isset($$var)) {
+ $expirevar = $$var;
+ }
+ }
+
+
+ $sql = "UPDATE $table SET expiry = $expiry ,expireref=".$conn->Param('0').", modified = $sysTimeStamp WHERE $binary sesskey = ".$conn->Param('1')." AND expiry >= $sysTimeStamp";
+ $rs = $conn->Execute($sql,array($expirevar,$key));
+ return true;
+ }
+ $val = rawurlencode($oval);
+ foreach ($filter as $f) {
+ if (is_object($f)) {
+ $val = $f->write($val, ADODB_Session::_sessionKey());
+ }
+ }
+
+ $expireref = '';
+ if ($expire_notify) {
+ $var = reset($expire_notify);
+ global $$var;
+ if (isset($$var)) {
+ $expireref = $$var;
+ }
+ }
+
+ if (!$clob) { // no lobs, simply use replace()
+ $rs = $conn->Execute("SELECT COUNT(*) AS cnt FROM $table WHERE $binary sesskey = ".$conn->Param(0),array($key));
+ if ($rs) $rs->Close();
+
+ if ($rs && reset($rs->fields) > 0) {
+ $sql = "UPDATE $table SET expiry=$expiry, sessdata=".$conn->Param(0).", expireref= ".$conn->Param(1).",modified=$sysTimeStamp WHERE sesskey = ".$conn->Param(2);
+
+ } else {
+ $sql = "INSERT INTO $table (expiry, sessdata, expireref, sesskey, created, modified)
+ VALUES ($expiry,".$conn->Param('0').", ". $conn->Param('1').", ".$conn->Param('2').", $sysTimeStamp, $sysTimeStamp)";
+ }
+
+
+ $rs = $conn->Execute($sql,array($val,$expireref,$key));
+
+ } else {
+ // what value shall we insert/update for lob row?
+ if (strncmp($driver, 'oci8', 4) == 0) $lob_value = sprintf('empty_%s()', strtolower($clob));
+ else $lob_value = 'null';
+
+ $conn->StartTrans();
+
+ $rs = $conn->Execute("SELECT COUNT(*) AS cnt FROM $table WHERE $binary sesskey = ".$conn->Param(0),array($key));
+
+ if ($rs && reset($rs->fields) > 0) {
+ $sql = "UPDATE $table SET expiry=$expiry, sessdata=$lob_value, expireref= ".$conn->Param(0).",modified=$sysTimeStamp WHERE sesskey = ".$conn->Param('1');
+
+ } else {
+ $sql = "INSERT INTO $table (expiry, sessdata, expireref, sesskey, created, modified)
+ VALUES ($expiry,$lob_value, ". $conn->Param('0').", ".$conn->Param('1').", $sysTimeStamp, $sysTimeStamp)";
+ }
+
+ $rs = $conn->Execute($sql,array($expireref,$key));
+
+ $qkey = $conn->qstr($key);
+ $rs2 = $conn->UpdateBlob($table, 'sessdata', $val, " sesskey=$qkey", strtoupper($clob));
+ if ($debug) echo "<hr>",htmlspecialchars($oval), "<hr>";
+ $rs = @$conn->CompleteTrans();
+
+
+ }
+
+ if (!$rs) {
+ ADOConnection::outp('<p>Session Replace: ' . $conn->ErrorMsg() . '</p>', false);
+ return false;
+ } else {
+ // bug in access driver (could be odbc?) means that info is not committed
+ // properly unless select statement executed in Win2000
+ if ($conn->databaseType == 'access') {
+ $sql = "SELECT sesskey FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ if ($rs) {
+ $rs->Close();
+ }
+ }
+ }/*
+ if (ADODB_Session::Lock()) {
+ $conn->CommitTrans();
+ }*/
+ return $rs ? true : false;
+ }
+
+ /*!
+ */
+ static function destroy($key) {
+ $conn = ADODB_Session::_conn();
+ $table = ADODB_Session::table();
+ $expire_notify = ADODB_Session::expireNotify();
+
+ if (!$conn) {
+ return false;
+ }
+ $debug = ADODB_Session::debug();
+ if ($debug) $conn->debug = 1;
+ //assert('$table');
+
+ $qkey = $conn->quote($key);
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ if ($expire_notify) {
+ reset($expire_notify);
+ $fn = next($expire_notify);
+ $savem = $conn->SetFetchMode(ADODB_FETCH_NUM);
+ $sql = "SELECT expireref, sesskey FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ ADODB_Session::_dumprs($rs);
+ $conn->SetFetchMode($savem);
+ if (!$rs) {
+ return false;
+ }
+ if (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ //assert('$ref');
+ //assert('$key');
+ $fn($ref, $key);
+ }
+ $rs->Close();
+ }
+
+ $sql = "DELETE FROM $table WHERE $binary sesskey = $qkey";
+ $rs = $conn->Execute($sql);
+ if ($rs) {
+ $rs->Close();
+ }
+
+ return $rs ? true : false;
+ }
+
+ /*!
+ */
+ static function gc($maxlifetime)
+ {
+ $conn = ADODB_Session::_conn();
+ $debug = ADODB_Session::debug();
+ $expire_notify = ADODB_Session::expireNotify();
+ $optimize = ADODB_Session::optimize();
+ $table = ADODB_Session::table();
+
+ if (!$conn) {
+ return false;
+ }
+
+
+ $debug = ADODB_Session::debug();
+ if ($debug) {
+ $conn->debug = 1;
+ $COMMITNUM = 2;
+ } else {
+ $COMMITNUM = 20;
+ }
+
+ //assert('$table');
+
+ $time = $conn->OffsetDate(-$maxlifetime/24/3600,$conn->sysTimeStamp);
+ $binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+
+ if ($expire_notify) {
+ reset($expire_notify);
+ $fn = next($expire_notify);
+ } else {
+ $fn = false;
+ }
+
+ $savem = $conn->SetFetchMode(ADODB_FETCH_NUM);
+ $sql = "SELECT expireref, sesskey FROM $table WHERE expiry < $time ORDER BY 2"; # add order by to prevent deadlock
+ $rs = $conn->SelectLimit($sql,1000);
+ if ($debug) ADODB_Session::_dumprs($rs);
+ $conn->SetFetchMode($savem);
+ if ($rs) {
+ $tr = $conn->hasTransactions;
+ if ($tr) $conn->BeginTrans();
+ $keys = array();
+ $ccnt = 0;
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ if ($fn) $fn($ref, $key);
+ $del = $conn->Execute("DELETE FROM $table WHERE sesskey=".$conn->Param('0'),array($key));
+ $rs->MoveNext();
+ $ccnt += 1;
+ if ($tr && $ccnt % $COMMITNUM == 0) {
+ if ($debug) echo "Commit<br>\n";
+ $conn->CommitTrans();
+ $conn->BeginTrans();
+ }
+ }
+ $rs->Close();
+
+ if ($tr) $conn->CommitTrans();
+ }
+
+
+ // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
+ if ($optimize) {
+ $driver = ADODB_Session::driver();
+
+ if (preg_match('/mysql/i', $driver)) {
+ $sql = "OPTIMIZE TABLE $table";
+ }
+ if (preg_match('/postgres/i', $driver)) {
+ $sql = "VACUUM $table";
+ }
+ if (!empty($sql)) {
+ $conn->Execute($sql);
+ }
+ }
+
+
+ return true;
+ }
+}
+
+ADODB_Session::_init();
+if (empty($ADODB_SESSION_READONLY))
+ register_shutdown_function('session_write_close');
+
+// for backwards compatability only
+function adodb_sess_open($save_path, $session_name, $persist = true) {
+ return ADODB_Session::open($save_path, $session_name, $persist);
+}
+
+// for backwards compatability only
+function adodb_sess_gc($t)
+{
+ return ADODB_Session::gc($t);
+}
diff --git a/vendor/adodb/adodb-php/session/adodb-sessions.mysql.sql b/vendor/adodb/adodb-php/session/adodb-sessions.mysql.sql
new file mode 100644
index 0000000..f90de44
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-sessions.mysql.sql
@@ -0,0 +1,16 @@
+-- $CVSHeader$
+
+CREATE DATABASE /*! IF NOT EXISTS */ adodb_sessions;
+
+USE adodb_sessions;
+
+DROP TABLE /*! IF EXISTS */ sessions;
+
+CREATE TABLE /*! IF NOT EXISTS */ sessions (
+ sesskey CHAR(32) /*! BINARY */ NOT NULL DEFAULT '',
+ expiry INT(11) /*! UNSIGNED */ NOT NULL DEFAULT 0,
+ expireref VARCHAR(64) DEFAULT '',
+ data LONGTEXT DEFAULT '',
+ PRIMARY KEY (sesskey),
+ INDEX expiry (expiry)
+);
diff --git a/vendor/adodb/adodb-php/session/adodb-sessions.oracle.clob.sql b/vendor/adodb/adodb-php/session/adodb-sessions.oracle.clob.sql
new file mode 100644
index 0000000..c5c4f2d
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-sessions.oracle.clob.sql
@@ -0,0 +1,15 @@
+-- $CVSHeader$
+
+DROP TABLE adodb_sessions;
+
+CREATE TABLE sessions (
+ sesskey CHAR(32) DEFAULT '' NOT NULL,
+ expiry INT DEFAULT 0 NOT NULL,
+ expireref VARCHAR(64) DEFAULT '',
+ data CLOB DEFAULT '',
+ PRIMARY KEY (sesskey)
+);
+
+CREATE INDEX ix_expiry ON sessions (expiry);
+
+QUIT;
diff --git a/vendor/adodb/adodb-php/session/adodb-sessions.oracle.sql b/vendor/adodb/adodb-php/session/adodb-sessions.oracle.sql
new file mode 100644
index 0000000..8fd5a34
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/adodb-sessions.oracle.sql
@@ -0,0 +1,16 @@
+-- $CVSHeader$
+
+DROP TABLE adodb_sessions;
+
+CREATE TABLE sessions (
+ sesskey CHAR(32) DEFAULT '' NOT NULL,
+ expiry INT DEFAULT 0 NOT NULL,
+ expireref VARCHAR(64) DEFAULT '',
+ data VARCHAR(4000) DEFAULT '',
+ PRIMARY KEY (sesskey),
+ INDEX expiry (expiry)
+);
+
+CREATE INDEX ix_expiry ON sessions (expiry);
+
+QUIT;
diff --git a/vendor/adodb/adodb-php/session/crypt.inc.php b/vendor/adodb/adodb-php/session/crypt.inc.php
new file mode 100644
index 0000000..1468cb1
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/crypt.inc.php
@@ -0,0 +1,157 @@
+<?php
+// Session Encryption by Ari Kuorikoski <ari.kuorikoski@finebyte.com>
+class MD5Crypt{
+ function keyED($txt,$encrypt_key)
+ {
+ $encrypt_key = md5($encrypt_key);
+ $ctr=0;
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++){
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+ $tmp.= substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1);
+ $ctr++;
+ }
+ return $tmp;
+ }
+
+ function Encrypt($txt,$key)
+ {
+ srand((double)microtime()*1000000);
+ $encrypt_key = md5(rand(0,32000));
+ $ctr=0;
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++)
+ {
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+ $tmp.= substr($encrypt_key,$ctr,1) .
+ (substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1));
+ $ctr++;
+ }
+ return base64_encode($this->keyED($tmp,$key));
+ }
+
+ function Decrypt($txt,$key)
+ {
+ $txt = $this->keyED(base64_decode($txt),$key);
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++){
+ $md5 = substr($txt,$i,1);
+ $i++;
+ $tmp.= (substr($txt,$i,1) ^ $md5);
+ }
+ return $tmp;
+ }
+
+ function RandPass()
+ {
+ $randomPassword = "";
+ srand((double)microtime()*1000000);
+ for($i=0;$i<8;$i++)
+ {
+ $randnumber = rand(48,120);
+
+ while (($randnumber >= 58 && $randnumber <= 64) || ($randnumber >= 91 && $randnumber <= 96))
+ {
+ $randnumber = rand(48,120);
+ }
+
+ $randomPassword .= chr($randnumber);
+ }
+ return $randomPassword;
+ }
+
+}
+
+
+class SHA1Crypt{
+ function keyED($txt,$encrypt_key)
+ {
+
+ $encrypt_key = sha1($encrypt_key);
+ $ctr=0;
+ $tmp = "";
+
+ for ($i=0;$i<strlen($txt);$i++){
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+ $tmp.= substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1);
+ $ctr++;
+ }
+ return $tmp;
+
+ }
+
+ function Encrypt($txt,$key)
+ {
+
+ srand((double)microtime()*1000000);
+ $encrypt_key = sha1(rand(0,32000));
+ $ctr=0;
+ $tmp = "";
+
+ for ($i=0;$i<strlen($txt);$i++)
+
+ {
+
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+
+ $tmp.= substr($encrypt_key,$ctr,1) .
+
+ (substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1));
+
+ $ctr++;
+
+ }
+
+ return base64_encode($this->keyED($tmp,$key));
+
+ }
+
+
+
+ function Decrypt($txt,$key)
+ {
+
+ $txt = $this->keyED(base64_decode($txt),$key);
+
+ $tmp = "";
+
+ for ($i=0;$i<strlen($txt);$i++){
+
+ $sha1 = substr($txt,$i,1);
+
+ $i++;
+
+ $tmp.= (substr($txt,$i,1) ^ $sha1);
+
+ }
+
+ return $tmp;
+ }
+
+
+
+ function RandPass()
+ {
+ $randomPassword = "";
+ srand((double)microtime()*1000000);
+
+ for($i=0;$i<8;$i++)
+ {
+
+ $randnumber = rand(48,120);
+
+ while (($randnumber >= 58 && $randnumber <= 64) || ($randnumber >= 91 && $randnumber <= 96))
+ {
+ $randnumber = rand(48,120);
+ }
+
+ $randomPassword .= chr($randnumber);
+ }
+
+ return $randomPassword;
+
+ }
+
+
+
+}
diff --git a/vendor/adodb/adodb-php/session/old/adodb-cryptsession.php b/vendor/adodb/adodb-php/session/old/adodb-cryptsession.php
new file mode 100644
index 0000000..7836022
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/old/adodb-cryptsession.php
@@ -0,0 +1,325 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Made table name configurable - by David Johnson djohnson@inpro.net
+ Encryption by Ari Kuorikoski <ari.kuorikoski@finebyte.com>
+
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+ ======================================================================
+
+ This file provides PHP4 session management using the ADODB database
+wrapper library.
+
+ Example
+ =======
+
+ include('adodb.inc.php');
+ #---------------------------------#
+ include('adodb-cryptsession.php');
+ #---------------------------------#
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+
+
+ Installation
+ ============
+ 1. Create a new database in MySQL or Access "sessions" like
+so:
+
+ create table sessions (
+ SESSKEY char(32) not null,
+ EXPIRY int(11) unsigned not null,
+ EXPIREREF varchar(64),
+ DATA CLOB,
+ primary key (sesskey)
+ );
+
+ 2. Then define the following parameters. You can either modify
+ this file, or define them before this file is included:
+
+ $ADODB_SESSION_DRIVER='database driver, eg. mysql or ibase';
+ $ADODB_SESSION_CONNECT='server to connect to';
+ $ADODB_SESSION_USER ='user';
+ $ADODB_SESSION_PWD ='password';
+ $ADODB_SESSION_DB ='database';
+ $ADODB_SESSION_TBL = 'sessions'
+
+ 3. Recommended is PHP 4.0.2 or later. There are documented
+session bugs in earlier versions of PHP.
+
+*/
+
+
+include_once('crypt.inc.php');
+
+if (!defined('_ADODB_LAYER')) {
+ include (dirname(__FILE__).'/adodb.inc.php');
+}
+
+ /* if database time and system time is difference is greater than this, then give warning */
+ define('ADODB_SESSION_SYNCH_SECS',60);
+
+if (!defined('ADODB_SESSION')) {
+
+ define('ADODB_SESSION',1);
+
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_LIFE,
+ $ADODB_SESS_DEBUG,
+ $ADODB_SESS_INSERT,
+ $ADODB_SESSION_EXPIRE_NOTIFY,
+ $ADODB_SESSION_TBL;
+
+ //$ADODB_SESS_DEBUG = true;
+
+ /* SET THE FOLLOWING PARAMETERS */
+if (empty($ADODB_SESSION_DRIVER)) {
+ $ADODB_SESSION_DRIVER='mysql';
+ $ADODB_SESSION_CONNECT='localhost';
+ $ADODB_SESSION_USER ='root';
+ $ADODB_SESSION_PWD ='';
+ $ADODB_SESSION_DB ='xphplens_2';
+}
+
+if (empty($ADODB_SESSION_TBL)){
+ $ADODB_SESSION_TBL = 'sessions';
+}
+
+if (empty($ADODB_SESSION_EXPIRE_NOTIFY)) {
+ $ADODB_SESSION_EXPIRE_NOTIFY = false;
+}
+
+function ADODB_Session_Key()
+{
+$ADODB_CRYPT_KEY = 'CRYPTED ADODB SESSIONS ROCK!';
+
+ /* USE THIS FUNCTION TO CREATE THE ENCRYPTION KEY FOR CRYPTED SESSIONS */
+ /* Crypt the used key, $ADODB_CRYPT_KEY as key and session_ID as SALT */
+ return crypt($ADODB_CRYPT_KEY, session_ID());
+}
+
+$ADODB_SESS_LIFE = ini_get('session.gc_maxlifetime');
+if ($ADODB_SESS_LIFE <= 1) {
+ // bug in PHP 4.0.3 pl 1 -- how about other versions?
+ //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $ADODB_SESS_LIFE</h3>";
+ $ADODB_SESS_LIFE=1440;
+}
+
+function adodb_sess_open($save_path, $session_name)
+{
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_DEBUG;
+
+ $ADODB_SESS_INSERT = false;
+
+ if (isset($ADODB_SESS_CONN)) return true;
+
+ $ADODB_SESS_CONN = ADONewConnection($ADODB_SESSION_DRIVER);
+ if (!empty($ADODB_SESS_DEBUG)) {
+ $ADODB_SESS_CONN->debug = true;
+ print" conn=$ADODB_SESSION_CONNECT user=$ADODB_SESSION_USER pwd=$ADODB_SESSION_PWD db=$ADODB_SESSION_DB ";
+ }
+ return $ADODB_SESS_CONN->PConnect($ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
+
+}
+
+function adodb_sess_close()
+{
+global $ADODB_SESS_CONN;
+
+ if ($ADODB_SESS_CONN) $ADODB_SESS_CONN->Close();
+ return true;
+}
+
+function adodb_sess_read($key)
+{
+$Crypt = new MD5Crypt;
+global $ADODB_SESS_CONN,$ADODB_SESS_INSERT,$ADODB_SESSION_TBL;
+ $rs = $ADODB_SESS_CONN->Execute("SELECT data FROM $ADODB_SESSION_TBL WHERE sesskey = '$key' AND expiry >= " . time());
+ if ($rs) {
+ if ($rs->EOF) {
+ $ADODB_SESS_INSERT = true;
+ $v = '';
+ } else {
+ // Decrypt session data
+ $v = rawurldecode($Crypt->Decrypt(reset($rs->fields), ADODB_Session_Key()));
+ }
+ $rs->Close();
+ return $v;
+ }
+ else $ADODB_SESS_INSERT = true;
+
+ return '';
+}
+
+function adodb_sess_write($key, $val)
+{
+$Crypt = new MD5Crypt;
+ global $ADODB_SESS_INSERT,$ADODB_SESS_CONN, $ADODB_SESS_LIFE, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ $expiry = time() + $ADODB_SESS_LIFE;
+
+ // encrypt session data..
+ $val = $Crypt->Encrypt(rawurlencode($val), ADODB_Session_Key());
+
+ $arr = array('sesskey' => $key, 'expiry' => $expiry, 'data' => $val);
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ $var = reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ global $$var;
+ $arr['expireref'] = $$var;
+ }
+ $rs = $ADODB_SESS_CONN->Replace($ADODB_SESSION_TBL,
+ $arr,
+ 'sesskey',$autoQuote = true);
+
+ if (!$rs) {
+ ADOConnection::outp( '
+-- Session Replace: '.$ADODB_SESS_CONN->ErrorMsg().'</p>',false);
+ } else {
+ // bug in access driver (could be odbc?) means that info is not commited
+ // properly unless select statement executed in Win2000
+
+ if ($ADODB_SESS_CONN->databaseType == 'access') $rs = $ADODB_SESS_CONN->Execute("select sesskey from $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ }
+ return isset($rs);
+}
+
+function adodb_sess_destroy($key)
+{
+ global $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $ADODB_SESS_CONN->CommitTrans();
+ }
+ } else {
+ $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE sesskey = '$key'";
+ $rs = $ADODB_SESS_CONN->Execute($qry);
+ }
+ return $rs ? true : false;
+}
+
+
+function adodb_sess_gc($maxlifetime) {
+ global $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY,$ADODB_SESS_DEBUG;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $t = time();
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE expiry < $t");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ //$del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE expiry < $t");
+ $ADODB_SESS_CONN->CommitTrans();
+ }
+ } else {
+ $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE expiry < " . time();
+ $ADODB_SESS_CONN->Execute($qry);
+ }
+
+ // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
+ if (defined('ADODB_SESSION_OPTIMIZE'))
+ {
+ global $ADODB_SESSION_DRIVER;
+
+ switch( $ADODB_SESSION_DRIVER ) {
+ case 'mysql':
+ case 'mysqlt':
+ $opt_qry = 'OPTIMIZE TABLE '.$ADODB_SESSION_TBL;
+ break;
+ case 'postgresql':
+ case 'postgresql7':
+ $opt_qry = 'VACUUM '.$ADODB_SESSION_TBL;
+ break;
+ }
+ }
+
+ if ($ADODB_SESS_CONN->dataProvider === 'oci8') $sql = 'select TO_CHAR('.($ADODB_SESS_CONN->sysTimeStamp).', \'RRRR-MM-DD HH24:MI:SS\') from '. $ADODB_SESSION_TBL;
+ else $sql = 'select '.$ADODB_SESS_CONN->sysTimeStamp.' from '. $ADODB_SESSION_TBL;
+
+ $rs = $ADODB_SESS_CONN->SelectLimit($sql,1);
+ if ($rs && !$rs->EOF) {
+
+ $dbts = reset($rs->fields);
+ $rs->Close();
+ $dbt = $ADODB_SESS_CONN->UnixTimeStamp($dbts);
+ $t = time();
+ if (abs($dbt - $t) >= ADODB_SESSION_SYNCH_SECS) {
+ $msg =
+ __FILE__.": Server time for webserver {$_SERVER['HTTP_HOST']} not in synch with database: database=$dbt ($dbts), webserver=$t (diff=".(abs($dbt-$t)/3600)." hrs)";
+ error_log($msg);
+ if ($ADODB_SESS_DEBUG) ADOConnection::outp("
+-- $msg</p>");
+ }
+ }
+
+ return true;
+}
+
+session_module_name('user');
+session_set_save_handler(
+ "adodb_sess_open",
+ "adodb_sess_close",
+ "adodb_sess_read",
+ "adodb_sess_write",
+ "adodb_sess_destroy",
+ "adodb_sess_gc");
+}
+
+/* TEST SCRIPT -- UNCOMMENT */
+/*
+if (0) {
+
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+}
+*/
diff --git a/vendor/adodb/adodb-php/session/old/adodb-session-clob.php b/vendor/adodb/adodb-php/session/old/adodb-session-clob.php
new file mode 100644
index 0000000..c633b61
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/old/adodb-session-clob.php
@@ -0,0 +1,448 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+ ======================================================================
+
+ This file provides PHP4 session management using the ADODB database
+ wrapper library, using Oracle CLOB's to store data. Contributed by achim.gosse@ddd.de.
+
+ Example
+ =======
+
+ include('adodb.inc.php');
+ include('adodb-session.php');
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+
+To force non-persistent connections, call adodb_session_open first before session_start():
+
+ include('adodb.inc.php');
+ include('adodb-session.php');
+ adodb_session_open(false,false,false);
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+
+
+ Installation
+ ============
+ 1. Create this table in your database (syntax might vary depending on your db):
+
+ create table sessions (
+ SESSKEY char(32) not null,
+ EXPIRY int(11) unsigned not null,
+ EXPIREREF varchar(64),
+ DATA CLOB,
+ primary key (sesskey)
+ );
+
+
+ 2. Then define the following parameters in this file:
+ $ADODB_SESSION_DRIVER='database driver, eg. mysql or ibase';
+ $ADODB_SESSION_CONNECT='server to connect to';
+ $ADODB_SESSION_USER ='user';
+ $ADODB_SESSION_PWD ='password';
+ $ADODB_SESSION_DB ='database';
+ $ADODB_SESSION_TBL = 'sessions'
+ $ADODB_SESSION_USE_LOBS = false; (or, if you wanna use CLOBS (= 'CLOB') or ( = 'BLOB')
+
+ 3. Recommended is PHP 4.1.0 or later. There are documented
+ session bugs in earlier versions of PHP.
+
+ 4. If you want to receive notifications when a session expires, then
+ you can tag a session with an EXPIREREF, and before the session
+ record is deleted, we can call a function that will pass the EXPIREREF
+ as the first parameter, and the session key as the second parameter.
+
+ To do this, define a notification function, say NotifyFn:
+
+ function NotifyFn($expireref, $sesskey)
+ {
+ }
+
+ Then you need to define a global variable $ADODB_SESSION_EXPIRE_NOTIFY.
+ This is an array with 2 elements, the first being the name of the variable
+ you would like to store in the EXPIREREF field, and the 2nd is the
+ notification function's name.
+
+ In this example, we want to be notified when a user's session
+ has expired, so we store the user id in the global variable $USERID,
+ store this value in the EXPIREREF field:
+
+ $ADODB_SESSION_EXPIRE_NOTIFY = array('USERID','NotifyFn');
+
+ Then when the NotifyFn is called, we are passed the $USERID as the first
+ parameter, eg. NotifyFn($userid, $sesskey).
+*/
+
+if (!defined('_ADODB_LAYER')) {
+ include (dirname(__FILE__).'/adodb.inc.php');
+}
+
+if (!defined('ADODB_SESSION')) {
+
+ define('ADODB_SESSION',1);
+
+ /* if database time and system time is difference is greater than this, then give warning */
+ define('ADODB_SESSION_SYNCH_SECS',60);
+
+/****************************************************************************************\
+ Global definitions
+\****************************************************************************************/
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_LIFE,
+ $ADODB_SESS_DEBUG,
+ $ADODB_SESSION_EXPIRE_NOTIFY,
+ $ADODB_SESSION_CRC,
+ $ADODB_SESSION_USE_LOBS,
+ $ADODB_SESSION_TBL;
+
+ if (!isset($ADODB_SESSION_USE_LOBS)) $ADODB_SESSION_USE_LOBS = 'CLOB';
+
+ $ADODB_SESS_LIFE = ini_get('session.gc_maxlifetime');
+ if ($ADODB_SESS_LIFE <= 1) {
+ // bug in PHP 4.0.3 pl 1 -- how about other versions?
+ //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $ADODB_SESS_LIFE</h3>";
+ $ADODB_SESS_LIFE=1440;
+ }
+ $ADODB_SESSION_CRC = false;
+ //$ADODB_SESS_DEBUG = true;
+
+ //////////////////////////////////
+ /* SET THE FOLLOWING PARAMETERS */
+ //////////////////////////////////
+
+ if (empty($ADODB_SESSION_DRIVER)) {
+ $ADODB_SESSION_DRIVER='mysql';
+ $ADODB_SESSION_CONNECT='localhost';
+ $ADODB_SESSION_USER ='root';
+ $ADODB_SESSION_PWD ='';
+ $ADODB_SESSION_DB ='xphplens_2';
+ }
+
+ if (empty($ADODB_SESSION_EXPIRE_NOTIFY)) {
+ $ADODB_SESSION_EXPIRE_NOTIFY = false;
+ }
+ // Made table name configurable - by David Johnson djohnson@inpro.net
+ if (empty($ADODB_SESSION_TBL)){
+ $ADODB_SESSION_TBL = 'sessions';
+ }
+
+
+ // defaulting $ADODB_SESSION_USE_LOBS
+ if (!isset($ADODB_SESSION_USE_LOBS) || empty($ADODB_SESSION_USE_LOBS)) {
+ $ADODB_SESSION_USE_LOBS = false;
+ }
+
+ /*
+ $ADODB_SESS['driver'] = $ADODB_SESSION_DRIVER;
+ $ADODB_SESS['connect'] = $ADODB_SESSION_CONNECT;
+ $ADODB_SESS['user'] = $ADODB_SESSION_USER;
+ $ADODB_SESS['pwd'] = $ADODB_SESSION_PWD;
+ $ADODB_SESS['db'] = $ADODB_SESSION_DB;
+ $ADODB_SESS['life'] = $ADODB_SESS_LIFE;
+ $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
+
+ $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
+ $ADODB_SESS['table'] = $ADODB_SESS_TBL;
+ */
+
+/****************************************************************************************\
+ Create the connection to the database.
+
+ If $ADODB_SESS_CONN already exists, reuse that connection
+\****************************************************************************************/
+function adodb_sess_open($save_path, $session_name,$persist=true)
+{
+GLOBAL $ADODB_SESS_CONN;
+ if (isset($ADODB_SESS_CONN)) return true;
+
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_DEBUG;
+
+ // cannot use & below - do not know why...
+ $ADODB_SESS_CONN = ADONewConnection($ADODB_SESSION_DRIVER);
+ if (!empty($ADODB_SESS_DEBUG)) {
+ $ADODB_SESS_CONN->debug = true;
+ ADOConnection::outp( " conn=$ADODB_SESSION_CONNECT user=$ADODB_SESSION_USER pwd=$ADODB_SESSION_PWD db=$ADODB_SESSION_DB ");
+ }
+ if ($persist) $ok = $ADODB_SESS_CONN->PConnect($ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
+ else $ok = $ADODB_SESS_CONN->Connect($ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
+
+ if (!$ok) ADOConnection::outp( "
+-- Session: connection failed</p>",false);
+}
+
+/****************************************************************************************\
+ Close the connection
+\****************************************************************************************/
+function adodb_sess_close()
+{
+global $ADODB_SESS_CONN;
+
+ if ($ADODB_SESS_CONN) $ADODB_SESS_CONN->Close();
+ return true;
+}
+
+/****************************************************************************************\
+ Slurp in the session variables and return the serialized string
+\****************************************************************************************/
+function adodb_sess_read($key)
+{
+global $ADODB_SESS_CONN,$ADODB_SESSION_TBL,$ADODB_SESSION_CRC;
+
+ $rs = $ADODB_SESS_CONN->Execute("SELECT data FROM $ADODB_SESSION_TBL WHERE sesskey = '$key' AND expiry >= " . time());
+ if ($rs) {
+ if ($rs->EOF) {
+ $v = '';
+ } else
+ $v = rawurldecode(reset($rs->fields));
+
+ $rs->Close();
+
+ // new optimization adodb 2.1
+ $ADODB_SESSION_CRC = strlen($v).crc32($v);
+
+ return $v;
+ }
+
+ return ''; // thx to Jorma Tuomainen, webmaster#wizactive.com
+}
+
+/****************************************************************************************\
+ Write the serialized data to a database.
+
+ If the data has not been modified since adodb_sess_read(), we do not write.
+\****************************************************************************************/
+function adodb_sess_write($key, $val)
+{
+ global
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_LIFE,
+ $ADODB_SESSION_TBL,
+ $ADODB_SESS_DEBUG,
+ $ADODB_SESSION_CRC,
+ $ADODB_SESSION_EXPIRE_NOTIFY,
+ $ADODB_SESSION_DRIVER, // added
+ $ADODB_SESSION_USE_LOBS; // added
+
+ $expiry = time() + $ADODB_SESS_LIFE;
+
+ // crc32 optimization since adodb 2.1
+ // now we only update expiry date, thx to sebastian thom in adodb 2.32
+ if ($ADODB_SESSION_CRC !== false && $ADODB_SESSION_CRC == strlen($val).crc32($val)) {
+ if ($ADODB_SESS_DEBUG) echo "
+-- Session: Only updating date - crc32 not changed</p>";
+ $qry = "UPDATE $ADODB_SESSION_TBL SET expiry=$expiry WHERE sesskey='$key' AND expiry >= " . time();
+ $rs = $ADODB_SESS_CONN->Execute($qry);
+ return true;
+ }
+ $val = rawurlencode($val);
+
+ $arr = array('sesskey' => $key, 'expiry' => $expiry, 'data' => $val);
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ $var = reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ global $$var;
+ $arr['expireref'] = $$var;
+ }
+
+
+ if ($ADODB_SESSION_USE_LOBS === false) { // no lobs, simply use replace()
+ $rs = $ADODB_SESS_CONN->Replace($ADODB_SESSION_TBL,$arr, 'sesskey',$autoQuote = true);
+ if (!$rs) {
+ $err = $ADODB_SESS_CONN->ErrorMsg();
+ }
+ } else {
+ // what value shall we insert/update for lob row?
+ switch ($ADODB_SESSION_DRIVER) {
+ // empty_clob or empty_lob for oracle dbs
+ case "oracle":
+ case "oci8":
+ case "oci8po":
+ case "oci805":
+ $lob_value = sprintf("empty_%s()", strtolower($ADODB_SESSION_USE_LOBS));
+ break;
+
+ // null for all other
+ default:
+ $lob_value = "null";
+ break;
+ }
+
+ // do we insert or update? => as for sesskey
+ $res = $ADODB_SESS_CONN->Execute("select count(*) as cnt from $ADODB_SESSION_TBL where sesskey = '$key'");
+ if ($res && reset($res->fields) > 0) {
+ $qry = sprintf("update %s set expiry = %d, data = %s where sesskey = '%s'", $ADODB_SESSION_TBL, $expiry, $lob_value, $key);
+ } else {
+ // insert
+ $qry = sprintf("insert into %s (sesskey, expiry, data) values ('%s', %d, %s)", $ADODB_SESSION_TBL, $key, $expiry, $lob_value);
+ }
+
+ $err = "";
+ $rs1 = $ADODB_SESS_CONN->Execute($qry);
+ if (!$rs1) {
+ $err .= $ADODB_SESS_CONN->ErrorMsg()."\n";
+ }
+ $rs2 = $ADODB_SESS_CONN->UpdateBlob($ADODB_SESSION_TBL, 'data', $val, "sesskey='$key'", strtoupper($ADODB_SESSION_USE_LOBS));
+ if (!$rs2) {
+ $err .= $ADODB_SESS_CONN->ErrorMsg()."\n";
+ }
+ $rs = ($rs1 && $rs2) ? true : false;
+ }
+
+ if (!$rs) {
+ ADOConnection::outp( '
+-- Session Replace: '.nl2br($err).'</p>',false);
+ } else {
+ // bug in access driver (could be odbc?) means that info is not commited
+ // properly unless select statement executed in Win2000
+ if ($ADODB_SESS_CONN->databaseType == 'access')
+ $rs = $ADODB_SESS_CONN->Execute("select sesskey from $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ }
+ return !empty($rs);
+}
+
+function adodb_sess_destroy($key)
+{
+ global $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $ADODB_SESS_CONN->CommitTrans();
+ }
+ } else {
+ $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE sesskey = '$key'";
+ $rs = $ADODB_SESS_CONN->Execute($qry);
+ }
+ return $rs ? true : false;
+}
+
+function adodb_sess_gc($maxlifetime)
+{
+ global $ADODB_SESS_DEBUG, $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $t = time();
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE expiry < $t");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ //$ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE expiry < $t");
+ $ADODB_SESS_CONN->CommitTrans();
+
+ }
+ } else {
+ $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE expiry < " . time());
+
+ if ($ADODB_SESS_DEBUG) ADOConnection::outp("
+-- <b>Garbage Collection</b>: $qry</p>");
+ }
+ // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
+ if (defined('ADODB_SESSION_OPTIMIZE')) {
+ global $ADODB_SESSION_DRIVER;
+
+ switch( $ADODB_SESSION_DRIVER ) {
+ case 'mysql':
+ case 'mysqlt':
+ $opt_qry = 'OPTIMIZE TABLE '.$ADODB_SESSION_TBL;
+ break;
+ case 'postgresql':
+ case 'postgresql7':
+ $opt_qry = 'VACUUM '.$ADODB_SESSION_TBL;
+ break;
+ }
+ if (!empty($opt_qry)) {
+ $ADODB_SESS_CONN->Execute($opt_qry);
+ }
+ }
+ if ($ADODB_SESS_CONN->dataProvider === 'oci8') $sql = 'select TO_CHAR('.($ADODB_SESS_CONN->sysTimeStamp).', \'RRRR-MM-DD HH24:MI:SS\') from '. $ADODB_SESSION_TBL;
+ else $sql = 'select '.$ADODB_SESS_CONN->sysTimeStamp.' from '. $ADODB_SESSION_TBL;
+
+ $rs = $ADODB_SESS_CONN->SelectLimit($sql,1);
+ if ($rs && !$rs->EOF) {
+
+ $dbts = reset($rs->fields);
+ $rs->Close();
+ $dbt = $ADODB_SESS_CONN->UnixTimeStamp($dbts);
+ $t = time();
+ if (abs($dbt - $t) >= ADODB_SESSION_SYNCH_SECS) {
+ $msg =
+ __FILE__.": Server time for webserver {$_SERVER['HTTP_HOST']} not in synch with database: database=$dbt ($dbts), webserver=$t (diff=".(abs($dbt-$t)/3600)." hrs)";
+ error_log($msg);
+ if ($ADODB_SESS_DEBUG) ADOConnection::outp("
+-- $msg</p>");
+ }
+ }
+
+ return true;
+}
+
+session_module_name('user');
+session_set_save_handler(
+ "adodb_sess_open",
+ "adodb_sess_close",
+ "adodb_sess_read",
+ "adodb_sess_write",
+ "adodb_sess_destroy",
+ "adodb_sess_gc");
+}
+
+/* TEST SCRIPT -- UNCOMMENT */
+
+if (0) {
+
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ ADOConnection::outp( "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>",false);
+}
diff --git a/vendor/adodb/adodb-php/session/old/adodb-session.php b/vendor/adodb/adodb-php/session/old/adodb-session.php
new file mode 100644
index 0000000..2385cc6
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/old/adodb-session.php
@@ -0,0 +1,439 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+ ======================================================================
+
+ This file provides PHP4 session management using the ADODB database
+wrapper library.
+
+ Example
+ =======
+
+ include('adodb.inc.php');
+ include('adodb-session.php');
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+
+To force non-persistent connections, call adodb_session_open first before session_start():
+
+ include('adodb.inc.php');
+ include('adodb-session.php');
+ adodb_sess_open(false,false,false);
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ print "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
+
+
+ Installation
+ ============
+ 1. Create this table in your database (syntax might vary depending on your db):
+
+ create table sessions (
+ SESSKEY char(32) not null,
+ EXPIRY int(11) unsigned not null,
+ EXPIREREF varchar(64),
+ DATA text not null,
+ primary key (sesskey)
+ );
+
+ For oracle:
+ create table sessions (
+ SESSKEY char(32) not null,
+ EXPIRY DECIMAL(16) not null,
+ EXPIREREF varchar(64),
+ DATA varchar(4000) not null,
+ primary key (sesskey)
+ );
+
+
+ 2. Then define the following parameters. You can either modify
+ this file, or define them before this file is included:
+
+ $ADODB_SESSION_DRIVER='database driver, eg. mysql or ibase';
+ $ADODB_SESSION_CONNECT='server to connect to';
+ $ADODB_SESSION_USER ='user';
+ $ADODB_SESSION_PWD ='password';
+ $ADODB_SESSION_DB ='database';
+ $ADODB_SESSION_TBL = 'sessions'
+
+ 3. Recommended is PHP 4.1.0 or later. There are documented
+ session bugs in earlier versions of PHP.
+
+ 4. If you want to receive notifications when a session expires, then
+ you can tag a session with an EXPIREREF, and before the session
+ record is deleted, we can call a function that will pass the EXPIREREF
+ as the first parameter, and the session key as the second parameter.
+
+ To do this, define a notification function, say NotifyFn:
+
+ function NotifyFn($expireref, $sesskey)
+ {
+ }
+
+ Then you need to define a global variable $ADODB_SESSION_EXPIRE_NOTIFY.
+ This is an array with 2 elements, the first being the name of the variable
+ you would like to store in the EXPIREREF field, and the 2nd is the
+ notification function's name.
+
+ In this example, we want to be notified when a user's session
+ has expired, so we store the user id in the global variable $USERID,
+ store this value in the EXPIREREF field:
+
+ $ADODB_SESSION_EXPIRE_NOTIFY = array('USERID','NotifyFn');
+
+ Then when the NotifyFn is called, we are passed the $USERID as the first
+ parameter, eg. NotifyFn($userid, $sesskey).
+*/
+
+if (!defined('_ADODB_LAYER')) {
+ include (dirname(__FILE__).'/adodb.inc.php');
+}
+
+if (!defined('ADODB_SESSION')) {
+
+ define('ADODB_SESSION',1);
+
+ /* if database time and system time is difference is greater than this, then give warning */
+ define('ADODB_SESSION_SYNCH_SECS',60);
+
+ /*
+ Thanks Joe Li. See http://phplens.com/lens/lensforum/msgs.php?id=11487&x=1
+*/
+function adodb_session_regenerate_id()
+{
+ $conn = ADODB_Session::_conn();
+ if (!$conn) return false;
+
+ $old_id = session_id();
+ if (function_exists('session_regenerate_id')) {
+ session_regenerate_id();
+ } else {
+ session_id(md5(uniqid(rand(), true)));
+ $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ //@session_start();
+ }
+ $new_id = session_id();
+ $ok = $conn->Execute('UPDATE '. ADODB_Session::table(). ' SET sesskey='. $conn->qstr($new_id). ' WHERE sesskey='.$conn->qstr($old_id));
+
+ /* it is possible that the update statement fails due to a collision */
+ if (!$ok) {
+ session_id($old_id);
+ if (empty($ck)) $ck = session_get_cookie_params();
+ setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************************\
+ Global definitions
+\****************************************************************************************/
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_LIFE,
+ $ADODB_SESS_DEBUG,
+ $ADODB_SESSION_EXPIRE_NOTIFY,
+ $ADODB_SESSION_CRC,
+ $ADODB_SESSION_TBL;
+
+
+ $ADODB_SESS_LIFE = ini_get('session.gc_maxlifetime');
+ if ($ADODB_SESS_LIFE <= 1) {
+ // bug in PHP 4.0.3 pl 1 -- how about other versions?
+ //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $ADODB_SESS_LIFE</h3>";
+ $ADODB_SESS_LIFE=1440;
+ }
+ $ADODB_SESSION_CRC = false;
+ //$ADODB_SESS_DEBUG = true;
+
+ //////////////////////////////////
+ /* SET THE FOLLOWING PARAMETERS */
+ //////////////////////////////////
+
+ if (empty($ADODB_SESSION_DRIVER)) {
+ $ADODB_SESSION_DRIVER='mysql';
+ $ADODB_SESSION_CONNECT='localhost';
+ $ADODB_SESSION_USER ='root';
+ $ADODB_SESSION_PWD ='';
+ $ADODB_SESSION_DB ='xphplens_2';
+ }
+
+ if (empty($ADODB_SESSION_EXPIRE_NOTIFY)) {
+ $ADODB_SESSION_EXPIRE_NOTIFY = false;
+ }
+ // Made table name configurable - by David Johnson djohnson@inpro.net
+ if (empty($ADODB_SESSION_TBL)){
+ $ADODB_SESSION_TBL = 'sessions';
+ }
+
+ /*
+ $ADODB_SESS['driver'] = $ADODB_SESSION_DRIVER;
+ $ADODB_SESS['connect'] = $ADODB_SESSION_CONNECT;
+ $ADODB_SESS['user'] = $ADODB_SESSION_USER;
+ $ADODB_SESS['pwd'] = $ADODB_SESSION_PWD;
+ $ADODB_SESS['db'] = $ADODB_SESSION_DB;
+ $ADODB_SESS['life'] = $ADODB_SESS_LIFE;
+ $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
+
+ $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
+ $ADODB_SESS['table'] = $ADODB_SESS_TBL;
+ */
+
+/****************************************************************************************\
+ Create the connection to the database.
+
+ If $ADODB_SESS_CONN already exists, reuse that connection
+\****************************************************************************************/
+function adodb_sess_open($save_path, $session_name,$persist=true)
+{
+GLOBAL $ADODB_SESS_CONN;
+ if (isset($ADODB_SESS_CONN)) return true;
+
+GLOBAL $ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_DRIVER,
+ $ADODB_SESSION_USER,
+ $ADODB_SESSION_PWD,
+ $ADODB_SESSION_DB,
+ $ADODB_SESS_DEBUG;
+
+ // cannot use & below - do not know why...
+ $ADODB_SESS_CONN = ADONewConnection($ADODB_SESSION_DRIVER);
+ if (!empty($ADODB_SESS_DEBUG)) {
+ $ADODB_SESS_CONN->debug = true;
+ ADOConnection::outp( " conn=$ADODB_SESSION_CONNECT user=$ADODB_SESSION_USER pwd=$ADODB_SESSION_PWD db=$ADODB_SESSION_DB ");
+ }
+ if ($persist) $ok = $ADODB_SESS_CONN->PConnect($ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
+ else $ok = $ADODB_SESS_CONN->Connect($ADODB_SESSION_CONNECT,
+ $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
+
+ if (!$ok) ADOConnection::outp( "
+-- Session: connection failed</p>",false);
+}
+
+/****************************************************************************************\
+ Close the connection
+\****************************************************************************************/
+function adodb_sess_close()
+{
+global $ADODB_SESS_CONN;
+
+ if ($ADODB_SESS_CONN) $ADODB_SESS_CONN->Close();
+ return true;
+}
+
+/****************************************************************************************\
+ Slurp in the session variables and return the serialized string
+\****************************************************************************************/
+function adodb_sess_read($key)
+{
+global $ADODB_SESS_CONN,$ADODB_SESSION_TBL,$ADODB_SESSION_CRC;
+
+ $rs = $ADODB_SESS_CONN->Execute("SELECT data FROM $ADODB_SESSION_TBL WHERE sesskey = '$key' AND expiry >= " . time());
+ if ($rs) {
+ if ($rs->EOF) {
+ $v = '';
+ } else
+ $v = rawurldecode(reset($rs->fields));
+
+ $rs->Close();
+
+ // new optimization adodb 2.1
+ $ADODB_SESSION_CRC = strlen($v).crc32($v);
+
+ return $v;
+ }
+
+ return ''; // thx to Jorma Tuomainen, webmaster#wizactive.com
+}
+
+/****************************************************************************************\
+ Write the serialized data to a database.
+
+ If the data has not been modified since adodb_sess_read(), we do not write.
+\****************************************************************************************/
+function adodb_sess_write($key, $val)
+{
+ global
+ $ADODB_SESS_CONN,
+ $ADODB_SESS_LIFE,
+ $ADODB_SESSION_TBL,
+ $ADODB_SESS_DEBUG,
+ $ADODB_SESSION_CRC,
+ $ADODB_SESSION_EXPIRE_NOTIFY;
+
+ $expiry = time() + $ADODB_SESS_LIFE;
+
+ // crc32 optimization since adodb 2.1
+ // now we only update expiry date, thx to sebastian thom in adodb 2.32
+ if ($ADODB_SESSION_CRC !== false && $ADODB_SESSION_CRC == strlen($val).crc32($val)) {
+ if ($ADODB_SESS_DEBUG) echo "
+-- Session: Only updating date - crc32 not changed</p>";
+ $qry = "UPDATE $ADODB_SESSION_TBL SET expiry=$expiry WHERE sesskey='$key' AND expiry >= " . time();
+ $rs = $ADODB_SESS_CONN->Execute($qry);
+ return true;
+ }
+ $val = rawurlencode($val);
+
+ $arr = array('sesskey' => $key, 'expiry' => $expiry, 'data' => $val);
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ $var = reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ global $$var;
+ $arr['expireref'] = $$var;
+ }
+ $rs = $ADODB_SESS_CONN->Replace($ADODB_SESSION_TBL,$arr,
+ 'sesskey',$autoQuote = true);
+
+ if (!$rs) {
+ ADOConnection::outp( '
+-- Session Replace: '.$ADODB_SESS_CONN->ErrorMsg().'</p>',false);
+ } else {
+ // bug in access driver (could be odbc?) means that info is not commited
+ // properly unless select statement executed in Win2000
+ if ($ADODB_SESS_CONN->databaseType == 'access')
+ $rs = $ADODB_SESS_CONN->Execute("select sesskey from $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ }
+ return !empty($rs);
+}
+
+function adodb_sess_destroy($key)
+{
+ global $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $ADODB_SESS_CONN->CommitTrans();
+ }
+ } else {
+ $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE sesskey = '$key'";
+ $rs = $ADODB_SESS_CONN->Execute($qry);
+ }
+ return $rs ? true : false;
+}
+
+function adodb_sess_gc($maxlifetime)
+{
+ global $ADODB_SESS_DEBUG, $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
+
+ if ($ADODB_SESSION_EXPIRE_NOTIFY) {
+ reset($ADODB_SESSION_EXPIRE_NOTIFY);
+ $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
+ $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
+ $t = time();
+ $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE expiry < $t");
+ $ADODB_SESS_CONN->SetFetchMode($savem);
+ if ($rs) {
+ $ADODB_SESS_CONN->BeginTrans();
+ while (!$rs->EOF) {
+ $ref = $rs->fields[0];
+ $key = $rs->fields[1];
+ $fn($ref,$key);
+ $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
+ $rs->MoveNext();
+ }
+ $rs->Close();
+
+ $ADODB_SESS_CONN->CommitTrans();
+
+ }
+ } else {
+ $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE expiry < " . time();
+ $ADODB_SESS_CONN->Execute($qry);
+
+ if ($ADODB_SESS_DEBUG) ADOConnection::outp("
+-- <b>Garbage Collection</b>: $qry</p>");
+ }
+ // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
+ if (defined('ADODB_SESSION_OPTIMIZE')) {
+ global $ADODB_SESSION_DRIVER;
+
+ switch( $ADODB_SESSION_DRIVER ) {
+ case 'mysql':
+ case 'mysqlt':
+ $opt_qry = 'OPTIMIZE TABLE '.$ADODB_SESSION_TBL;
+ break;
+ case 'postgresql':
+ case 'postgresql7':
+ $opt_qry = 'VACUUM '.$ADODB_SESSION_TBL;
+ break;
+ }
+ if (!empty($opt_qry)) {
+ $ADODB_SESS_CONN->Execute($opt_qry);
+ }
+ }
+ if ($ADODB_SESS_CONN->dataProvider === 'oci8') $sql = 'select TO_CHAR('.($ADODB_SESS_CONN->sysTimeStamp).', \'RRRR-MM-DD HH24:MI:SS\') from '. $ADODB_SESSION_TBL;
+ else $sql = 'select '.$ADODB_SESS_CONN->sysTimeStamp.' from '. $ADODB_SESSION_TBL;
+
+ $rs = $ADODB_SESS_CONN->SelectLimit($sql,1);
+ if ($rs && !$rs->EOF) {
+
+ $dbts = reset($rs->fields);
+ $rs->Close();
+ $dbt = $ADODB_SESS_CONN->UnixTimeStamp($dbts);
+ $t = time();
+
+ if (abs($dbt - $t) >= ADODB_SESSION_SYNCH_SECS) {
+
+ $msg =
+ __FILE__.": Server time for webserver {$_SERVER['HTTP_HOST']} not in synch with database: database=$dbt ($dbts), webserver=$t (diff=".(abs($dbt-$t)/3600)." hrs)";
+ error_log($msg);
+ if ($ADODB_SESS_DEBUG) ADOConnection::outp("
+-- $msg</p>");
+ }
+ }
+
+ return true;
+}
+
+session_module_name('user');
+session_set_save_handler(
+ "adodb_sess_open",
+ "adodb_sess_close",
+ "adodb_sess_read",
+ "adodb_sess_write",
+ "adodb_sess_destroy",
+ "adodb_sess_gc");
+}
+
+/* TEST SCRIPT -- UNCOMMENT */
+
+if (0) {
+
+ session_start();
+ session_register('AVAR');
+ $_SESSION['AVAR'] += 1;
+ ADOConnection::outp( "
+-- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>",false);
+}
diff --git a/vendor/adodb/adodb-php/session/old/crypt.inc.php b/vendor/adodb/adodb-php/session/old/crypt.inc.php
new file mode 100644
index 0000000..9c347db
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/old/crypt.inc.php
@@ -0,0 +1,63 @@
+<?php
+// Session Encryption by Ari Kuorikoski <ari.kuorikoski@finebyte.com>
+class MD5Crypt{
+ function keyED($txt,$encrypt_key)
+ {
+ $encrypt_key = md5($encrypt_key);
+ $ctr=0;
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++){
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+ $tmp.= substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1);
+ $ctr++;
+ }
+ return $tmp;
+ }
+
+ function Encrypt($txt,$key)
+ {
+ srand((double)microtime()*1000000);
+ $encrypt_key = md5(rand(0,32000));
+ $ctr=0;
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++)
+ {
+ if ($ctr==strlen($encrypt_key)) $ctr=0;
+ $tmp.= substr($encrypt_key,$ctr,1) .
+ (substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1));
+ $ctr++;
+ }
+ return base64_encode($this->keyED($tmp,$key));
+ }
+
+ function Decrypt($txt,$key)
+ {
+ $txt = $this->keyED(base64_decode($txt),$key);
+ $tmp = "";
+ for ($i=0;$i<strlen($txt);$i++){
+ $md5 = substr($txt,$i,1);
+ $i++;
+ $tmp.= (substr($txt,$i,1) ^ $md5);
+ }
+ return $tmp;
+ }
+
+ function RandPass()
+ {
+ $randomPassword = "";
+ srand((double)microtime()*1000000);
+ for($i=0;$i<8;$i++)
+ {
+ $randnumber = rand(48,120);
+
+ while (($randnumber >= 58 && $randnumber <= 64) || ($randnumber >= 91 && $randnumber <= 96))
+ {
+ $randnumber = rand(48,120);
+ }
+
+ $randomPassword .= chr($randnumber);
+ }
+ return $randomPassword;
+ }
+
+}
diff --git a/vendor/adodb/adodb-php/session/session_schema.xml b/vendor/adodb/adodb-php/session/session_schema.xml
new file mode 100644
index 0000000..27e47bf
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/session_schema.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<schema version="0.2">
+ <table name="sessions">
+ <desc>table for ADOdb session-management</desc>
+
+ <field name="SESSKEY" type="C" size="32">
+ <descr>session key</descr>
+ <KEY/>
+ <NOTNULL/>
+ </field>
+
+ <field name="EXPIRY" type="I" size="11">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+
+ <field name="EXPIREREF" type="C" size="64">
+ <descr></descr>
+ </field>
+
+ <field name="DATA" type="XL">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+ </table>
+</schema>
diff --git a/vendor/adodb/adodb-php/session/session_schema2.xml b/vendor/adodb/adodb-php/session/session_schema2.xml
new file mode 100644
index 0000000..f0d3ec9
--- /dev/null
+++ b/vendor/adodb/adodb-php/session/session_schema2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="sessions2">
+ <desc>table for ADOdb session-management</desc>
+
+ <field name="SESSKEY" type="C" size="64">
+ <descr>session key</descr>
+ <KEY/>
+ <NOTNULL/>
+ </field>
+
+
+
+ <field name="EXPIRY" type="T">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+
+ <field name="CREATED" type="T">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+
+ <field name="MODIFIED" type="T">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+
+ <field name="EXPIREREF" type="C" size="250">
+ <descr></descr>
+ </field>
+
+ <field name="SESSDATA" type="XL">
+ <descr></descr>
+ <NOTNULL/>
+ </field>
+ </table>
+</schema>
diff --git a/vendor/adodb/adodb-php/tests/benchmark.php b/vendor/adodb/adodb-php/tests/benchmark.php
new file mode 100644
index 0000000..afb044b
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/benchmark.php
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<head>
+ <title>ADODB Benchmarks</title>
+</head>
+
+<body>
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Benchmark code to test the speed to the ADODB library with different databases.
+ This is a simplistic benchmark to be used as the basis for further testing.
+ It should not be used as proof of the superiority of one database over the other.
+*/
+
+$testmssql = true;
+//$testvfp = true;
+$testoracle = true;
+$testado = true;
+$testibase = true;
+$testaccess = true;
+$testmysql = true;
+$testsqlite = true;;
+
+set_time_limit(240); // increase timeout
+
+include("../tohtml.inc.php");
+include("../adodb.inc.php");
+
+function testdb(&$db,$createtab="create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)")
+{
+GLOBAL $ADODB_version,$ADODB_FETCH_MODE;
+
+ adodb_backtrace();
+
+ $max = 100;
+ $sql = 'select * from ADOXYZ';
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ //print "<h3>ADODB Version: $ADODB_version Host: <i>$db->host</i> &nbsp; Database: <i>$db->database</i></h3>";
+
+ // perform query once to cache results so we are only testing throughput
+ $rs = $db->Execute($sql);
+ if (!$rs){
+ print "Error in recordset<p>";
+ return;
+ }
+ $arr = $rs->GetArray();
+ //$db->debug = true;
+ global $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+ $start = microtime();
+ for ($i=0; $i < $max; $i++) {
+ $rs = $db->Execute($sql);
+ $arr = $rs->GetArray();
+ // print $arr[0][1];
+ }
+ $end = microtime();
+ $start = explode(' ',$start);
+ $end = explode(' ',$end);
+
+ //print_r($start);
+ //print_r($end);
+
+ // print_r($arr);
+ $total = $end[0]+trim($end[1]) - $start[0]-trim($start[1]);
+ printf ("<p>seconds = %8.2f for %d iterations each with %d records</p>",$total,$max, sizeof($arr));
+ flush();
+
+
+ //$db->Close();
+}
+include("testdatabases.inc.php");
+
+?>
+
+
+</body>
+</html>
diff --git a/vendor/adodb/adodb-php/tests/client.php b/vendor/adodb/adodb-php/tests/client.php
new file mode 100644
index 0000000..3e63a31
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/client.php
@@ -0,0 +1,199 @@
+<html>
+<body bgcolor=white>
+<?php
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2001-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ *
+ * set tabs to 8
+ */
+
+ // documentation on usage is at http://adodb.org/dokuwiki/doku.php?id=v5:proxy:proxy_index
+
+ echo PHP_VERSION,'<br>';
+ var_dump(parse_url('odbc_mssql://userserver/'));
+ die();
+
+include('../adodb.inc.php');
+include('../tohtml.inc.php');
+
+ function send2server($url,$sql)
+ {
+ $url .= '?sql='.urlencode($sql);
+ print "<p>$url</p>";
+ $rs = csv2rs($url,$err);
+ if ($err) print $err;
+ return $rs;
+ }
+
+ function print_pre($s)
+ {
+ print "<pre>";print_r($s);print "</pre>";
+ }
+
+
+$serverURL = 'http://localhost/php/phplens/adodb/server.php';
+$testhttp = false;
+
+$sql1 = "insertz into products (productname) values ('testprod 1')";
+$sql2 = "insert into products (productname) values ('testprod 1')";
+$sql3 = "insert into products (productname) values ('testprod 2')";
+$sql4 = "delete from products where productid>80";
+$sql5 = 'select * from products';
+
+if ($testhttp) {
+ print "<a href=#c>Client Driver Tests</a><p>";
+ print "<h3>Test Error</h3>";
+ $rs = send2server($serverURL,$sql1);
+ print_pre($rs);
+ print "<hr />";
+
+ print "<h3>Test Insert</h3>";
+
+ $rs = send2server($serverURL,$sql2);
+ print_pre($rs);
+ print "<hr />";
+
+ print "<h3>Test Insert2</h3>";
+
+ $rs = send2server($serverURL,$sql3);
+ print_pre($rs);
+ print "<hr />";
+
+ print "<h3>Test Delete</h3>";
+
+ $rs = send2server($serverURL,$sql4);
+ print_pre($rs);
+ print "<hr />";
+
+
+ print "<h3>Test Select</h3>";
+ $rs = send2server($serverURL,$sql5);
+ if ($rs) rs2html($rs);
+
+ print "<hr />";
+}
+
+
+print "<a name=c><h1>CLIENT Driver Tests</h1>";
+$conn = ADONewConnection('csv');
+$conn->Connect($serverURL);
+$conn->debug = true;
+
+print "<h3>Bad SQL</h3>";
+
+$rs = $conn->Execute($sql1);
+
+print "<h3>Insert SQL 1</h3>";
+$rs = $conn->Execute($sql2);
+
+print "<h3>Insert SQL 2</h3>";
+$rs = $conn->Execute($sql3);
+
+print "<h3>Select SQL</h3>";
+$rs = $conn->Execute($sql5);
+if ($rs) rs2html($rs);
+
+print "<h3>Delete SQL</h3>";
+$rs = $conn->Execute($sql4);
+
+print "<h3>Select SQL</h3>";
+$rs = $conn->Execute($sql5);
+if ($rs) rs2html($rs);
+
+
+/* EXPECTED RESULTS FOR HTTP TEST:
+
+Test Insert
+http://localhost/php/adodb/server.php?sql=insert+into+products+%28productname%29+values+%28%27testprod%27%29
+
+adorecordset Object
+(
+ [dataProvider] => native
+ [fields] =>
+ [blobSize] => 64
+ [canSeek] =>
+ [EOF] => 1
+ [emptyTimeStamp] =>
+ [emptyDate] =>
+ [debug] =>
+ [timeToLive] => 0
+ [bind] =>
+ [_numOfRows] => -1
+ [_numOfFields] => 0
+ [_queryID] => 1
+ [_currentRow] => -1
+ [_closed] =>
+ [_inited] =>
+ [sql] => insert into products (productname) values ('testprod')
+ [affectedrows] => 1
+ [insertid] => 81
+)
+
+
+--------------------------------------------------------------------------------
+
+Test Insert2
+http://localhost/php/adodb/server.php?sql=insert+into+products+%28productname%29+values+%28%27testprod%27%29
+
+adorecordset Object
+(
+ [dataProvider] => native
+ [fields] =>
+ [blobSize] => 64
+ [canSeek] =>
+ [EOF] => 1
+ [emptyTimeStamp] =>
+ [emptyDate] =>
+ [debug] =>
+ [timeToLive] => 0
+ [bind] =>
+ [_numOfRows] => -1
+ [_numOfFields] => 0
+ [_queryID] => 1
+ [_currentRow] => -1
+ [_closed] =>
+ [_inited] =>
+ [sql] => insert into products (productname) values ('testprod')
+ [affectedrows] => 1
+ [insertid] => 82
+)
+
+
+--------------------------------------------------------------------------------
+
+Test Delete
+http://localhost/php/adodb/server.php?sql=delete+from+products+where+productid%3E80
+
+adorecordset Object
+(
+ [dataProvider] => native
+ [fields] =>
+ [blobSize] => 64
+ [canSeek] =>
+ [EOF] => 1
+ [emptyTimeStamp] =>
+ [emptyDate] =>
+ [debug] =>
+ [timeToLive] => 0
+ [bind] =>
+ [_numOfRows] => -1
+ [_numOfFields] => 0
+ [_queryID] => 1
+ [_currentRow] => -1
+ [_closed] =>
+ [_inited] =>
+ [sql] => delete from products where productid>80
+ [affectedrows] => 2
+ [insertid] => 0
+)
+
+[more stuff deleted]
+ .
+ .
+ .
+*/
diff --git a/vendor/adodb/adodb-php/tests/pdo.php b/vendor/adodb/adodb-php/tests/pdo.php
new file mode 100644
index 0000000..31ca596
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/pdo.php
@@ -0,0 +1,92 @@
+<?php
+error_reporting(E_ALL);
+include('../adodb.inc.php');
+
+echo "<pre>";
+try {
+ echo "New Connection\n";
+
+
+ $dsn = 'pdo_mysql://root:@localhost/northwind?persist';
+
+ if (!empty($dsn)) {
+ $DB = NewADOConnection($dsn) || die("CONNECT FAILED");
+ $connstr = $dsn;
+ } else {
+
+ $DB = NewADOConnection('pdo');
+
+ echo "Connect\n";
+
+ $u = ''; $p = '';
+ /*
+ $connstr = 'odbc:nwind';
+
+ $connstr = 'oci:';
+ $u = 'scott';
+ $p = 'natsoft';
+
+
+ $connstr ="sqlite:d:\inetpub\adodb\sqlite.db";
+ */
+
+ $connstr = "mysql:dbname=northwind";
+ $u = 'root';
+
+ $connstr = "pgsql:dbname=test";
+ $u = 'tester';
+ $p = 'test';
+
+ $DB->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+
+ }
+
+ echo "connection string=$connstr\n Execute\n";
+
+ //$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rs = $DB->Execute("select * from ADOXYZ where id<3");
+ if ($DB->ErrorNo()) echo "*** errno=".$DB->ErrorNo() . " ".($DB->ErrorMsg())."\n";
+
+
+ //print_r(get_class_methods($DB->_stmt));
+
+ if (!$rs) die("NO RS");
+
+ echo "Meta\n";
+ for ($i=0; $i < $rs->NumCols(); $i++) {
+ var_dump($rs->FetchField($i));
+ echo "<br>";
+ }
+
+ echo "FETCH\n";
+ $cnt = 0;
+ while (!$rs->EOF) {
+ adodb_pr($rs->fields);
+ $rs->MoveNext();
+ if ($cnt++ > 1000) break;
+ }
+
+ echo "<br>--------------------------------------------------------<br>\n\n\n";
+
+ $stmt = $DB->PrepareStmt("select * from ADOXYZ");
+
+ $rs = $stmt->Execute();
+ $cols = $stmt->NumCols(); // execute required
+
+ echo "COLS = $cols";
+ for($i=1;$i<=$cols;$i++) {
+ $v = $stmt->_stmt->getColumnMeta($i);
+ var_dump($v);
+ }
+
+ echo "e=".$stmt->ErrorNo() . " ".($stmt->ErrorMsg())."\n";
+ while ($arr = $rs->FetchRow()) {
+ adodb_pr($arr);
+ }
+ die("DONE\n");
+
+} catch (exception $e) {
+ echo "<pre>";
+ echo $e;
+ echo "</pre>";
+}
diff --git a/vendor/adodb/adodb-php/tests/test-active-record.php b/vendor/adodb/adodb-php/tests/test-active-record.php
new file mode 100644
index 0000000..5947162
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-active-record.php
@@ -0,0 +1,140 @@
+<?php
+
+ include_once('../adodb.inc.php');
+ include_once('../adodb-active-record.inc.php');
+
+ // uncomment the following if you want to test exceptions
+ if (@$_GET['except']) {
+ if (PHP_VERSION >= 5) {
+ include('../adodb-exceptions.inc.php');
+ echo "<h3>Exceptions included</h3>";
+ }
+ }
+
+ $db = NewADOConnection('mysql://root@localhost/northwind?persist');
+ $db->debug=1;
+ ADOdb_Active_Record::SetDatabaseAdapter($db);
+
+
+ $db->Execute("CREATE TEMPORARY TABLE `persons` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_color` varchar(100) NOT NULL default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $db->Execute("CREATE TEMPORARY TABLE `children` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `person_id` int(10) unsigned NOT NULL,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_pet` varchar(100) NOT NULL default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+ class Person extends ADOdb_Active_Record{ function ret($v) {return $v;} }
+ $person = new Person();
+ ADOdb_Active_Record::$_quoteNames = '111';
+
+ echo "<p>Output of getAttributeNames: ";
+ var_dump($person->getAttributeNames());
+
+ /**
+ * Outputs the following:
+ * array(4) {
+ * [0]=>
+ * string(2) "id"
+ * [1]=>
+ * string(9) "name_first"
+ * [2]=>
+ * string(8) "name_last"
+ * [3]=>
+ * string(13) "favorite_color"
+ * }
+ */
+
+ $person = new Person();
+ $person->name_first = 'Andi';
+ $person->name_last = 'Gutmans';
+ $person->save(); // this save() will fail on INSERT as favorite_color is a must fill...
+
+
+ $person = new Person();
+ $person->name_first = 'Andi';
+ $person->name_last = 'Gutmans';
+ $person->favorite_color = 'blue';
+ $person->save(); // this save will perform an INSERT successfully
+
+ echo "<p>The Insert ID generated:"; print_r($person->id);
+
+ $person->favorite_color = 'red';
+ $person->save(); // this save() will perform an UPDATE
+
+ $person = new Person();
+ $person->name_first = 'John';
+ $person->name_last = 'Lim';
+ $person->favorite_color = 'lavender';
+ $person->save(); // this save will perform an INSERT successfully
+
+ // load record where id=2 into a new ADOdb_Active_Record
+ $person2 = new Person();
+ $person2->Load('id=2');
+
+ $activeArr = $db->GetActiveRecordsClass($class = "Person",$table = "Persons","id=".$db->Param(0),array(2));
+ $person2 = $activeArr[0];
+ echo "<p>Name (should be John): ",$person->name_first, " <br> Class (should be Person): ",get_class($person2),"<br>";
+
+ $db->Execute("insert into children (person_id,name_first,name_last) values (2,'Jill','Lim')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (2,'Joan','Lim')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (2,'JAMIE','Lim')");
+
+ $newperson2 = new Person();
+ $person2->HasMany('children','person_id');
+ $person2->Load('id=2');
+ $person2->name_last='green';
+ $c = $person2->children;
+ $person2->save();
+
+ if (is_array($c) && sizeof($c) == 3 && $c[0]->name_first=='Jill' && $c[1]->name_first=='Joan'
+ && $c[2]->name_first == 'JAMIE') echo "OK Loaded HasMany</br>";
+ else {
+ var_dump($c);
+ echo "error loading hasMany should have 3 array elements Jill Joan Jamie<br>";
+ }
+
+ class Child extends ADOdb_Active_Record{};
+ $ch = new Child('children',array('id'));
+ $ch->BelongsTo('person','person_id','id');
+ $ch->Load('id=1');
+ if ($ch->name_first !== 'Jill') echo "error in Loading Child<br>";
+
+ $p = $ch->person;
+ if ($p->name_first != 'John') echo "Error loading belongsTo<br>";
+ else echo "OK loading BelongTo<br>";
+
+ $p->hasMany('children','person_id');
+ $p->LoadRelations('children', " Name_first like 'J%' order by id",1,2);
+ if (sizeof($p->children) == 2 && $p->children[1]->name_first == 'JAMIE') echo "OK LoadRelations<br>";
+ else echo "error LoadRelations<br>";
+
+ $db->Execute("CREATE TEMPORARY TABLE `persons2` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_color` varchar(100) default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $p = new adodb_active_record('persons2');
+ $p->name_first = 'James';
+
+ $p->name_last = 'James';
+
+ $p->HasMany('children','person_id');
+ $p->children;
+ var_dump($p);
+ $p->Save();
diff --git a/vendor/adodb/adodb-php/tests/test-active-recs2.php b/vendor/adodb/adodb-php/tests/test-active-recs2.php
new file mode 100644
index 0000000..f5898fc
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-active-recs2.php
@@ -0,0 +1,76 @@
+<?php
+error_reporting(E_ALL);
+include('../adodb.inc.php');
+
+include('../adodb-active-record.inc.php');
+
+###########################
+
+$ADODB_ACTIVE_CACHESECS = 36;
+
+$DBMS = @$_GET['db'];
+
+$DBMS = 'mysql';
+if ($DBMS == 'mysql') {
+ $db = NewADOConnection('mysql://root@localhost/northwind');
+} else if ($DBMS == 'postgres') {
+ $db = NewADOConnection('postgres');
+ $db->Connect("localhost","tester","test","test");
+} else
+ $db = NewADOConnection('oci8://scott:natsoft@/');
+
+
+$arr = $db->ServerInfo();
+echo "<h3>$db->dataProvider: {$arr['description']}</h3>";
+
+$arr = $db->GetActiveRecords('products',' productid<10');
+adodb_pr($arr);
+
+ADOdb_Active_Record::SetDatabaseAdapter($db);
+if (!$db) die('failed');
+
+
+
+
+$rec = new ADODB_Active_Record('photos');
+
+$rec = new ADODB_Active_Record('products');
+
+
+adodb_pr($rec->getAttributeNames());
+
+echo "<hr>";
+
+
+$rec->load('productid=2');
+adodb_pr($rec);
+
+$db->debug=1;
+
+
+$rec->productname = 'Changie Chan'.rand();
+
+$rec->insert();
+$rec->update();
+
+$rec->productname = 'Changie Chan 99';
+$rec->replace();
+
+
+$rec2 = new ADODB_Active_Record('products');
+$rec->load('productid=3');
+$rec->save();
+
+$rec = new ADODB_Active_record('products');
+$rec->productname = 'John ActiveRec';
+$rec->notes = 22;
+#$rec->productid=0;
+$rec->discontinued=1;
+$rec->Save();
+$rec->supplierid=33;
+$rec->Save();
+$rec->discontinued=0;
+$rec->Save();
+$rec->Delete();
+
+echo "<p>Affected Rows after delete=".$db->Affected_Rows()."</p>";
diff --git a/vendor/adodb/adodb-php/tests/test-active-relations.php b/vendor/adodb/adodb-php/tests/test-active-relations.php
new file mode 100644
index 0000000..7a98d47
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-active-relations.php
@@ -0,0 +1,85 @@
+<?php
+
+ include_once('../adodb.inc.php');
+ include_once('../adodb-active-record.inc.php');
+
+
+ $db = NewADOConnection('mysql://root@localhost/northwind');
+ $db->debug=1;
+ ADOdb_Active_Record::SetDatabaseAdapter($db);
+
+ $db->Execute("CREATE TEMPORARY TABLE `persons` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_color` varchar(100) NOT NULL default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $db->Execute("CREATE TEMPORARY TABLE `children` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `person_id` int(10) unsigned NOT NULL,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_pet` varchar(100) NOT NULL default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+
+ $db->Execute("insert into children (person_id,name_first,name_last) values (1,'Jill','Lim')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (1,'Joan','Lim')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (1,'JAMIE','Lim')");
+
+ ADODB_Active_Record::TableHasMany('persons', 'children','person_id');
+ class person extends ADOdb_Active_Record{}
+
+ $person = new person();
+# $person->HasMany('children','person_id'); ## this is affects all other instances of Person
+
+ $person->name_first = 'John';
+ $person->name_last = 'Lim';
+ $person->favorite_color = 'lavender';
+ $person->save(); // this save will perform an INSERT successfully
+
+ $person2 = new person();
+ $person2->Load('id=1');
+
+ $c = $person2->children;
+ if (is_array($c) && sizeof($c) == 3 && $c[0]->name_first=='Jill' && $c[1]->name_first=='Joan'
+ && $c[2]->name_first == 'JAMIE') echo "OK Loaded HasMany</br>";
+ else {
+ var_dump($c);
+ echo "error loading hasMany should have 3 array elements Jill Joan Jamie<br>";
+ }
+
+ class child extends ADOdb_Active_Record{};
+ ADODB_Active_Record::TableBelongsTo('children','person','person_id','id');
+ $ch = new Child('children',array('id'));
+
+ $ch->Load('id=1');
+ if ($ch->name_first !== 'Jill') echo "error in Loading Child<br>";
+
+ $p = $ch->person;
+ if (!$p || $p->name_first != 'John') echo "Error loading belongsTo<br>";
+ else echo "OK loading BelongTo<br>";
+
+ if ($p) {
+ #$p->HasMany('children','person_id'); ## this is affects all other instances of Person
+ $p->LoadRelations('children', 'order by id',1,2);
+ if (sizeof($p->children) == 2 && $p->children[1]->name_first == 'JAMIE') echo "OK LoadRelations<br>";
+ else {
+ var_dump($p->children);
+ echo "error LoadRelations<br>";
+ }
+
+ unset($p->children);
+ $p->LoadRelations('children', " name_first like 'J%' order by id",1,2);
+ }
+ if ($p)
+ foreach($p->children as $c) {
+ echo " Saving $c->name_first <br>";
+ $c->name_first .= ' K.';
+ $c->Save();
+ }
diff --git a/vendor/adodb/adodb-php/tests/test-active-relationsx.php b/vendor/adodb/adodb-php/tests/test-active-relationsx.php
new file mode 100644
index 0000000..0f28f72
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-active-relationsx.php
@@ -0,0 +1,418 @@
+<?php
+global $err_count;
+$err_count = 0;
+
+ function found($obj, $cond)
+ {
+ $res = var_export($obj, true);
+ return (strpos($res, $cond));
+ }
+
+ function notfound($obj, $cond)
+ {
+ return !found($obj, $cond);
+ }
+
+ function ar_assert($bool)
+ {
+ global $err_count;
+ if(!$bool)
+ $err_count ++;
+ return $bool;
+ }
+
+ define('WEB', true);
+ function ar_echo($txt)
+ {
+ if(WEB)
+ $txt = str_replace("\n", "<br />\n", $txt);
+ echo $txt;
+ }
+
+ include_once('../adodb.inc.php');
+ include_once('../adodb-active-recordx.inc.php');
+
+
+ $db = NewADOConnection('mysql://root@localhost/test');
+ $db->debug=0;
+ ADOdb_Active_Record::SetDatabaseAdapter($db);
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("Preparing database using SQL queries (creating 'people', 'children')\n");
+
+ $db->Execute("DROP TABLE `people`");
+ $db->Execute("DROP TABLE `children`");
+ $db->Execute("DROP TABLE `artists`");
+ $db->Execute("DROP TABLE `songs`");
+
+ $db->Execute("CREATE TABLE `people` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_color` varchar(100) NOT NULL default '',
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+ $db->Execute("CREATE TABLE `children` (
+ `person_id` int(10) unsigned NOT NULL,
+ `name_first` varchar(100) NOT NULL default '',
+ `name_last` varchar(100) NOT NULL default '',
+ `favorite_pet` varchar(100) NOT NULL default '',
+ `id` int(10) unsigned NOT NULL auto_increment,
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $db->Execute("CREATE TABLE `artists` (
+ `name` varchar(100) NOT NULL default '',
+ `artistuniqueid` int(10) unsigned NOT NULL auto_increment,
+ PRIMARY KEY (`artistuniqueid`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $db->Execute("CREATE TABLE `songs` (
+ `name` varchar(100) NOT NULL default '',
+ `artistid` int(10) NOT NULL,
+ `recordid` int(10) unsigned NOT NULL auto_increment,
+ PRIMARY KEY (`recordid`)
+ ) ENGINE=MyISAM;
+ ");
+
+ $db->Execute("insert into children (person_id,name_first,name_last,favorite_pet) values (1,'Jill','Lim','tortoise')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (1,'Joan','Lim')");
+ $db->Execute("insert into children (person_id,name_first,name_last) values (1,'JAMIE','Lim')");
+
+ $db->Execute("insert into artists (artistuniqueid, name) values(1,'Elvis Costello')");
+ $db->Execute("insert into songs (recordid, name, artistid) values(1,'No Hiding Place', 1)");
+ $db->Execute("insert into songs (recordid, name, artistid) values(2,'American Gangster Time', 1)");
+
+ // This class _implicitely_ relies on the 'people' table (pluralized form of 'person')
+ class Person extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct();
+ $this->hasMany('children');
+ }
+ }
+ // This class _implicitely_ relies on the 'children' table
+ class Child extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct();
+ $this->belongsTo('person');
+ }
+ }
+ // This class _explicitely_ relies on the 'children' table and shares its metadata with Child
+ class Kid extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct('children');
+ $this->belongsTo('person');
+ }
+ }
+ // This class _explicitely_ relies on the 'children' table but does not share its metadata
+ class Rugrat extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct('children', false, false, array('new' => true));
+ }
+ }
+
+ class Artist extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct('artists', array('artistuniqueid'));
+ $this->hasMany('songs', 'artistid');
+ }
+ }
+ class Song extends ADOdb_Active_Record
+ {
+ function __construct()
+ {
+ parent::__construct('songs', array('recordid'));
+ $this->belongsTo('artist', 'artistid');
+ }
+ }
+
+ ar_echo("Inserting person in 'people' table ('John Lim, he likes lavender')\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ $person->name_first = 'John';
+ $person->name_last = 'Lim';
+ $person->favorite_color = 'lavender';
+ $person->save(); // this save will perform an INSERT successfully
+
+ $person = new Person();
+ $person->name_first = 'Lady';
+ $person->name_last = 'Cat';
+ $person->favorite_color = 'green';
+ $person->save();
+
+ $child = new Child();
+ $child->name_first = 'Fluffy';
+ $child->name_last = 'Cat';
+ $child->favorite_pet = 'Cat Lady';
+ $child->person_id = $person->id;
+ $child->save();
+
+ $child = new Child();
+ $child->name_first = 'Sun';
+ $child->name_last = 'Cat';
+ $child->favorite_pet = 'Cat Lady';
+ $child->person_id = $person->id;
+ $child->save();
+
+ $err_count = 0;
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("person->Find('id=1') [Lazy Method]\n");
+ ar_echo("person is loaded but its children will be loaded on-demand later on\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ $people = $person->Find('id=1');
+ ar_echo((ar_assert(found($people, "'name_first' => 'John'"))) ? "[OK] Found John\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo("\n-- Lazily Loading Children:\n\n");
+ foreach($people as $aperson)
+ {
+ foreach($aperson->children as $achild)
+ {
+ if($achild->name_first);
+ }
+ }
+ ar_echo((ar_assert(found($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] Found relation: child\n" : "[!!] Missing relation: child\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Joan'"))) ? "[OK] Found Joan\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'JAMIE'"))) ? "[OK] Found JAMIE\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("person->Find('id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("person is loaded, and so are its children\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ $people = $person->Find('id=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($people, "'name_first' => 'John'"))) ? "[OK] Found John\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] Found relation: child\n" : "[!!] Missing relation: child\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Joan'"))) ? "[OK] Found Joan\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'JAMIE'"))) ? "[OK] Found JAMIE\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("person->Find('id=1' ... ADODB_JOIN_AR) [Join Method]\n");
+ ar_echo("person and its children are loaded using a single query\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ // When I specifically ask for a join, I have to specify which table id I am looking up
+ // otherwise the SQL parser will wonder which table's id that would be.
+ $people = $person->Find('people.id=1', false, false, array('loading' => ADODB_JOIN_AR));
+ ar_echo((ar_assert(found($people, "'name_first' => 'John'"))) ? "[OK] Found John\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] Found relation: child\n" : "[!!] Missing relation: child\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Joan'"))) ? "[OK] Found Joan\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'JAMIE'"))) ? "[OK] Found JAMIE\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("person->Load('people.id=1') [Join Method]\n");
+ ar_echo("Load() always uses the join method since it returns only one row\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ // Under the hood, Load(), since it returns only one row, always perform a join
+ // Therefore we need to clarify which id we are talking about.
+ $person->Load('people.id=1');
+ ar_echo((ar_assert(found($person, "'name_first' => 'John'"))) ? "[OK] Found John\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($person, "'favorite_pet' => 'tortoise'"))) ? "[OK] Found relation: child\n" : "[!!] Missing relation: child\n");
+ ar_echo((ar_assert(found($person, "'name_first' => 'Joan'"))) ? "[OK] Found Joan\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($person, "'name_first' => 'JAMIE'"))) ? "[OK] Found JAMIE\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("child->Load('children.id=1') [Join Method]\n");
+ ar_echo("We are now loading from the 'children' table, not from 'people'\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $child = new Child();
+ $child->Load('children.id=1');
+ ar_echo((ar_assert(found($child, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($child, "'favorite_color' => 'lavender'"))) ? "[OK] Found relation: person\n" : "[!!] Missing relation: person\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("child->Find('children.id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $child = new Child();
+ $children = $child->Find('id=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($children, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($children, "'favorite_color' => 'lavender'"))) ? "[OK] Found relation: person\n" : "[!!] Missing relation: person\n");
+ ar_echo((ar_assert(notfound($children, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($children, "'name_first' => 'JAMIE'"))) ? "[OK] No JAMIE relation\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("kid->Find('children.id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("Where we see that kid shares relationships with child because they are stored\n");
+ ar_echo("in the common table's metadata structure.\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $kid = new Kid('children');
+ $kids = $kid->Find('children.id=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($kids, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($kids, "'favorite_color' => 'lavender'"))) ? "[OK] Found relation: person\n" : "[!!] Missing relation: person\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'JAMIE'"))) ? "[OK] No JAMIE relation\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("kid->Find('children.id=1' ... ADODB_LAZY_AR) [Lazy Method]\n");
+ ar_echo("Of course, lazy loading also retrieve medata information...\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $kid = new Kid('children');
+ $kids = $kid->Find('children.id=1', false, false, array('loading' => ADODB_LAZY_AR));
+ ar_echo((ar_assert(found($kids, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($kids, "'favorite_color' => 'lavender'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo("\n-- Lazily Loading People:\n\n");
+ foreach($kids as $akid)
+ {
+ if($akid->person);
+ }
+ ar_echo((ar_assert(found($kids, "'favorite_color' => 'lavender'"))) ? "[OK] Found relation: person\n" : "[!!] Missing relation: person\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'JAMIE'"))) ? "[OK] No JAMIE relation\n" : "[!!] Found relation when I shouldn't\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("rugrat->Find('children.id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("In rugrat's constructor it is specified that\nit must forget any existing relation\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $rugrat = new Rugrat('children');
+ $rugrats = $rugrat->Find('children.id=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($rugrats, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($rugrats, "'favorite_color' => 'lavender'"))) ? "[OK] No relation found\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($rugrats, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($rugrats, "'name_first' => 'JAMIE'"))) ? "[OK] No JAMIE relation\n" : "[!!] Found relation when I shouldn't\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("kid->Find('children.id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("Note how only rugrat forgot its relations - kid is fine.\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $kid = new Kid('children');
+ $kids = $kid->Find('children.id=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($kids, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($kids, "'favorite_color' => 'lavender'"))) ? "[OK] I did not forget relation: person\n" : "[!!] I should not have forgotten relation: person\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($kids, "'name_first' => 'JAMIE'"))) ? "[OK] No JAMIE relation\n" : "[!!] Found relation when I shouldn't\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("rugrat->Find('children.id=1' ... ADODB_WORK_AR) [Worker Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $rugrat = new Rugrat('children');
+ $rugrats = $rugrat->Find('children.id=1', false, false, array('loading' => ADODB_WORK_AR));
+ $arugrat = $rugrats[0];
+ ar_echo((ar_assert(found($arugrat, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($arugrat, "'favorite_color' => 'lavender'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+
+ ar_echo("\n-- Loading relations:\n\n");
+ $arugrat->belongsTo('person');
+ $arugrat->LoadRelations('person', 'order by id', 0, 2);
+ ar_echo((ar_assert(found($arugrat, "'favorite_color' => 'lavender'"))) ? "[OK] Found relation: person\n" : "[!!] Missing relation: person\n");
+ ar_echo((ar_assert(found($arugrat, "'name_first' => 'Jill'"))) ? "[OK] Found Jill\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($arugrat, "'name_first' => 'Joan'"))) ? "[OK] No Joan relation\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($arugrat, "'name_first' => 'JAMIE'"))) ? "[OK] No Joan relation\n" : "[!!] Found relation when I shouldn't\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("person->Find('1=1') [Lazy Method]\n");
+ ar_echo("And now for our finale...\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $person = new Person();
+ $people = $person->Find('1=1', false, false, array('loading' => ADODB_LAZY_AR));
+ ar_echo((ar_assert(found($people, "'name_first' => 'John'"))) ? "[OK] Found John\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+ ar_echo((ar_assert(notfound($people, "'name_first' => 'Fluffy'"))) ? "[OK] No Fluffy yet\n" : "[!!] Found Fluffy relation when I shouldn't\n");
+ ar_echo("\n-- Lazily Loading Everybody:\n\n");
+ foreach($people as $aperson)
+ {
+ foreach($aperson->children as $achild)
+ {
+ if($achild->name_first);
+ }
+ }
+ ar_echo((ar_assert(found($people, "'favorite_pet' => 'tortoise'"))) ? "[OK] Found relation: child\n" : "[!!] Missing relation: child\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Joan'"))) ? "[OK] Found Joan\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'JAMIE'"))) ? "[OK] Found JAMIE\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Lady'"))) ? "[OK] Found Cat Lady\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Fluffy'"))) ? "[OK] Found Fluffy\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($people, "'name_first' => 'Sun'"))) ? "[OK] Found Sun\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("artist->Load('artistuniqueid=1') [Join Method]\n");
+ ar_echo("Yes, we are dabbling in the musical field now..\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $artist = new Artist();
+ $artist->Load('artistuniqueid=1');
+ ar_echo((ar_assert(found($artist, "'name' => 'Elvis Costello'"))) ? "[OK] Found Elvis Costello\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($artist, "'name' => 'No Hiding Place'"))) ? "[OK] Found relation: song\n" : "[!!] Missing relation: song\n");
+
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("song->Load('recordid=1') [Join Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $song = new Song();
+ $song->Load('recordid=1');
+ ar_echo((ar_assert(found($song, "'name' => 'No Hiding Place'"))) ? "[OK] Found song\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("artist->Find('artistuniqueid=1' ... ADODB_JOIN_AR) [Join Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $artist = new Artist();
+ $artists = $artist->Find('artistuniqueid=1', false, false, array('loading' => ADODB_JOIN_AR));
+ ar_echo((ar_assert(found($artists, "'name' => 'Elvis Costello'"))) ? "[OK] Found Elvis Costello\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($artists, "'name' => 'No Hiding Place'"))) ? "[OK] Found relation: song\n" : "[!!] Missing relation: song\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("song->Find('recordid=1' ... ADODB_JOIN_AR) [Join Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $song = new Song();
+ $songs = $song->Find('recordid=1', false, false, array('loading' => ADODB_JOIN_AR));
+ ar_echo((ar_assert(found($songs, "'name' => 'No Hiding Place'"))) ? "[OK] Found song\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("artist->Find('artistuniqueid=1' ... ADODB_WORK_AR) [Work Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $artist = new Artist();
+ $artists = $artist->Find('artistuniqueid=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($artists, "'name' => 'Elvis Costello'"))) ? "[OK] Found Elvis Costello\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(found($artists, "'name' => 'No Hiding Place'"))) ? "[OK] Found relation: song\n" : "[!!] Missing relation: song\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("song->Find('recordid=1' ... ADODB_JOIN_AR) [Join Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $song = new Song();
+ $songs = $song->Find('recordid=1', false, false, array('loading' => ADODB_WORK_AR));
+ ar_echo((ar_assert(found($songs, "'name' => 'No Hiding Place'"))) ? "[OK] Found song\n" : "[!!] Find failed\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("artist->Find('artistuniqueid=1' ... ADODB_LAZY_AR) [Lazy Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $artist = new Artist();
+ $artists = $artist->Find('artistuniqueid=1', false, false, array('loading' => ADODB_LAZY_AR));
+ ar_echo((ar_assert(found($artists, "'name' => 'Elvis Costello'"))) ? "[OK] Found Elvis Costello\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($artists, "'name' => 'No Hiding Place'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+ foreach($artists as $anartist)
+ {
+ foreach($anartist->songs as $asong)
+ {
+ if($asong->name);
+ }
+ }
+ ar_echo((ar_assert(found($artists, "'name' => 'No Hiding Place'"))) ? "[OK] Found relation: song\n" : "[!!] Missing relation: song\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("song->Find('recordid=1' ... ADODB_LAZY_AR) [Lazy Method]\n");
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
+ $song = new Song();
+ $songs = $song->Find('recordid=1', false, false, array('loading' => ADODB_LAZY_AR));
+ ar_echo((ar_assert(found($songs, "'name' => 'No Hiding Place'"))) ? "[OK] Found song\n" : "[!!] Find failed\n");
+ ar_echo((ar_assert(notfound($songs, "'name' => 'Elvis Costello'"))) ? "[OK] No relation yet\n" : "[!!] Found relation when I shouldn't\n");
+ foreach($songs as $asong)
+ {
+ if($asong->artist);
+ }
+ ar_echo((ar_assert(found($songs, "'name' => 'Elvis Costello'"))) ? "[OK] Found relation: artist\n" : "[!!] Missing relation: artist\n");
+
+ ar_echo("\n\n-------------------------------------------------------------------------------------------------------------------\n");
+ ar_echo("Test suite complete. " . (($err_count > 0) ? "$err_count errors found.\n" : "Success.\n"));
+ ar_echo("-------------------------------------------------------------------------------------------------------------------\n");
diff --git a/vendor/adodb/adodb-php/tests/test-datadict.php b/vendor/adodb/adodb-php/tests/test-datadict.php
new file mode 100644
index 0000000..9793f44
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-datadict.php
@@ -0,0 +1,251 @@
+<?php
+/*
+
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Set tabs to 4 for best viewing.
+
+*/
+
+error_reporting(E_ALL);
+include_once('../adodb.inc.php');
+
+foreach(array('sapdb','sybase','mysql','access','oci8po','odbc_mssql','odbc','db2','firebird','postgres','informix') as $dbType) {
+ echo "<h3>$dbType</h3><p>";
+ $db = NewADOConnection($dbType);
+ $dict = NewDataDictionary($db);
+
+ if (!$dict) continue;
+ $dict->debug = 1;
+
+ $opts = array('REPLACE','mysql' => 'ENGINE=INNODB', 'oci8' => 'TABLESPACE USERS');
+
+/* $flds = array(
+ array('id', 'I',
+ 'AUTO','KEY'),
+
+ array('name' => 'firstname', 'type' => 'varchar','size' => 30,
+ 'DEFAULT'=>'Joan'),
+
+ array('lastname','varchar',28,
+ 'DEFAULT'=>'Chen','key'),
+
+ array('averylonglongfieldname','X',1024,
+ 'NOTNULL','default' => 'test'),
+
+ array('price','N','7.2',
+ 'NOTNULL','default' => '0.00'),
+
+ array('MYDATE', 'D',
+ 'DEFDATE'),
+ array('TS','T',
+ 'DEFTIMESTAMP')
+ );*/
+
+ $flds = "
+ID I AUTO KEY,
+FIRSTNAME VARCHAR(30) DEFAULT 'Joan' INDEX idx_name,
+LASTNAME VARCHAR(28) DEFAULT 'Chen' key INDEX idx_name INDEX idx_lastname,
+averylonglongfieldname X(1024) DEFAULT 'test',
+price N(7.2) DEFAULT '0.00',
+MYDATE D DEFDATE INDEX idx_date,
+BIGFELLOW X NOTNULL,
+TS_SECS T DEFTIMESTAMP,
+TS_SUBSEC TS DEFTIMESTAMP
+";
+
+
+ $sqla = $dict->CreateDatabase('KUTU',array('postgres'=>"LOCATION='/u01/postdata'"));
+ $dict->SetSchema('KUTU');
+
+ $sqli = ($dict->CreateTableSQL('testtable',$flds, $opts));
+ $sqla = array_merge($sqla,$sqli);
+
+ $sqli = $dict->CreateIndexSQL('idx','testtable','price,firstname,lastname',array('BITMAP','FULLTEXT','CLUSTERED','HASH'));
+ $sqla = array_merge($sqla,$sqli);
+ $sqli = $dict->CreateIndexSQL('idx2','testtable','price,lastname');//,array('BITMAP','FULLTEXT','CLUSTERED'));
+ $sqla = array_merge($sqla,$sqli);
+
+ $addflds = array(array('height', 'F'),array('weight','F'));
+ $sqli = $dict->AddColumnSQL('testtable',$addflds);
+ $sqla = array_merge($sqla,$sqli);
+ $addflds = array(array('height', 'F','NOTNULL'),array('weight','F','NOTNULL'));
+ $sqli = $dict->AlterColumnSQL('testtable',$addflds);
+ $sqla = array_merge($sqla,$sqli);
+
+
+ printsqla($dbType,$sqla);
+
+ if (file_exists('d:\inetpub\wwwroot\php\phplens\adodb\adodb.inc.php'))
+ if ($dbType == 'mysqlt') {
+ $db->Connect('localhost', "root", "", "test");
+ $dict->SetSchema('');
+ $sqla2 = $dict->ChangeTableSQL('adoxyz',$flds);
+ if ($sqla2) printsqla($dbType,$sqla2);
+ }
+ if ($dbType == 'postgres') {
+ if (@$db->Connect('localhost', "tester", "test", "test"));
+ $dict->SetSchema('');
+ $sqla2 = $dict->ChangeTableSQL('adoxyz',$flds);
+ if ($sqla2) printsqla($dbType,$sqla2);
+ }
+
+ if ($dbType == 'odbc_mssql') {
+ $dsn = $dsn = "PROVIDER=MSDASQL;Driver={SQL Server};Server=localhost;Database=northwind;";
+ if (@$db->Connect($dsn, "sa", "natsoft", "test"));
+ $dict->SetSchema('');
+ $sqla2 = $dict->ChangeTableSQL('adoxyz',$flds);
+ if ($sqla2) printsqla($dbType,$sqla2);
+ }
+
+
+
+ adodb_pr($dict->databaseType);
+ printsqla($dbType, $dict->DropColumnSQL('table',array('my col','`col2_with_Quotes`','A_col3','col3(10)')));
+ printsqla($dbType, $dict->ChangeTableSQL('adoxyz','LASTNAME varchar(32)'));
+
+}
+
+function printsqla($dbType,$sqla)
+{
+ print "<pre>";
+ //print_r($dict->MetaTables());
+ foreach($sqla as $s) {
+ $s = htmlspecialchars($s);
+ print "$s;\n";
+ if ($dbType == 'oci8') print "/\n";
+ }
+ print "</pre><hr />";
+}
+
+/***
+
+Generated SQL:
+
+mysql
+
+CREATE DATABASE KUTU;
+DROP TABLE KUTU.testtable;
+CREATE TABLE KUTU.testtable (
+id INTEGER NOT NULL AUTO_INCREMENT,
+firstname VARCHAR(30) DEFAULT 'Joan',
+lastname VARCHAR(28) NOT NULL DEFAULT 'Chen',
+averylonglongfieldname LONGTEXT NOT NULL,
+price NUMERIC(7,2) NOT NULL DEFAULT 0.00,
+MYDATE DATE DEFAULT CURDATE(),
+ PRIMARY KEY (id, lastname)
+)TYPE=ISAM;
+CREATE FULLTEXT INDEX idx ON KUTU.testtable (firstname,lastname);
+CREATE INDEX idx2 ON KUTU.testtable (price,lastname);
+ALTER TABLE KUTU.testtable ADD height DOUBLE;
+ALTER TABLE KUTU.testtable ADD weight DOUBLE;
+ALTER TABLE KUTU.testtable MODIFY COLUMN height DOUBLE NOT NULL;
+ALTER TABLE KUTU.testtable MODIFY COLUMN weight DOUBLE NOT NULL;
+
+
+--------------------------------------------------------------------------------
+
+oci8
+
+CREATE USER KUTU IDENTIFIED BY tiger;
+/
+GRANT CREATE SESSION, CREATE TABLE,UNLIMITED TABLESPACE,CREATE SEQUENCE TO KUTU;
+/
+DROP TABLE KUTU.testtable CASCADE CONSTRAINTS;
+/
+CREATE TABLE KUTU.testtable (
+id NUMBER(16) NOT NULL,
+firstname VARCHAR(30) DEFAULT 'Joan',
+lastname VARCHAR(28) DEFAULT 'Chen' NOT NULL,
+averylonglongfieldname CLOB NOT NULL,
+price NUMBER(7,2) DEFAULT 0.00 NOT NULL,
+MYDATE DATE DEFAULT TRUNC(SYSDATE),
+ PRIMARY KEY (id, lastname)
+)TABLESPACE USERS;
+/
+DROP SEQUENCE KUTU.SEQ_testtable;
+/
+CREATE SEQUENCE KUTU.SEQ_testtable;
+/
+CREATE OR REPLACE TRIGGER KUTU.TRIG_SEQ_testtable BEFORE insert ON KUTU.testtable
+ FOR EACH ROW
+ BEGIN
+ select KUTU.SEQ_testtable.nextval into :new.id from dual;
+ END;
+/
+CREATE BITMAP INDEX idx ON KUTU.testtable (firstname,lastname);
+/
+CREATE INDEX idx2 ON KUTU.testtable (price,lastname);
+/
+ALTER TABLE testtable ADD (
+ height NUMBER,
+ weight NUMBER);
+/
+ALTER TABLE testtable MODIFY(
+ height NUMBER NOT NULL,
+ weight NUMBER NOT NULL);
+/
+
+
+--------------------------------------------------------------------------------
+
+postgres
+AlterColumnSQL not supported for PostgreSQL
+
+
+CREATE DATABASE KUTU LOCATION='/u01/postdata';
+DROP TABLE KUTU.testtable;
+CREATE TABLE KUTU.testtable (
+id SERIAL,
+firstname VARCHAR(30) DEFAULT 'Joan',
+lastname VARCHAR(28) DEFAULT 'Chen' NOT NULL,
+averylonglongfieldname TEXT NOT NULL,
+price NUMERIC(7,2) DEFAULT 0.00 NOT NULL,
+MYDATE DATE DEFAULT CURRENT_DATE,
+ PRIMARY KEY (id, lastname)
+);
+CREATE INDEX idx ON KUTU.testtable USING HASH (firstname,lastname);
+CREATE INDEX idx2 ON KUTU.testtable (price,lastname);
+ALTER TABLE KUTU.testtable ADD height FLOAT8;
+ALTER TABLE KUTU.testtable ADD weight FLOAT8;
+
+
+--------------------------------------------------------------------------------
+
+odbc_mssql
+
+CREATE DATABASE KUTU;
+DROP TABLE KUTU.testtable;
+CREATE TABLE KUTU.testtable (
+id INT IDENTITY(1,1) NOT NULL,
+firstname VARCHAR(30) DEFAULT 'Joan',
+lastname VARCHAR(28) DEFAULT 'Chen' NOT NULL,
+averylonglongfieldname TEXT NOT NULL,
+price NUMERIC(7,2) DEFAULT 0.00 NOT NULL,
+MYDATE DATETIME DEFAULT GetDate(),
+ PRIMARY KEY (id, lastname)
+);
+CREATE CLUSTERED INDEX idx ON KUTU.testtable (firstname,lastname);
+CREATE INDEX idx2 ON KUTU.testtable (price,lastname);
+ALTER TABLE KUTU.testtable ADD
+ height REAL,
+ weight REAL;
+ALTER TABLE KUTU.testtable ALTER COLUMN height REAL NOT NULL;
+ALTER TABLE KUTU.testtable ALTER COLUMN weight REAL NOT NULL;
+
+
+--------------------------------------------------------------------------------
+*/
+
+
+echo "<h1>Test XML Schema</h1>";
+$ff = file('xmlschema.xml');
+echo "<pre>";
+foreach($ff as $xml) echo htmlspecialchars($xml);
+echo "</pre>";
+include_once('test-xmlschema.php');
diff --git a/vendor/adodb/adodb-php/tests/test-perf.php b/vendor/adodb/adodb-php/tests/test-perf.php
new file mode 100644
index 0000000..62465be
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-perf.php
@@ -0,0 +1,48 @@
+<?php
+
+include_once('../adodb-perf.inc.php');
+
+error_reporting(E_ALL);
+session_start();
+
+if (isset($_GET)) {
+ foreach($_GET as $k => $v) {
+ if (strncmp($k,'test',4) == 0) $_SESSION['_db'] = $k;
+ }
+}
+
+if (isset($_SESSION['_db'])) {
+ $_db = $_SESSION['_db'];
+ $_GET[$_db] = 1;
+ $$_db = 1;
+}
+
+echo "<h1>Performance Monitoring</h1>";
+include_once('testdatabases.inc.php');
+
+
+function testdb($db)
+{
+ if (!$db) return;
+ echo "<font size=1>";print_r($db->ServerInfo()); echo " user=".$db->user."</font>";
+
+ $perf = NewPerfMonitor($db);
+
+ # unit tests
+ if (0) {
+ //$DB->debug=1;
+ echo "Data Cache Size=".$perf->DBParameter('data cache size').'<p>';
+ echo $perf->HealthCheck();
+ echo($perf->SuspiciousSQL());
+ echo($perf->ExpensiveSQL());
+ echo($perf->InvalidSQL());
+ echo $perf->Tables();
+
+ echo "<pre>";
+ echo $perf->HealthCheckCLI();
+ $perf->Poll(3);
+ die();
+ }
+
+ if ($perf) $perf->UI(3);
+}
diff --git a/vendor/adodb/adodb-php/tests/test-pgblob.php b/vendor/adodb/adodb-php/tests/test-pgblob.php
new file mode 100644
index 0000000..3add99e
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-pgblob.php
@@ -0,0 +1,86 @@
+<?php
+
+function getmicrotime()
+{
+ $t = microtime();
+ $t = explode(' ',$t);
+ return (float)$t[1]+ (float)$t[0];
+}
+
+function doloop()
+{
+global $db,$MAX;
+
+ $sql = "select id,firstname,lastname from adoxyz where
+ firstname not like ? and lastname not like ? and id=?";
+ $offset = 0;
+ /*$sql = "select * from juris9.employee join juris9.emp_perf_plan on epp_empkey = emp_id
+ where emp_name not like ? and emp_name not like ? and emp_id=28000+?";
+ $offset = 28000;*/
+ for ($i=1; $i <= $MAX; $i++) {
+ $db->Param(false);
+ $x = (rand() % 10) + 1;
+ $db->debug= ($i==1);
+ $id = $db->GetOne($sql,
+ array('Z%','Z%',$x));
+ if($id != $offset+$x) {
+ print "<p>Error at $x";
+ break;
+ }
+ }
+}
+
+include_once('../adodb.inc.php');
+$db = NewADOConnection('postgres7');
+$db->PConnect('localhost','tester','test','test') || die("failed connection");
+
+$enc = "GIF89a%01%00%01%00%80%FF%00%C0%C0%C0%00%00%00%21%F9%04%01%00%00%00%00%2C%00%00%00%00%01%00%01%00%00%01%012%00%3Bt_clear.gif%0D";
+$val = rawurldecode($enc);
+
+$MAX = 1000;
+
+adodb_pr($db->ServerInfo());
+
+echo "<h4>Testing PREPARE/EXECUTE PLAN</h4>";
+
+
+$db->_bindInputArray = true; // requires postgresql 7.3+ and ability to modify database
+$t = getmicrotime();
+doloop();
+echo '<p>',$MAX,' times, with plan=',getmicrotime() - $t,'</p>';
+
+
+$db->_bindInputArray = false;
+$t = getmicrotime();
+doloop();
+echo '<p>',$MAX,' times, no plan=',getmicrotime() - $t,'</p>';
+
+
+
+echo "<h4>Testing UPDATEBLOB</h4>";
+$db->debug=1;
+
+### TEST BEGINS
+
+$db->Execute("insert into photos (id,name) values(9999,'dot.gif')");
+$db->UpdateBlob('photos','photo',$val,'id=9999');
+$v = $db->GetOne('select photo from photos where id=9999');
+
+
+### CLEANUP
+
+$db->Execute("delete from photos where id=9999");
+
+### VALIDATION
+
+if ($v !== $val) echo "<b>*** ERROR: Inserted value does not match downloaded val<b>";
+else echo "<b>*** OK: Passed</b>";
+
+echo "<pre>";
+echo "INSERTED: ", $enc;
+echo "<hr />";
+echo"RETURNED: ", rawurlencode($v);
+echo "<hr /><p>";
+echo "INSERTED: ", $val;
+echo "<hr />";
+echo "RETURNED: ", $v;
diff --git a/vendor/adodb/adodb-php/tests/test-php5.php b/vendor/adodb/adodb-php/tests/test-php5.php
new file mode 100644
index 0000000..4fe7ed8
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-php5.php
@@ -0,0 +1,116 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+ */
+
+
+error_reporting(E_ALL);
+
+$path = dirname(__FILE__);
+
+include("$path/../adodb-exceptions.inc.php");
+include("$path/../adodb.inc.php");
+
+echo "<h3>PHP ".PHP_VERSION."</h3>\n";
+try {
+
+$dbt = 'oci8po';
+
+try {
+switch($dbt) {
+case 'oci8po':
+ $db = NewADOConnection("oci8po");
+
+ $db->Connect('localhost','scott','natsoft','sherkhan');
+ break;
+default:
+case 'mysql':
+ $db = NewADOConnection("mysql");
+ $db->Connect('localhost','root','','northwind');
+ break;
+
+case 'mysqli':
+ $db = NewADOConnection("mysqli://root:@localhost/northwind");
+ //$db->Connect('localhost','root','','test');
+ break;
+}
+} catch (exception $e){
+ echo "Connect Failed";
+ adodb_pr($e);
+ die();
+}
+
+$db->debug=1;
+
+$cnt = $db->GetOne("select count(*) from adoxyz where ?<id and id<?",array(10,20));
+$stmt = $db->Prepare("select * from adoxyz where ?<id and id<?");
+if (!$stmt) echo $db->ErrorMsg(),"\n";
+$rs = $db->Execute($stmt,array(10,20));
+
+echo "<hr /> Foreach Iterator Test (rand=".rand().")<hr />";
+$i = 0;
+foreach($rs as $v) {
+ $i += 1;
+ echo "rec $i: "; $s1 = adodb_pr($v,true); $s2 = adodb_pr($rs->fields,true);
+ if ($s1 != $s2 && !empty($v)) {adodb_pr($s1); adodb_pr($s2);}
+ else echo "passed<br>";
+ flush();
+}
+
+$rs = new ADORecordSet_empty();
+foreach($rs as $v) {
+ echo "<p>empty ";var_dump($v);
+}
+
+
+if ($i != $cnt) die("actual cnt is $i, cnt should be $cnt\n");
+else echo "Count $i is correct<br>";
+
+$rs = $db->Execute("select bad from badder");
+
+} catch (exception $e) {
+ adodb_pr($e);
+ echo "<h3>adodb_backtrace:</h3>\n";
+ $e = adodb_backtrace($e->gettrace());
+}
+
+$rs = $db->Execute("select distinct id, firstname,lastname from adoxyz order by id");
+echo "Result=\n",$rs,"</p>";
+
+echo "<h3>Active Record</h3>";
+
+ include_once("../adodb-active-record.inc.php");
+ ADOdb_Active_Record::SetDatabaseAdapter($db);
+
+try {
+ class City extends ADOdb_Active_Record{};
+ $a = new City();
+
+} catch(exception $e){
+ echo $e->getMessage();
+}
+
+try {
+
+ $a = new City();
+
+ echo "<p>Successfully created City()<br>";
+ #var_dump($a->GetPrimaryKeys());
+ $a->city = 'Kuala Lumpur';
+ $a->Save();
+ $a->Update();
+ #$a->SetPrimaryKeys(array('city'));
+ $a->country = "M'sia";
+ $a->save();
+ $a->Delete();
+} catch(exception $e){
+ echo $e->getMessage();
+}
+
+//include_once("test-active-record.php");
diff --git a/vendor/adodb/adodb-php/tests/test-xmlschema.php b/vendor/adodb/adodb-php/tests/test-xmlschema.php
new file mode 100644
index 0000000..c56cfec
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test-xmlschema.php
@@ -0,0 +1,53 @@
+<?PHP
+
+// V4.50 6 July 2004
+
+error_reporting(E_ALL);
+include_once( "../adodb.inc.php" );
+include_once( "../adodb-xmlschema03.inc.php" );
+
+// To build the schema, start by creating a normal ADOdb connection:
+$db = ADONewConnection( 'mysql' );
+$db->Connect( 'localhost', 'root', '', 'test' ) || die('fail connect1');
+
+// To create a schema object and build the query array.
+$schema = new adoSchema( $db );
+
+// To upgrade an existing schema object, use the following
+// To upgrade an existing database to the provided schema,
+// uncomment the following line:
+#$schema->upgradeSchema();
+
+print "<b>SQL to build xmlschema.xml</b>:\n<pre>";
+// Build the SQL array
+$sql = $schema->ParseSchema( "xmlschema.xml" );
+
+var_dump( $sql );
+print "</pre>\n";
+
+// Execute the SQL on the database
+//$result = $schema->ExecuteSchema( $sql );
+
+// Finally, clean up after the XML parser
+// (PHP won't do this for you!)
+//$schema->Destroy();
+
+
+
+print "<b>SQL to build xmlschema-mssql.xml</b>:\n<pre>";
+
+$db2 = ADONewConnection('mssql');
+$db2->Connect('','adodb','natsoft','northwind') || die("Fail 2");
+
+$db2->Execute("drop table simple_table");
+
+$schema = new adoSchema( $db2 );
+$sql = $schema->ParseSchema( "xmlschema-mssql.xml" );
+
+print_r( $sql );
+print "</pre>\n";
+
+$db2->debug=1;
+
+foreach ($sql as $s)
+$db2->Execute($s);
diff --git a/vendor/adodb/adodb-php/tests/test.php b/vendor/adodb/adodb-php/tests/test.php
new file mode 100644
index 0000000..030e85c
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test.php
@@ -0,0 +1,1781 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+
+//if (!defined('E_STRICT')) define('E_STRICT',0);
+error_reporting(E_ALL|E_STRICT);
+
+$ADODB_FLUSH = true;
+
+define('ADODB_ASSOC_CASE',0);
+
+
+function getmicrotime()
+{
+ $t = microtime();
+ $t = explode(' ',$t);
+ return (float)$t[1]+ (float)$t[0];
+}
+
+
+if (PHP_VERSION < 5) include_once('../adodb-pear.inc.php');
+//--------------------------------------------------------------------------------------
+//define('ADODB_ASSOC_CASE',1);
+//
+function Err($msg)
+{
+ print "<b>$msg</b><br>";
+ flush();
+}
+
+function CheckWS($conn)
+{
+global $ADODB_EXTENSION;
+
+ include_once('../session/adodb-session.php');
+ if (defined('CHECKWSFAIL')){ echo " TESTING $conn ";flush();}
+ $saved = $ADODB_EXTENSION;
+ $db = ADONewConnection($conn);
+ $ADODB_EXTENSION = $saved;
+ if (headers_sent()) {
+ print "<p><b>White space detected in adodb-$conn.inc.php or include file...</b></p>";
+ //die();
+ }
+}
+
+function do_strtolower(&$arr)
+{
+ foreach($arr as $k => $v) {
+ if (is_object($v)) $arr[$k] = adodb_pr($v,true);
+ else $arr[$k] = strtolower($v);
+ }
+}
+
+
+function CountExecs($db, $sql, $inputarray)
+{
+global $EXECS; $EXECS++;
+}
+
+function CountCachedExecs($db, $secs2cache, $sql, $inputarray)
+{
+global $CACHED; $CACHED++;
+}
+
+// the table creation code is specific to the database, so we allow the user
+// to define their own table creation stuff
+
+function testdb(&$db,$createtab="create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)")
+{
+GLOBAL $ADODB_vers,$ADODB_CACHE_DIR,$ADODB_FETCH_MODE,$ADODB_COUNTRECS;
+
+ //adodb_pr($db);
+
+?> <form method=GET>
+ </p>
+ <table width=100% ><tr><td bgcolor=beige>&nbsp;</td></tr></table>
+ </p>
+<?php
+ $create =false;
+ /*$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ $rs = $db->Execute('select lastname,firstname,lastname,id from ADOXYZ');
+ $arr = $rs->GetAssoc();
+ echo "<pre>";print_r($arr);
+ die();*/
+
+ if (!$db) die("testdb: database not inited");
+ GLOBAL $EXECS, $CACHED;
+
+ $EXECS = 0;
+ $CACHED = 0;
+ //$db->Execute("drop table adodb_logsql");
+ if ((rand()%3) == 0) @$db->Execute("delete from adodb_logsql");
+ $db->debug=1;
+
+ $db->fnExecute = 'CountExecs';
+ $db->fnCacheExecute = 'CountCachedExecs';
+
+ if (empty($_GET['nolog'])) {
+ echo "<h3>SQL Logging enabled</h3>";
+ $db->LogSQL();/*
+ $sql =
+"SELECT t1.sid, t1.sid, t1.title, t1.hometext, t1.notes, t1.aid, t1.informant,
+t2.url, t2.email, t1.catid, t3.title, t1.topic, t4.topicname, t4.topicimage,
+t4.topictext, t1.score, t1.ratings, t1.counter, t1.comments, t1.acomm
+FROM `nuke_stories` `t1`, `nuke_authors` `t2`, `nuke_stories_cat` `t3`, `nuke_topics` `t4`
+ WHERE ((t2.aid=t1.aid) AND (t3.catid=t1.catid) AND (t4.topicid=t1.topic)
+ AND ((t1.alanguage='german') OR (t1.alanguage='')) AND (t1.ihome='0'))
+ ORDER BY t1.time DESC";
+ $db->SelectLimit($sql);
+ echo $db->ErrorMsg();*/
+ }
+ $ADODB_CACHE_DIR = dirname(TempNam('/tmp','testadodb'));
+ $db->debug = false;
+ //print $db->UnixTimeStamp('2003-7-22 23:00:00');
+
+ $phpv = phpversion();
+ if (defined('ADODB_EXTENSION')) $ext = ' &nbsp; Extension '.ADODB_EXTENSION.' installed';
+ else $ext = '';
+ print "<h3>ADODB Version: $ADODB_vers";
+ print "<p>Host: <i>$db->host</i>";
+ print "<br>Database: <i>$db->database</i>";
+ print "<br>PHP: <i>$phpv $ext</i></h3>";
+
+ flush();
+
+ print "Current timezone: " . date_default_timezone_get() . "<p>";
+
+ $arr = $db->ServerInfo();
+ print_r($arr);
+ echo E_ALL,' ',E_STRICT, "<br>";
+ $e = error_reporting(E_ALL | E_STRICT);
+ echo error_reporting(),'<p>';
+ flush();
+ #$db->debug=1;
+ $tt = $db->Time();
+ if ($tt == 0) echo '<br><b>$db->Time failed</b>';
+ else echo "<br>db->Time: ".date('d-m-Y H:i:s',$tt);
+ echo '<br>';
+
+ echo "Date=",$db->UserDate('2002-04-07'),'<br>';
+ print "<i>date1</i> (1969-02-20) = ".$db->DBDate('1969-2-20');
+ print "<br><i>date1</i> (1999-02-20) = ".$db->DBDate('1999-2-20');
+ print "<br><i>date1.1</i> 1999 injection attack= ".$db->DBDate("'1999', ' injection attack '");
+ print "<br><i>date2</i> (1970-1-2) = ".$db->DBDate(24*3600)."<p>";
+ print "<i>ts1</i> (1999-02-20 13:40:50) = ".$db->DBTimeStamp('1999-2-20 1:40:50 pm');
+ print "<br><i>ts1.1</i> (1999-02-20 13:40:00) = ".$db->DBTimeStamp('1999-2-20 13:40');
+ print "<br><i>ts2</i> (1999-02-20) = ".$db->DBTimeStamp('1999-2-20');
+ print "<br><i>ts2</i> (1999-02-20) = ".$db->DBTimeStamp("'1999-2-20', 'injection attack'");
+ print "<br><i>ts3</i> (1970-1-2 +/- timezone) = ".$db->DBTimeStamp(24*3600);
+ print "<br> Fractional TS (1999-2-20 13:40:50.91): ".$db->DBTimeStamp($db->UnixTimeStamp('1999-2-20 13:40:50.91+1'));
+ $dd = $db->UnixDate('1999-02-20');
+ print "<br>unixdate</i> 1999-02-20 = ".date('Y-m-d',$dd)."<p>";
+ print "<br><i>ts4</i> =".($db->UnixTimeStamp("19700101000101")+8*3600);
+ print "<br><i>ts5</i> =".$db->DBTimeStamp($db->UnixTimeStamp("20040110092123"));
+ print "<br><i>ts6</i> =".$db->UserTimeStamp("20040110092123");
+ print "<br><i>ts7</i> =".$db->DBTimeStamp("20040110092123");
+ flush();
+ // mssql too slow in failing bad connection
+ if (false && $db->databaseType != 'mssql') {
+ print "<p>Testing bad connection. Ignore following error msgs:<br>";
+ $db2 = ADONewConnection();
+ $rez = $db2->Connect("bad connection");
+ $err = $db2->ErrorMsg();
+ print "<i>Error='$err'</i></p>";
+ if ($rez) print "<b>Cannot check if connection failed.</b> The Connect() function returned true.</p>";
+ }
+ #error_reporting($e);
+ flush();
+
+ //$ADODB_COUNTRECS=false;
+ $rs=$db->Execute('select * from ADOXYZ order by id');
+ if($rs === false) $create = true;
+ else $rs->Close();
+
+ //if ($db->databaseType !='vfp') $db->Execute("drop table ADOXYZ");
+
+ if ($create) {
+ if (false && $db->databaseType == 'ibase') {
+ print "<b>Please create the following table for testing:</b></p>$createtab</p>";
+ return;
+ } else {
+ $db->debug = 99;
+ # $e = error_reporting(E_ALL-E_WARNING);
+ $db->Execute($createtab);
+ # error_reporting($e);
+ }
+ }
+ #error_reporting(E_ALL);
+ echo "<p>Testing Metatypes</p>";
+ $t = $db->MetaType('varchar');
+ if ($t != 'C') Err("Bad Metatype for varchar");
+
+ $rs = $db->Execute("delete from ADOXYZ"); // some ODBC drivers will fail the drop so we delete
+ if ($rs) {
+ if(! $rs->EOF) print "<b>Error: </b>RecordSet returned by Execute('delete...') should show EOF</p>";
+ $rs->Close();
+ } else print "err=".$db->ErrorMsg();
+
+ print "<p>Test select on empty table, FetchField when EOF, and GetInsertSQL</p>";
+ $rs = $db->Execute("select id,firstname from ADOXYZ where id=9999");
+ if ($rs && !$rs->EOF) print "<b>Error: </b>RecordSet returned by Execute(select...') on empty table should show EOF</p>";
+ if ($rs->EOF && (($ox = $rs->FetchField(0)) && !empty($ox->name))) {
+ $record['id'] = 99;
+ $record['firstname'] = 'John';
+ $sql = $db->GetInsertSQL($rs, $record);
+ if (strtoupper($sql) != strtoupper("INSERT INTO ADOXYZ ( id, firstname ) VALUES ( 99, 'John' )")) Err("GetInsertSQL does not work on empty table: $sql");
+ } else {
+ Err("FetchField does not work on empty recordset, meaning GetInsertSQL will fail...");
+ }
+ if ($rs) $rs->Close();
+ flush();
+ //$db->debug=true;
+ print "<p>Testing Commit: ";
+ $time = $db->DBDate(time());
+ if (!$db->BeginTrans()) {
+ print '<b>Transactions not supported</b></p>';
+ if ($db->hasTransactions) Err("hasTransactions should be false");
+ } else { /* COMMIT */
+ if (!$db->hasTransactions) Err("hasTransactions should be true");
+ if ($db->transCnt != 1) Err("Invalid transCnt = $db->transCnt (should be 1)");
+ $rs = $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values (99,'Should Not','Exist (Commit)',$time)");
+ if ($rs && $db->CommitTrans()) {
+ $rs->Close();
+ $rs = $db->Execute("select * from ADOXYZ where id=99");
+ if ($rs === false || $rs->EOF) {
+ print '<b>Data not saved</b></p>';
+ $rs = $db->Execute("select * from ADOXYZ where id=99");
+ print_r($rs);
+ die();
+ } else print 'OK</p>';
+ if ($rs) $rs->Close();
+ } else {
+ if (!$rs) {
+ print "<b>Insert failed</b></p>";
+ $db->RollbackTrans();
+ } else print "<b>Commit failed</b></p>";
+ }
+ if ($db->transCnt != 0) Err("Invalid transCnt = $db->transCnt (should be 0)");
+
+ /* ROLLBACK */
+ if (!$db->BeginTrans()) print "<p><b>Error in BeginTrans</b>()</p>";
+ print "<p>Testing Rollback: ";
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values (100,'Should Not','Exist (Rollback)',$time)");
+ if ($db->RollbackTrans()) {
+ $rs = $db->Execute("select * from ADOXYZ where id=100");
+ if ($rs && !$rs->EOF) print '<b>Fail: Data should rollback</b></p>';
+ else print 'OK</p>';
+ if ($rs) $rs->Close();
+ } else
+ print "<b>Commit failed</b></p>";
+
+ $rs = $db->Execute('delete from ADOXYZ where id>50');
+ if ($rs) $rs->Close();
+
+ if ($db->transCnt != 0) Err("Invalid transCnt = $db->transCnt (should be 0)");
+ }
+
+ if (1) {
+ print "<p>Testing MetaDatabases()</p>";
+ print_r( $db->MetaDatabases());
+
+ print "<p>Testing MetaTables() and MetaColumns()</p>";
+ $a = $db->MetaTables();
+ if ($a===false) print "<b>MetaTables not supported</b></p>";
+ else {
+ print "Array of tables and views: ";
+ foreach($a as $v) print " ($v) ";
+ print '</p>';
+ }
+
+ $a = $db->MetaTables('VIEW');
+ if ($a===false) print "<b>MetaTables not supported (views)</b></p>";
+ else {
+ print "Array of views: ";
+ foreach($a as $v) print " ($v) ";
+ print '</p>';
+ }
+
+ $a = $db->MetaTables(false,false,'aDo%');
+ if ($a===false) print "<b>MetaTables not supported (mask)</b></p>";
+ else {
+ print "Array of ado%: ";
+ foreach($a as $v) print " ($v) ";
+ print '</p>';
+ }
+
+ $a = $db->MetaTables('TABLE');
+ if ($a===false) print "<b>MetaTables not supported</b></p>";
+ else {
+ print "Array of tables: ";
+ foreach($a as $v) print " ($v) ";
+ print '</p>';
+ }
+
+ $db->debug=0;
+ $rez = $db->MetaColumns("NOSUCHTABLEHERE");
+ if ($rez !== false) {
+ Err("MetaColumns error handling failed");
+ var_dump($rez);
+ }
+ $db->debug=1;
+ $a = $db->MetaColumns('ADOXYZ');
+ if ($a===false) print "<b>MetaColumns not supported</b></p>";
+ else {
+ print "<p>Columns of ADOXYZ: <font size=1><br>";
+ foreach($a as $v) {print_r($v); echo "<br>";}
+ echo "</font>";
+ }
+
+ print "<p>Testing MetaIndexes</p>";
+
+ $a = $db->MetaIndexes(('ADOXYZ'),true);
+ if ($a===false) print "<b>MetaIndexes not supported</b></p>";
+ else {
+ print "<p>Indexes of ADOXYZ: <font size=1><br>";
+ adodb_pr($a);
+ echo "</font>";
+ }
+ print "<p>Testing MetaPrimaryKeys</p>";
+ $a = $db->MetaPrimaryKeys('ADOXYZ');
+ var_dump($a);
+ }
+ $rs = $db->Execute('delete from ADOXYZ');
+ if ($rs) $rs->Close();
+
+ $db->debug = false;
+
+
+ switch ($db->databaseType) {
+ case 'vfp':
+
+ if (0) {
+ // memo test
+ $rs = $db->Execute("select data from memo");
+ rs2html($rs);
+ }
+ break;
+
+ case 'postgres7':
+ case 'postgres64':
+ case 'postgres':
+ case 'ibase':
+ print "<p>Encode=".$db->BlobEncode("abc\0d\"'
+ef")."</p>";//'
+
+ print "<p>Testing Foreign Keys</p>";
+ $arr = $db->MetaForeignKeys('ADOXYZ',false,true);
+ print_r($arr);
+ if (!$arr) Err("No MetaForeignKeys");
+ break;
+
+ case 'odbc_mssql':
+ case 'mssqlpo':
+ print "<p>Testing Foreign Keys</p>";
+ $arr = $db->MetaForeignKeys('Orders',false,true);
+ print_r($arr);
+ if (!$arr) Err("Bad MetaForeignKeys");
+ if ($db->databaseType == 'odbc_mssql') break;
+
+ case 'mssql':
+
+
+/*
+ASSUME Northwind available...
+
+CREATE PROCEDURE SalesByCategory
+ @CategoryName nvarchar(15), @OrdYear nvarchar(4) = '1998'
+AS
+IF @OrdYear != '1996' AND @OrdYear != '1997' AND @OrdYear != '1998'
+BEGIN
+ SELECT @OrdYear = '1998'
+END
+
+SELECT ProductName,
+ TotalPurchase=ROUND(SUM(CONVERT(decimal(14,2), OD.Quantity * (1-OD.Discount) * OD.UnitPrice)), 0)
+FROM [Order Details] OD, Orders O, Products P, Categories C
+WHERE OD.OrderID = O.OrderID
+ AND OD.ProductID = P.ProductID
+ AND P.CategoryID = C.CategoryID
+ AND C.CategoryName = @CategoryName
+ AND SUBSTRING(CONVERT(nvarchar(22), O.OrderDate, 111), 1, 4) = @OrdYear
+GROUP BY ProductName
+ORDER BY ProductName
+GO
+
+
+CREATE PROCEDURE ADODBTestSP
+@a nvarchar(25)
+AS
+SELECT GETDATE() AS T, @a AS A
+GO
+*/
+ print "<h4>Testing Stored Procedures for mssql</h4>";
+ $saved = $db->debug;
+ $db->debug=true;
+ $assoc = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $cmd = $db->PrepareSP('ADODBTestSP');
+ $ss = "You should see me in the output.";
+ $db->InParameter($cmd,$ss,'a');
+ $rs = $db->Execute($cmd);
+ #var_dump($rs->fields);
+ echo $rs->fields['T']." --- ".$rs->fields['A']."---<br>";
+
+ $cat = 'Dairy Products';
+ $yr = '1998';
+
+ $stmt = $db->PrepareSP('SalesByCategory');
+ $db->InParameter($stmt,$cat,'CategoryName');
+ $db->InParameter($stmt,$yr,'OrdYear');
+ $rs = $db->Execute($stmt);
+ rs2html($rs);
+
+ $cat = 'Grains/Cereals';
+ $yr = 1998;
+
+ $stmt = $db->PrepareSP('SalesByCategory');
+ $db->InParameter($stmt,$cat,'CategoryName');
+ $db->InParameter($stmt,$yr,'OrdYear');
+ $rs = $db->Execute($stmt);
+ rs2html($rs);
+
+ $ADODB_FETCH_MODE = $assoc;
+
+ /*
+ Test out params - works in PHP 4.2.3 and 4.3.3 and 4.3.8 but not 4.3.0:
+
+ CREATE PROCEDURE at_date_interval
+ @days INTEGER,
+ @start VARCHAR(20) OUT,
+ @end VARCHAR(20) OUT
+ AS
+ BEGIN
+ set @start = CONVERT(VARCHAR(20), getdate(), 101)
+ set @end =CONVERT(VARCHAR(20), dateadd(day, @days, getdate()), 101 )
+ END
+ GO
+ */
+ $db->debug=1;
+ $stmt = $db->PrepareSP('at_date_interval');
+ $days = 10;
+ $begin_date = '';
+ $end_date = '';
+ $db->InParameter($stmt,$days,'days', 4, SQLINT4);
+ $db->OutParameter($stmt,$begin_date,'start', 20, SQLVARCHAR );
+ $db->OutParameter($stmt,$end_date,'end', 20, SQLVARCHAR );
+ $db->Execute($stmt);
+ if (empty($begin_date) or empty($end_date) or $begin_date == $end_date) {
+ Err("MSSQL SP Test for OUT Failed");
+ print "begin=$begin_date end=$end_date<p>";
+ } else print "(Today +10days) = (begin=$begin_date end=$end_date)<p>";
+
+ $db->debug = $saved;
+ break;
+ case 'oci8':
+ case 'oci8po':
+
+ if (0) {
+ $t = getmicrotime();
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $arr = $db->GetArray('select * from abalone_tree');
+ $arr = $db->GetArray('select * from abalone_tree');
+ $arr = $db->GetArray('select * from abalone_tree');
+ echo "<p>t = ",getmicrotime() - $t,"</p>";
+ die();
+ }
+
+ # cleanup
+ $db->Execute("delete from photos where id=99 or id=1");
+ $db->Execute("insert into photos (id) values(1)");
+ $db->Execute("update photos set photo=null,descclob=null where id=1");
+
+ $saved = $db->debug;
+ $db->debug=true;
+
+
+
+ /*
+ CREATE TABLE PHOTOS
+ (
+ ID NUMBER(16) primary key,
+ PHOTO BLOB,
+ DESCRIPTION VARCHAR2(4000 BYTE),
+ DESCCLOB CLOB
+ );
+
+ INSERT INTO PHOTOS (ID) VALUES(1);
+ */
+ $s = '';
+ for ($i = 0; $i <= 500; $i++) {
+ $s .= '1234567890';
+ }
+
+ $sql = "INSERT INTO photos ( ID, photo) ".
+ "VALUES ( :id, empty_blob() )".
+ " RETURNING photo INTO :xx";
+
+
+ $blob_data = $s;
+ $id = 99;
+
+ $stmt = $db->PrepareSP($sql);
+ $db->InParameter($stmt, $id, 'id');
+ $blob = $db->InParameter($stmt, $s, 'xx',-1, OCI_B_BLOB);
+ $db->StartTrans();
+ $result = $db->Execute($stmt);
+ $db->CompleteTrans();
+
+ $s2= $db->GetOne("select photo from photos where id=99");
+ echo "<br>---$s2";
+ if ($s !== $s2) Err("insert blob does not match");
+
+ print "<h4>Testing Blob: size=".strlen($s)."</h4>";
+ $ok = $db->Updateblob('photos','photo',$s,'id=1');
+ if (!$ok) Err("Blob failed 1");
+ else {
+ $s2= $db->GetOne("select photo from photos where id=1");
+ if ($s !== $s2) Err("updateblob does not match");
+ }
+
+ print "<h4>Testing Clob: size=".strlen($s)."</h4>";
+ $ok = $db->UpdateClob('photos','descclob',$s,'id=1');
+ if (!$ok) Err("Clob failed 1");
+ else {
+ $s2= $db->GetOne("select descclob from photos where id=1");
+ if ($s !== $s2) Err("updateclob does not match");
+ }
+
+
+ $s = '';
+ $s2 = '';
+ print "<h4>Testing Foreign Keys</h4>";
+ $arr = $db->MetaForeignKeys('emp','scott');
+ print_r($arr);
+ if (!$arr) Err("Bad MetaForeignKeys");
+/*
+-- TEST PACKAGE
+-- "Set scan off" turns off substitution variables.
+Set scan off;
+
+CREATE OR REPLACE PACKAGE Adodb AS
+TYPE TabType IS REF CURSOR RETURN TAB%ROWTYPE;
+PROCEDURE open_tab (tabcursor IN OUT TabType,tablenames IN VARCHAR);
+PROCEDURE open_tab2 (tabcursor IN OUT TabType,tablenames IN OUT VARCHAR) ;
+PROCEDURE data_out(input IN VARCHAR, output OUT VARCHAR);
+PROCEDURE data_in(input IN VARCHAR);
+PROCEDURE myproc (p1 IN NUMBER, p2 OUT NUMBER);
+END Adodb;
+/
+
+
+CREATE OR REPLACE PACKAGE BODY Adodb AS
+PROCEDURE open_tab (tabcursor IN OUT TabType,tablenames IN VARCHAR) IS
+ BEGIN
+ OPEN tabcursor FOR SELECT * FROM TAB WHERE tname LIKE tablenames;
+ END open_tab;
+
+ PROCEDURE open_tab2 (tabcursor IN OUT TabType,tablenames IN OUT VARCHAR) IS
+ BEGIN
+ OPEN tabcursor FOR SELECT * FROM TAB WHERE tname LIKE tablenames;
+ tablenames := 'TEST';
+ END open_tab2;
+
+PROCEDURE data_out(input IN VARCHAR, output OUT VARCHAR) IS
+ BEGIN
+ output := 'Cinta Hati '||input;
+ END;
+
+PROCEDURE data_in(input IN VARCHAR) IS
+ ignore varchar(1000);
+ BEGIN
+ ignore := input;
+ END;
+
+PROCEDURE myproc (p1 IN NUMBER, p2 OUT NUMBER) AS
+BEGIN
+p2 := p1;
+END;
+END Adodb;
+/
+
+*/
+
+ print "<h4>Testing Cursor Variables</h4>";
+ $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:zz,'A%'); END;",'zz');
+
+ if ($rs && !$rs->EOF) {
+ $v = $db->GetOne("SELECT count(*) FROM tab where tname like 'A%'");
+ if ($v == $rs->RecordCount()) print "Test 1 RowCount: OK<p>";
+ else Err("Test 1 RowCount ".$rs->RecordCount().", actual = $v");
+ } else {
+ print "<b>Error in using Cursor Variables 1</b><p>";
+ }
+ if ($rs) $rs->Close();
+
+ print "<h4>Testing Stored Procedures for oci8</h4>";
+
+ $stmt = $db->PrepareSP("BEGIN adodb.data_out(:a1, :a2); END;");
+ $a1 = 'Malaysia';
+ //$a2 = ''; # a2 doesn't even need to be defined!
+ $db->InParameter($stmt,$a1,'a1');
+ $db->OutParameter($stmt,$a2,'a2');
+ $rs = $db->Execute($stmt);
+ if ($rs) {
+ if ($a2 !== 'Cinta Hati Malaysia') print "<b>Stored Procedure Error: a2 = $a2</b><p>";
+ else echo "OK: a2=$a2<p>";
+ } else {
+ print "<b>Error in using Stored Procedure IN/Out Variables</b><p>";
+ }
+
+ $tname = 'A%';
+
+ $stmt = $db->PrepareSP('select * from tab where tname like :tablename');
+ $db->Parameter($stmt,$tname,'tablename');
+ $rs = $db->Execute($stmt);
+ rs2html($rs);
+
+ $stmt = $db->PrepareSP("begin adodb.data_in(:a1); end;");
+ $db->InParameter($stmt,$a1,'a1');
+ $db->Execute($stmt);
+
+ $db->debug = $saved;
+ break;
+
+ default:
+ break;
+ }
+ $arr = array(
+ array(1,'Caroline','Miranda'),
+ array(2,'John','Lim'),
+ array(3,'Wai Hun','See')
+ );
+ //$db->debug=1;
+ print "<p>Testing Bulk Insert of 3 rows</p>";
+
+// $db->debug=1;
+// $db->Execute('select * from table where val=? AND value=?', array('val'=>'http ://www.whatever.com/test?=21', 'value'=>'blabl'));
+
+
+ $sql = "insert into ADOXYZ (id,firstname,lastname) values (".$db->Param('0').",".$db->Param('1').",".$db->Param('2').")";
+ $db->bulkBind = true;
+ $db->StartTrans();
+ $db->debug=99;
+ $db->Execute($sql,$arr);
+ $db->CompleteTrans();
+ $db->bulkBind = false;
+ $rs = $db->Execute('select * from ADOXYZ order by id');
+ if (!$rs || $rs->RecordCount() != 3) Err("Bad bulk insert");
+
+ rs2html($rs);
+
+ $db->Execute('delete from ADOXYZ');
+
+ print "<p>Inserting 50 rows</p>";
+
+ for ($i = 0; $i < 5; $i++) {
+
+ $time = $db->DBDate(time());
+ if (empty($_GET['hide'])) $db->debug = true;
+ switch($db->databaseType){
+ case 'mssqlpo':
+ case 'mssql':
+ $sqlt = "CREATE TABLE mytable (
+ row1 INT IDENTITY(1,1) NOT NULL,
+ row2 varchar(16),
+ PRIMARY KEY (row1))";
+ //$db->debug=1;
+ if (!$db->Execute("delete from mytable"))
+ $db->Execute($sqlt);
+
+ $ok = $db->Execute("insert into mytable (row2) values ('test')");
+ $ins_id=$db->Insert_ID();
+ echo "Insert ID=";var_dump($ins_id);
+ if ($ins_id == 0) Err("Bad Insert_ID()");
+ $ins_id2 = $db->GetOne("select row1 from mytable");
+ if ($ins_id != $ins_id2) Err("Bad Insert_ID() 2");
+
+ $arr = array(0=>'Caroline',1=>'Miranda');
+ $sql = "insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+0,?,?,$time)";
+ break;
+ case 'mysqli':
+ case 'mysqlt':
+ case 'mysql':
+ $sqlt = "CREATE TABLE `mytable` (
+ `row1` int(11) NOT NULL auto_increment,
+ `row2` varchar(16) NOT NULL default '',
+ PRIMARY KEY (`row1`),
+ KEY `myindex` (`row1`,`row2`)
+) ";
+ if (!$db->Execute("delete from mytable"))
+ $db->Execute($sqlt);
+
+ $ok = $db->Execute("insert into mytable (row2) values ('test')");
+ $ins_id=$db->Insert_ID();
+ echo "Insert ID=";var_dump($ins_id);
+ if ($ins_id == 0) Err("Bad Insert_ID()");
+ $ins_id2 = $db->GetOne("select row1 from mytable");
+ if ($ins_id != $ins_id2) Err("Bad Insert_ID() 2");
+
+ default:
+ $arr = array(0=>'Caroline',1=>'Miranda');
+ $sql = "insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+0,?,?,$time)";
+ break;
+
+ case 'oci8':
+ case 'oci805':
+ $arr = array('first'=>'Caroline','last'=>'Miranda');
+ $amt = rand() % 100;
+ $sql = "insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+0,:first,:last,$time)";
+ break;
+ }
+ if ($i & 1) {
+ $sql = $db->Prepare($sql);
+ }
+ $rs = $db->Execute($sql,$arr);
+
+ if ($rs === false) Err( 'Error inserting with parameters');
+ else $rs->Close();
+ $db->debug = false;
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+1,'John','Lim',$time)");
+ /*$ins_id=$db->Insert_ID();
+ echo "Insert ID=";var_dump($ins_id);*/
+ if ($db->databaseType == 'mysql') if ($ins_id == 0) Err('Bad Insert_ID');
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+2,'Mary','Lamb',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+3,'George','Washington',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+4,'Mr. Alan','Tam',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+5,'Alan',".$db->quote("Turing'ton").",$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created)values ($i*10+6,'Serena','Williams',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+7,'Yat Sun','Sun',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+8,'Wai Hun','See',$time )");
+ $db->Execute("insert into ADOXYZ (id,firstname,lastname,created) values ($i*10+9,'Steven','Oey',$time )");
+ } // for
+ if (1) {
+ $db->debug=1;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $cnt = $db->GetOne("select count(*) from ADOXYZ");
+ $rs = $db->Execute('update ADOXYZ set id=id+1');
+ if (!is_object($rs)) {
+ print_r($rs);
+ err("Update should return object");
+ }
+ if (!$rs) err("Update generated error");
+
+ $nrows = $db->Affected_Rows();
+ if ($nrows === false) print "<p><b>Affected_Rows() not supported</b></p>";
+ else if ($nrows != $cnt) print "<p><b>Affected_Rows() Error: $nrows returned (should be 50) </b></p>";
+ else print "<p>Affected_Rows() passed</p>";
+ }
+
+ if ($db->dataProvider == 'oci8') $array = array('zid'=>1,'zdate'=>date('Y-m-d',time()));
+ else $array=array(1,date('Y-m-d',time()));
+
+
+ #$array = array(1,date('Y-m-d',time()));
+ $id = $db->GetOne("select id from ADOXYZ
+ where id=".$db->Param('zid')." and created>=".$db->Param('ZDATE')."",
+ $array);
+ if ($id != 1) Err("Bad bind; id=$id");
+ else echo "<br>Bind date/integer 1 passed";
+
+ $array =array(1,$db->BindDate(time()));
+ $id = $db->GetOne("select id from ADOXYZ
+ where id=".$db->Param('0')." and created>=".$db->Param('1')."",
+ $array);
+ if ($id != 1) Err("Bad bind; id=$id");
+ else echo "<br>Bind date/integer 2 passed";
+
+ $db->debug = false;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ $rs = $db->Execute("select * from ADOXYZ where firstname = 'not known'");
+ if (!$rs || !$rs->EOF) print "<p><b>Error on empty recordset</b></p>";
+ else if ($rs->RecordCount() != 0) {
+ print "<p><b>Error on RecordCount. Should be 0. Was ".$rs->RecordCount()."</b></p>";
+ print_r($rs->fields);
+ }
+ if ($db->databaseType !== 'odbc') {
+ $rs = $db->Execute("select id,firstname,lastname,created,".$db->random." from ADOXYZ order by id");
+ if ($rs) {
+ if ($rs->RecordCount() != 50) {
+ print "<p><b>RecordCount returns ".$rs->RecordCount().", should be 50</b></p>";
+ adodb_pr($rs->GetArray());
+ $poc = $rs->PO_RecordCount('ADOXYZ');
+ if ($poc == 50) print "<p> &nbsp; &nbsp; PO_RecordCount passed</p>";
+ else print "<p><b>PO_RecordCount returns wrong value: $poc</b></p>";
+ } else print "<p>RecordCount() passed</p>";
+ if (isset($rs->fields['firstname'])) print '<p>The fields columns can be indexed by column name.</p>';
+ else {
+ Err( '<p>The fields columns <i>cannot</i> be indexed by column name.</p>');
+ print_r($rs->fields);
+ }
+ if (empty($_GET['hide'])) rs2html($rs);
+ }
+ else print "<p><b>Error in Execute of SELECT with random</b></p>";
+ }
+ $val = $db->GetOne("select count(*) from ADOXYZ");
+ if ($val == 50) print "<p>GetOne returns ok</p>";
+ else print "<p><b>Fail: GetOne returns $val</b></p>";
+
+ echo "<b>GetRow Test</b>";
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $val1 = $db->GetRow("select count(*) from ADOXYZ");
+ $val2 = $db->GetRow("select count(*) from ADOXYZ");
+ if ($val1[0] == 50 and sizeof($val1) == 1 and $val2[0] == 50 and sizeof($val2) == 1) print "<p>GetRow returns ok</p>";
+ else {
+ print_r($val);
+ print "<p><b>Fail: GetRow returns {$val2[0]}</b></p>";
+ }
+
+ print "<p>FetchObject/FetchNextObject Test</p>";
+ $rs = $db->Execute('select * from ADOXYZ');
+ if ($rs) {
+ if (empty($rs->connection)) print "<b>Connection object missing from recordset</b></br>";
+
+ while ($o = $rs->FetchNextObject()) { // calls FetchObject internally
+ if (!is_string($o->FIRSTNAME) || !is_string($o->LASTNAME)) {
+ print_r($o);
+ print "<p><b>Firstname is not string</b></p>";
+ break;
+ }
+ }
+ } else {
+ print "<p><b>Failed rs</b></p>";
+ die("<p>ADOXYZ table cannot be read - die()");
+ }
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ print "<p>FetchObject/FetchNextObject Test 2</p>";
+ #$db->debug=99;
+ $rs = $db->Execute('select * from ADOXYZ');
+ if (empty($rs->connection)) print "<b>Connection object missing from recordset</b></br>";
+ print_r($rs->fields);
+ while ($o = $rs->FetchNextObject()) { // calls FetchObject internally
+ if (!is_string($o->FIRSTNAME) || !is_string($o->LASTNAME)) {
+ print_r($o);
+ print "<p><b>Firstname is not string</b></p>";
+ break;
+ }
+ }
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ $savefetch = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+ print "<p>CacheSelectLimit Test...</p>";
+ $rs = $db->CacheSelectLimit('select id, firstname from ADOXYZ order by id',2);
+
+ if (ADODB_ASSOC_CASE == 2 || $db->dataProvider == 'oci8') {
+ $id = 'ID';
+ $fname = 'FIRSTNAME';
+ }else {
+ $id = 'id';
+ $fname = 'firstname';
+ }
+
+
+ if ($rs && !$rs->EOF) {
+ if (isset($rs->fields[0])) {
+ Err("ASSOC has numeric fields");
+ print_r($rs->fields);
+ }
+ if ($rs->fields[$id] != 1) {Err("Error"); print_r($rs->fields);};
+ if (trim($rs->fields[$fname]) != 'Caroline') {print Err("Error 2"); print_r($rs->fields);};
+
+ $rs->MoveNext();
+ if ($rs->fields[$id] != 2) {Err("Error 3"); print_r($rs->fields);};
+ $rs->MoveNext();
+ if (!$rs->EOF) {
+ Err("Error EOF");
+ print_r($rs);
+ }
+ }
+
+ print "<p>FETCH_MODE = ASSOC: Should get 1, Caroline ASSOC_CASE=".ADODB_ASSOC_CASE."</p>";
+ $rs = $db->SelectLimit('select id,firstname from ADOXYZ order by id',2);
+ if ($rs && !$rs->EOF) {
+
+ if ($rs->fields[$id] != 1) {Err("Error 1"); print_r($rs->fields);};
+ if (trim($rs->fields[$fname]) != 'Caroline') {Err("Error 2"); print_r($rs->fields);};
+ $rs->MoveNext();
+ if ($rs->fields[$id] != 2) {Err("Error 3"); print_r($rs->fields);};
+ $rs->MoveNext();
+ if (!$rs->EOF) Err("Error EOF");
+ else if (is_array($rs->fields) || $rs->fields) {
+ Err("Error: ## fields should be set to false on EOF");
+ print_r($rs->fields);
+ }
+ }
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ print "<p>FETCH_MODE = NUM: Should get 1, Caroline</p>";
+ $rs = $db->SelectLimit('select id,firstname from ADOXYZ order by id',1);
+ if ($rs && !$rs->EOF) {
+ if (isset($rs->fields[$id])) Err("FETCH_NUM has ASSOC fields");
+ if ($rs->fields[0] != 1) {Err("Error 1"); print_r($rs->fields);};
+ if (trim($rs->fields[1]) != 'Caroline') {Err("Error 2");print_r($rs->fields);};
+ $rs->MoveNext();
+ if (!$rs->EOF) Err("Error EOF");
+
+ }
+ $ADODB_FETCH_MODE = $savefetch;
+
+ $db->debug = false;
+ print "<p>GetRowAssoc Upper: Should get 1, Caroline</p>";
+ $rs = $db->SelectLimit('select id,firstname from ADOXYZ order by id',1);
+ if ($rs && !$rs->EOF) {
+ $arr = $rs->GetRowAssoc(ADODB_ASSOC_CASE_UPPER);
+
+ if ($arr[strtoupper($id)] != 1) {Err("Error 1");print_r($arr);};
+ if (trim($arr[strtoupper($fname)]) != 'Caroline') {Err("Error 2"); print_r($arr);};
+ $rs->MoveNext();
+ if (!$rs->EOF) Err("Error EOF");
+
+ }
+ print "<p>GetRowAssoc Lower: Should get 1, Caroline</p>";
+ $rs = $db->SelectLimit('select id,firstname from ADOXYZ order by id',1);
+ if ($rs && !$rs->EOF) {
+ $arr = $rs->GetRowAssoc(ADODB_ASSOC_CASE_LOWER);
+ if ($arr['id'] != 1) {Err("Error 1"); print_r($arr);};
+ if (trim($arr['firstname']) != 'Caroline') {Err("Error 2"); print_r($arr);};
+
+ }
+
+ print "<p>GetCol Test</p>";
+ $col = $db->GetCol('select distinct firstname from ADOXYZ order by 1');
+ if (!is_array($col)) Err("Col size is wrong");
+ if (trim($col[0]) != 'Alan' or trim($col[9]) != 'Yat Sun') Err("Col elements wrong");
+
+
+ $col = $db->CacheGetCol('select distinct firstname from ADOXYZ order by 1');
+ if (!is_array($col)) Err("Col size is wrong");
+ if (trim($col[0]) != 'Alan' or trim($col[9]) != 'Yat Sun') Err("Col elements wrong");
+
+ $db->debug = true;
+
+
+ echo "<p>Date Update Test</p>";
+ $zdate = date('Y-m-d',time()+3600*24);
+ $zdate = $db->DBDate($zdate);
+ $db->Execute("update ADOXYZ set created=$zdate where id=1");
+ $row = $db->GetRow("select created,firstname from ADOXYZ where id=1");
+ print_r($row); echo "<br>";
+
+
+
+ print "<p>SelectLimit Distinct Test 1: Should see Caroline, John and Mary</p>";
+ $rs = $db->SelectLimit('select distinct * from ADOXYZ order by id',3);
+
+
+ if ($rs && !$rs->EOF) {
+ if (trim($rs->fields[1]) != 'Caroline') Err("Error 1 (exp Caroline), ".$rs->fields[1]);
+ $rs->MoveNext();
+
+ if (trim($rs->fields[1]) != 'John') Err("Error 2 (exp John), ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (trim($rs->fields[1]) != 'Mary') Err("Error 3 (exp Mary),".$rs->fields[1]);
+ $rs->MoveNext();
+ if (! $rs->EOF) Err("Error EOF");
+ //rs2html($rs);
+ } else Err("Failed SelectLimit Test 1");
+
+ print "<p>SelectLimit Test 2: Should see Mary, George and Mr. Alan</p>";
+ $rs = $db->SelectLimit('select * from ADOXYZ order by id',3,2);
+ if ($rs && !$rs->EOF) {
+ if (trim($rs->fields[1]) != 'Mary') Err("Error 1 - No Mary, instead: ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (trim($rs->fields[1]) != 'George')Err("Error 2 - No George, instead: ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (trim($rs->fields[1]) != 'Mr. Alan') Err("Error 3 - No Mr. Alan, instead: ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (! $rs->EOF) Err("Error EOF");
+ // rs2html($rs);
+ }
+ else Err("Failed SelectLimit Test 2 ". ($rs ? 'EOF':'no RS'));
+
+ print "<p>SelectLimit Test 3: Should see Wai Hun and Steven</p>";
+ $db->debug=1;
+ global $A; $A=1;
+ $rs = $db->SelectLimit('select * from ADOXYZ order by id',-1,48);
+ $A=0;
+ if ($rs && !$rs->EOF) {
+ if (empty($rs->connection)) print "<b>Connection object missing from recordset</b></br>";
+ if (trim($rs->fields[1]) != 'Wai Hun') Err("Error 1 ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (trim($rs->fields[1]) != 'Steven') Err("Error 2 ".$rs->fields[1]);
+ $rs->MoveNext();
+ if (! $rs->EOF) {
+ Err("Error EOF");
+ }
+ //rs2html($rs);
+ }
+ else Err("Failed SelectLimit Test 3");
+ $db->debug = false;
+
+
+ $rs = $db->Execute("select * from ADOXYZ order by id");
+ print "<p>Testing Move()</p>";
+ if (!$rs)Err( "Failed Move SELECT");
+ else {
+ if (!$rs->Move(2)) {
+ if (!$rs->canSeek) print "<p>$db->databaseType: <b>Move(), MoveFirst() nor MoveLast() not supported.</b></p>";
+ else print '<p><b>RecordSet->canSeek property should be set to false</b></p>';
+ } else {
+ $rs->MoveFirst();
+ if (trim($rs->Fields("firstname")) != 'Caroline') {
+ print "<p><b>$db->databaseType: MoveFirst failed -- probably cannot scroll backwards</b></p>";
+ }
+ else print "MoveFirst() OK<BR>";
+
+ // Move(3) tests error handling -- MoveFirst should not move cursor
+ $rs->Move(3);
+ if (trim($rs->Fields("firstname")) != 'George') {
+ print '<p>'.$rs->Fields("id")."<b>$db->databaseType: Move(3) failed</b></p>";
+ } else print "Move(3) OK<BR>";
+
+ $rs->Move(7);
+ if (trim($rs->Fields("firstname")) != 'Yat Sun') {
+ print '<p>'.$rs->Fields("id")."<b>$db->databaseType: Move(7) failed</b></p>";
+ print_r($rs);
+ } else print "Move(7) OK<BR>";
+ if ($rs->EOF) Err("Move(7) is EOF already");
+ $rs->MoveLast();
+ if (trim($rs->Fields("firstname")) != 'Steven'){
+ print '<p>'.$rs->Fields("id")."<b>$db->databaseType: MoveLast() failed</b></p>";
+ print_r($rs);
+ }else print "MoveLast() OK<BR>";
+ $rs->MoveNext();
+ if (!$rs->EOF) err("Bad MoveNext");
+ if ($rs->canSeek) {
+ $rs->Move(3);
+ if (trim($rs->Fields("firstname")) != 'George') {
+ print '<p>'.$rs->Fields("id")."<b>$db->databaseType: Move(3) after MoveLast failed</b></p>";
+
+ } else print "Move(3) after MoveLast() OK<BR>";
+ }
+
+ print "<p>Empty Move Test";
+ $rs = $db->Execute("select * from ADOXYZ where id > 0 and id < 0");
+ $rs->MoveFirst();
+ if (!$rs->EOF || $rs->fields) Err("Error in empty move first");
+ }
+ }
+
+ $rs = $db->Execute('select * from ADOXYZ where id = 2');
+ if ($rs->EOF || !is_array($rs->fields)) Err("Error in select");
+ $rs->MoveNext();
+ if (!$rs->EOF) Err("Error in EOF (xx) ");
+ // $db->debug=true;
+ print "<p>Testing ADODB_FETCH_ASSOC and concat: concat firstname and lastname</p>";
+
+ $save = $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ if ($db->dataProvider == 'postgres') {
+ $sql = "select ".$db->Concat('cast(firstname as varchar)',$db->qstr(' '),'lastname')." as fullname,id,".$db->sysTimeStamp." as d from ADOXYZ";
+ $rs = $db->Execute($sql);
+ } else {
+ $sql = "select distinct ".$db->Concat('firstname',$db->qstr(' '),'lastname')." as fullname,id,".$db->sysTimeStamp." as d from ADOXYZ";
+ $rs = $db->Execute($sql);
+ }
+ if ($rs) {
+ if (empty($_GET['hide'])) rs2html($rs);
+ } else {
+ Err( "Failed Concat:".$sql);
+ }
+ $ADODB_FETCH_MODE = $save;
+ print "<hr />Testing GetArray() ";
+ //$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+ $rs = $db->Execute("select * from ADOXYZ order by id");
+ if ($rs) {
+ $arr = $rs->GetArray(10);
+ if (sizeof($arr) != 10 || trim($arr[1][1]) != 'John' || trim($arr[1][2]) != 'Lim') print $arr[1][1].' '.$arr[1][2]."<b> &nbsp; ERROR</b><br>";
+ else print " OK<BR>";
+ }
+
+ $arr = $db->GetArray("select x from ADOXYZ");
+ $e = $db->ErrorMsg(); $e2 = $db->ErrorNo();
+ echo "Testing error handling, should see illegal column 'x' error=<i>$e ($e2) </i><br>";
+ if (!$e || !$e2) Err("Error handling did not work");
+ print "Testing FetchNextObject for 1 object ";
+ $rs = $db->Execute("select distinct lastname,firstname from ADOXYZ where firstname='Caroline'");
+ $fcnt = 0;
+ if ($rs)
+ while ($o = $rs->FetchNextObject()) {
+ $fcnt += 1;
+ }
+ if ($fcnt == 1) print " OK<BR>";
+ else print "<b>FAILED</b><BR>";
+
+ $stmt = $db->Prepare("select * from ADOXYZ where id < 3");
+ $rs = $db->Execute($stmt);
+ if (!$rs) Err("Prepare failed");
+ else {
+ $arr = $rs->GetArray();
+ if (!$arr) Err("Prepare failed 2");
+ if (sizeof($arr) != 2) Err("Prepare failed 3");
+ }
+ print "Testing GetAssoc() ";
+
+
+ if ($db->dataProvider == 'mysql') {
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $arr = $db->GetAssoc("SELECT 'adodb', '0'");
+ var_dump($arr);
+ die();
+ }
+
+ $savecrecs = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = false;
+ //$arr = $db->GetArray("select lastname,firstname from ADOXYZ");
+ //print_r($arr);
+ print "<hr />";
+ $rs = $db->Execute("select distinct lastname,firstname,created from ADOXYZ");
+
+ if ($rs) {
+ $arr = $rs->GetAssoc();
+ //print_r($arr);
+ if (empty($arr['See']) || trim(reset($arr['See'])) != 'Wai Hun') print $arr['See']." &nbsp; <b>ERROR</b><br>";
+ else print " OK 1";
+ }
+
+ $arr = $db->GetAssoc("select distinct lastname,firstname from ADOXYZ");
+ if ($arr) {
+ //print_r($arr);
+ if (empty($arr['See']) || trim($arr['See']) != 'Wai Hun') print $arr['See']." &nbsp; <b>ERROR</b><br>";
+ else print " OK 2<BR>";
+ }
+ // Comment this out to test countrecs = false
+ $ADODB_COUNTRECS = $savecrecs;
+ $db->debug=1;
+ $query = $db->Prepare("select count(*) from ADOXYZ");
+ $rs = $db->CacheExecute(10,$query);
+ if (reset($rs->fields) != 50) echo Err("$cnt wrong for Prepare/CacheGetOne");
+
+ for ($loop=0; $loop < 1; $loop++) {
+ print "Testing GetMenu() and CacheExecute<BR>";
+ $db->debug = true;
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+
+
+
+
+ if ($rs) print 'With blanks, Steven selected:'. $rs->GetMenu('menu','Steven').'<BR>';
+ else print " Fail<BR>";
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+
+ if ($rs) print ' No blanks, Steven selected: '. $rs->GetMenu('menu','Steven',false).'<BR>';
+ else print " Fail<BR>";
+
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+
+ if ($rs) print ' 1st line set to **** , Steven selected: '. $rs->GetMenu('menu','Steven','1st:****').'<BR>';
+ else print " Fail<BR>";
+
+
+
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+ if ($rs) print ' Multiple, Alan selected: '. $rs->GetMenu('menu','Alan',false,true).'<BR>';
+ else print " Fail<BR>";
+ print '</p><hr />';
+
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+ if ($rs) {
+ print ' Multiple, Alan and George selected: '. $rs->GetMenu('menu',array('Alan','George'),false,true);
+ if (empty($rs->connection)) print "<b>Connection object missing from recordset</b></br>";
+ } else print " Fail<BR>";
+ print '</p><hr />';
+
+ print "Testing GetMenu3()<br>";
+ $rs = $db->Execute("select ".$db->Concat('firstname',"'-'",'id').",id, lastname from ADOXYZ order by lastname,id");
+ if ($rs) print "Grouped Menu: ".$rs->GetMenu3('name');
+ else Err('Grouped Menu GetMenu3()');
+ print "<hr />";
+
+ print "Testing GetMenu2() <BR>";
+ $rs = $db->CacheExecute(4,"select distinct firstname,lastname from ADOXYZ");
+ if ($rs) print 'With blanks, Steven selected:'. $rs->GetMenu2('menu',('Oey')).'<BR>';
+ else print " Fail<BR>";
+ $rs = $db->CacheExecute(6,"select distinct firstname,lastname from ADOXYZ");
+ if ($rs) print ' No blanks, Steven selected: '. $rs->GetMenu2('menu',('Oey'),false).'<BR>';
+ else print " Fail<BR>";
+ }
+ echo "<h3>CacheExecute</h3>";
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $rs = $db->CacheExecute(6,"select distinct firstname,lastname from ADOXYZ");
+ print_r($rs->fields); echo $rs->fetchMode;echo "<br>";
+ echo $rs->Fields($fname);
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rs = $db->CacheExecute(6,"select distinct firstname,lastname from ADOXYZ");
+ print_r($rs->fields);echo "<br>";
+ echo $rs->Fields($fname);
+ $db->debug = false;
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ // phplens
+
+ $sql = 'select * from ADOXYZ where 0=1';
+ echo "<p>**Testing '$sql' (phplens compat 1)</p>";
+ $rs = $db->Execute($sql);
+ if (!$rs) err( "<b>No recordset returned for '$sql'</b>");
+ if (!$rs->FieldCount()) err( "<b>No fields returned for $sql</b>");
+ if (!$rs->FetchField(1)) err( "<b>FetchField failed for $sql</b>");
+
+ $sql = 'select * from ADOXYZ order by 1';
+ echo "<p>**Testing '$sql' (phplens compat 2)</p>";
+ $rs = $db->Execute($sql);
+ if (!$rs) err( "<b>No recordset returned for '$sql'<br>".$db->ErrorMsg()."</b>");
+
+
+ $sql = 'select * from ADOXYZ order by 1,1';
+ echo "<p>**Testing '$sql' (phplens compat 3)</p>";
+ $rs = $db->Execute($sql);
+ if (!$rs) err( "<b>No recordset returned for '$sql'<br>".$db->ErrorMsg()."</b>");
+
+
+ // Move
+ $rs1 = $db->Execute("select id from ADOXYZ where id <= 2 order by 1");
+ $rs2 = $db->Execute("select id from ADOXYZ where id = 3 or id = 4 order by 1");
+
+ if ($rs1) $rs1->MoveLast();
+ if ($rs2) $rs2->MoveLast();
+
+ if (empty($rs1) || empty($rs2) || $rs1->fields[0] != 2 || $rs2->fields[0] != 4) {
+ $a = $rs1->fields[0];
+ $b = $rs2->fields[0];
+ print "<p><b>Error in multiple recordset test rs1=$a rs2=$b (should be rs1=2 rs2=4)</b></p>";
+ } else
+ print "<p>Testing multiple recordsets OK</p>";
+
+
+ echo "<p> GenID test: ";
+ for ($i=1; $i <= 10; $i++)
+ echo "($i: ",$val = $db->GenID($db->databaseType.'abcseq7' ,5), ") ";
+ if ($val == 0) Err("GenID not supported");
+
+ if ($val) {
+ $db->DropSequence('abc_seq2');
+ $db->CreateSequence('abc_seq2');
+ $val = $db->GenID('abc_seq2');
+ $db->DropSequence('abc_seq2');
+ $db->CreateSequence('abc_seq2');
+ $val = $db->GenID('abc_seq2');
+ if ($val != 1) Err("Drop and Create Sequence not supported ($val)");
+ }
+ echo "<p>";
+
+ if (substr($db->dataProvider,0,3) != 'notused') { // used to crash ado
+ $sql = "select firstnames from ADOXYZ";
+ print "<p>Testing execution of illegal statement: <i>$sql</i></p>";
+ if ($db->Execute($sql) === false) {
+ print "<p>This returns the following ErrorMsg(): <i>".$db->ErrorMsg()."</i> and ErrorNo(): ".$db->ErrorNo().'</p>';
+ } else
+ print "<p><b>Error in error handling -- Execute() should return false</b></p>";
+ } else
+ print "<p><b>ADO skipped error handling of bad select statement</b></p>";
+
+ print "<p>ASSOC TEST 2<br>";
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rs = $db->query('select * from ADOXYZ order by id');
+ if ($ee = $db->ErrorMsg()) {
+ Err("Error message=$ee");
+ }
+ if ($ee = $db->ErrorNo()) {
+ Err("Error No = $ee");
+ }
+ print_r($rs->fields);
+ for($i=0;$i<$rs->FieldCount();$i++)
+ {
+ $fld=$rs->FetchField($i);
+ print "<br> Field name is ".$fld->name;
+ print " ".$rs->Fields($fld->name);
+ }
+
+
+ print "<p>BOTH TEST 2<br>";
+ if ($db->dataProvider == 'ado') {
+ print "<b>ADODB_FETCH_BOTH not supported</b> for dataProvider=".$db->dataProvider."<br>";
+ } else {
+ $ADODB_FETCH_MODE = ADODB_FETCH_BOTH;
+ $rs = $db->query('select * from ADOXYZ order by id');
+ for($i=0;$i<$rs->FieldCount();$i++)
+ {
+ $fld=$rs->FetchField($i);
+ print "<br> Field name is ".$fld->name;
+ print " ".$rs->Fields($fld->name);
+ }
+ }
+
+ print "<p>NUM TEST 2<br>";
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $rs = $db->query('select * from ADOXYZ order by id');
+ for($i=0;$i<$rs->FieldCount();$i++)
+ {
+ $fld=$rs->FetchField($i);
+ print "<br> Field name is ".$fld->name;
+ print " ".$rs->Fields($fld->name);
+ }
+
+ print "<p>ASSOC Test of SelectLimit<br>";
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rs = $db->selectlimit('select * from ADOXYZ order by id',3,4);
+ $cnt = 0;
+ while ($rs && !$rs->EOF) {
+ $cnt += 1;
+ if (!isset($rs->fields['firstname'])) {
+ print "<br><b>ASSOC returned numeric field</b></p>";
+ break;
+ }
+ $rs->MoveNext();
+ }
+ if ($cnt != 3) print "<br><b>Count should be 3, instead it was $cnt</b></p>";
+
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ if ($db->sysDate) {
+ $saved = $db->debug;
+ $db->debug = 1;
+ $rs = $db->Execute("select {$db->sysDate} from ADOXYZ where id=1");
+ if (ADORecordSet::UnixDate(date('Y-m-d')) != $rs->UnixDate($rs->fields[0])) {
+ print "<p><b>Invalid date {$rs->fields[0]}</b></p>";
+ } else
+ print "<p>Passed \$sysDate test ({$rs->fields[0]})</p>";
+
+ print_r($rs->FetchField(0));
+ print time();
+ $db->debug=$saved;
+ } else {
+ print "<p><b>\$db->sysDate not defined</b></p>";
+ }
+
+ print "<p>Test CSV</p>";
+ include_once('../toexport.inc.php');
+ //$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $rs = $db->SelectLimit('select id,firstname,lastname,created,\'He, he\' he,\'"\' q from ADOXYZ',10);
+
+ print "<pre>";
+ print rs2csv($rs);
+ print "</pre>";
+
+ $rs = $db->SelectLimit('select id,firstname,lastname,created,\'The "young man", he said\' from ADOXYZ',10);
+
+ if (PHP_VERSION < 5) {
+ print "<pre>";
+ rs2tabout($rs);
+ print "</pre>";
+ }
+ #print " CacheFlush ";
+ #$db->CacheFlush();
+
+ $date = $db->SQLDate('d-m-M-Y-\QQ h:i:s A');
+ $sql = "SELECT $date from ADOXYZ";
+ print "<p>Test SQLDate: ".htmlspecialchars($sql)."</p>";
+ $rs = $db->SelectLimit($sql,1);
+ $d = date('d-m-M-Y-').'Q'.(ceil(date('m')/3.0)).date(' h:i:s A');
+ if (!$rs) Err("SQLDate query returned no recordset");
+ else if ($d != $rs->fields[0]) Err("SQLDate 1 failed expected: <br>act:$d <br>sql:".$rs->fields[0]);
+
+ $dbdate = $db->DBDate("1974-02-25");
+ if (substr($db->dataProvider, 0, 8) == 'postgres') {
+ $dbdate .= "::TIMESTAMP";
+ }
+
+ $date = $db->SQLDate('d-m-M-Y-\QQ h:i:s A', $dbdate);
+ $sql = "SELECT $date from ADOXYZ";
+ print "<p>Test SQLDate: ".htmlspecialchars($sql)."</p>";
+ $db->debug=1;
+ $rs = $db->SelectLimit($sql,1);
+ $ts = ADOConnection::UnixDate('1974-02-25');
+ $d = date('d-m-M-Y-',$ts).'Q'.(ceil(date('m',$ts)/3.0)).date(' h:i:s A',$ts);
+ if (!$rs) {
+ Err("SQLDate query returned no recordset");
+ echo $db->ErrorMsg(),'<br>';
+ } else if ($d != reset($rs->fields)) {
+ Err("SQLDate 2 failed expected: <br>act:$d <br>sql:".$rs->fields[0].' <br>'.$db->ErrorMsg());
+ }
+
+
+ print "<p>Test Filter</p>";
+ $db->debug = 1;
+
+ $rs = $db->SelectLimit('select * from ADOXYZ where id < 3 order by id');
+
+ $rs = RSFilter($rs,'do_strtolower');
+ if (trim($rs->fields[1]) != 'caroline' && trim($rs->fields[2]) != 'miranda') {
+ err('**** RSFilter failed');
+ print_r($rs->fields);
+ }
+
+ rs2html($rs);
+
+ $db->debug=1;
+
+
+ print "<p>Test Replace</p>";
+
+ $ret = $db->Replace('ADOXYZ',
+ array('id'=>1,'firstname'=>'Caroline','lastname'=>'Miranda'),
+ array('id'),
+ $autoq = true);
+ if (!$ret) echo "<p>Error in replacing existing record</p>";
+ else {
+ $saved = $db->debug;
+ $db->debug = 0;
+ $savec = $ADODB_COUNTRECS;
+ $ADODB_COUNTRECS = true;
+ $rs = $db->Execute('select * FROM ADOXYZ where id=1');
+ $db->debug = $saved;
+ if ($rs->RecordCount() != 1) {
+ $cnt = $rs->RecordCount();
+ rs2html($rs);
+ print "<b>Error - Replace failed, count=$cnt</b><p>";
+ }
+ $ADODB_COUNTRECS = $savec;
+ }
+ $ret = $db->Replace('ADOXYZ',
+ array('id'=>1000,'firstname'=>'Harun','lastname'=>'Al-Rashid'),
+ array('id','firstname'),
+ $autoq = true);
+ if ($ret != 2) print "<b>Replace failed: </b>";
+ print "test A return value=$ret (2 expected) <p>";
+
+ $ret = $db->Replace('ADOXYZ',
+ array('id'=>1000,'firstname'=>'Sherazade','lastname'=>'Al-Rashid'),
+ 'id',
+ $autoq = true);
+ if ($ret != 1)
+ if ($db->dataProvider == 'ibase' && $ret == 2);
+ else print "<b>Replace failed: </b>";
+ print "test B return value=$ret (1 or if ibase then 2 expected) <p>";
+
+ print "<h3>rs2rs Test</h3>";
+
+ $rs = $db->Execute('select * from ADOXYZ where id>= 1 order by id');
+ $rs = $db->_rs2rs($rs);
+ $rs->valueX = 'X';
+ $rs->MoveNext();
+ $rs = $db->_rs2rs($rs);
+ if (!isset($rs->valueX)) err("rs2rs does not preserve array recordsets");
+ if (reset($rs->fields) != 1) err("rs2rs does not move to first row: id=".reset($rs->fields));
+
+ /////////////////////////////////////////////////////////////
+ include_once('../pivottable.inc.php');
+ print "<h3>Pivot Test</h3>";
+ $db->debug=true;
+ $sql = PivotTableSQL(
+ $db, # adodb connection
+ 'ADOXYZ', # tables
+ 'firstname', # row fields
+ 'lastname', # column fields
+ false, # join
+ 'ID', # sum
+ 'Sum ', # label for sum
+ 'sum', # aggregate function
+ true
+ );
+ $rs = $db->Execute($sql);
+ if ($rs) rs2html($rs);
+ else Err("Pivot sql error");
+
+ $pear = false; //true;
+ $db->debug=false;
+
+ if ($pear) {
+ // PEAR TESTS BELOW
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
+ include_once "PEAR.php";
+ $rs = $db->query('select * from ADOXYZ where id>0 and id<10 order by id');
+
+ $i = 0;
+ if ($rs && !$rs->EOF) {
+ while ($arr = $rs->fetchRow()) {
+ $i++;
+ //print "$i ";
+ if ($arr[0] != $i) {
+ print_r($arr);
+ print "<p><b>PEAR DB emulation error 1.</b></p>";
+ $pear = false;
+ break;
+ }
+ }
+ $rs->Close();
+ }
+
+
+ if ($i != $db->GetOne('select count(*) from ADOXYZ where id>0 and id<10')) {
+ print "<p><b>PEAR DB emulation error 1.1 EOF ($i)</b></p>";
+ $pear = false;
+ }
+
+ $rs = $db->limitQuery('select * from ADOXYZ where id>0 order by id',$i=3,$top=3);
+ $i2 = $i;
+ if ($rs && !$rs->EOF) {
+
+ while (!is_object($rs->fetchInto($arr))) {
+ $i2++;
+
+ // print_r($arr);
+ // print "$i ";print_r($arr);
+ if ($arr[0] != $i2) {
+ print "<p><b>PEAR DB emulation error 2.</b></p>";
+ $pear = false;
+ break;
+ }
+ }
+ $rs->Close();
+ }
+ if ($i2 != $i+$top) {
+ print "<p><b>PEAR DB emulation error 2.1 EOF (correct=$i+$top, actual=$i2)</b></p>";
+ $pear = false;
+ }
+ }
+ if ($pear) print "<p>PEAR DB emulation passed.</p>";
+ flush();
+
+
+ $rs = $db->SelectLimit("select ".$db->sysDate." from ADOXYZ",1);
+ $date = $rs->fields[0];
+ if (!$date) Err("Bad sysDate");
+ else {
+ $ds = $db->UserDate($date,"d m Y");
+ if ($ds != date("d m Y")) Err("Bad UserDate: ".$ds.' expected='.date("d m Y"));
+ else echo "Passed UserDate: $ds<p>";
+ }
+ $db->debug=1;
+ if ($db->dataProvider == 'oci8')
+ $rs = $db->SelectLimit("select to_char(".$db->sysTimeStamp.",'YYYY-MM-DD HH24:MI:SS') from ADOXYZ",1);
+ else
+ $rs = $db->SelectLimit("select ".$db->sysTimeStamp." from ADOXYZ",1);
+ $date = $rs->fields[0];
+ if (!$date) Err("Bad sysTimeStamp");
+ else {
+ $ds = $db->UserTimeStamp($date,"H \\h\\r\\s-d m Y");
+ if ($ds != date("H \\h\\r\\s-d m Y")) Err("Bad UserTimeStamp: ".$ds.", correct is ".date("H \\h\\r\\s-d m Y"));
+ else echo "Passed UserTimeStamp: $ds<p>";
+
+ $date = 100;
+ $ds = $db->UserTimeStamp($date,"H \\h\\r\\s-d m Y");
+ $ds2 = date("H \\h\\r\\s-d m Y",$date);
+ if ($ds != $ds2) Err("Bad UserTimeStamp 2: $ds: $ds2");
+ else echo "Passed UserTimeStamp 2: $ds<p>";
+ }
+ flush();
+
+ if ($db->hasTransactions) {
+ $db->debug=1;
+ echo "<p>Testing StartTrans CompleteTrans</p>";
+ $db->raiseErrorFn = false;
+
+ $db->SetTransactionMode('SERIALIZABLE');
+ $db->StartTrans();
+ $rs = $db->Execute('select * from notable');
+ $db->StartTrans();
+ $db->BeginTrans();
+ $db->Execute("update ADOXYZ set firstname='Carolx' where id=1");
+ $db->CommitTrans();
+ $db->CompleteTrans();
+ $rez = $db->CompleteTrans();
+ $db->SetTransactionMode('');
+ $db->debug=0;
+ if ($rez !== false) {
+ if (is_null($rez)) Err("Error: _transOK not modified");
+ else Err("Error: CompleteTrans (1) should have failed");
+ } else {
+ $name = $db->GetOne("Select firstname from ADOXYZ where id=1");
+ if ($name == "Carolx") Err("Error: CompleteTrans (2) should have failed");
+ else echo "<p> -- Passed StartTrans test1 - rolling back</p>";
+ }
+
+ $db->StartTrans();
+ $db->BeginTrans();
+ $db->Execute("update ADOXYZ set firstname='Carolx' where id=1");
+ $db->RollbackTrans();
+ $rez = $db->CompleteTrans();
+ if ($rez !== true) Err("Error: CompleteTrans (1) should have succeeded");
+ else {
+ $name = $db->GetOne("Select firstname from ADOXYZ where id=1");
+ if (trim($name) != "Carolx") Err("Error: CompleteTrans (2) should have succeeded, returned name=$name");
+ else echo "<p> -- Passed StartTrans test2 - commiting</p>";
+ }
+ }
+ flush();
+ $saved = $db->debug;
+ $db->debug=1;
+ $cnt = _adodb_getcount($db, 'select * from ADOXYZ where firstname in (select firstname from ADOXYZ)');
+ echo "<b>Count=</b> $cnt";
+ $db->debug=$saved;
+
+ global $TESTERRS;
+ $debugerr = true;
+
+ global $ADODB_LANG;$ADODB_LANG = 'fr';
+ $db->debug = false;
+ $TESTERRS = 0;
+ $db->raiseErrorFn = 'adodb_test_err';
+ global $ERRNO; // from adodb_test_err
+ $db->Execute('select * from nowhere');
+ $metae = $db->MetaError($ERRNO);
+ if ($metae !== DB_ERROR_NOSUCHTABLE) print "<p><b>MetaError=".$metae." wrong</b>, should be ".DB_ERROR_NOSUCHTABLE."</p>";
+ else print "<p>MetaError ok (".DB_ERROR_NOSUCHTABLE."): ".$db->MetaErrorMsg($metae)."</p>";
+ if ($TESTERRS != 1) print "<b>raiseErrorFn select nowhere failed</b><br>";
+ $rs = $db->Execute('select * from ADOXYZ');
+ if ($debugerr) print " Move";
+ $rs->Move(100);
+ $rs->_queryID = false;
+ if ($debugerr) print " MoveNext";
+ $rs->MoveNext();
+ if ($debugerr) print " $rs=false";
+ $rs = false;
+
+ flush();
+
+ print "<p>SetFetchMode() tests</p>";
+ $db->SetFetchMode(ADODB_FETCH_ASSOC);
+ $rs = $db->SelectLimit('select firstname from ADOXYZ',1);
+ if (!isset($rs->fields['firstname'])) Err("BAD FETCH ASSOC");
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+ $rs = $db->SelectLimit('select firstname from ADOXYZ',1);
+ //var_dump($rs->fields);
+ if (!isset($rs->fields['firstname'])) Err("BAD FETCH ASSOC");
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $db->SetFetchMode(ADODB_FETCH_NUM);
+ $rs = $db->SelectLimit('select firstname from ADOXYZ',1);
+ if (!isset($rs->fields[0])) Err("BAD FETCH NUM");
+
+ flush();
+
+ print "<p>Test MetaTables again with SetFetchMode()</p>";
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ $db->SetFetchMode(ADODB_FETCH_ASSOC);
+ print_r($db->MetaTables());
+ print "<p>";
+
+ ////////////////////////////////////////////////////////////////////
+
+ print "<p>Testing Bad Connection</p>";
+ flush();
+
+ if (true || PHP_VERSION < 5) {
+ if ($db->dataProvider == 'odbtp') $db->databaseType = 'odbtp';
+ $conn = NewADOConnection($db->databaseType);
+ $conn->raiseErrorFn = 'adodb_test_err';
+ if (1) $conn->PConnect('abc','baduser','badpassword');
+ if ($TESTERRS == 2) print "raiseErrorFn tests passed<br>";
+ else print "<b>raiseErrorFn tests failed ($TESTERRS)</b><br>";
+
+ flush();
+ }
+ ////////////////////////////////////////////////////////////////////
+
+ global $nocountrecs;
+
+ if (isset($nocountrecs) && $ADODB_COUNTRECS) err("Error: \$ADODB_COUNTRECS is set");
+ if (empty($nocountrecs) && $ADODB_COUNTRECS==false) err("Error: \$ADODB_COUNTRECS is not set");
+
+ flush();
+?>
+ </p>
+ <table width=100% ><tr><td bgcolor=beige>&nbsp;</td></tr></table>
+ </p></form>
+<?php
+
+ if ($rs1) $rs1->Close();
+ if ($rs2) $rs2->Close();
+ if ($rs) $rs->Close();
+ $db->Close();
+
+ if ($db->transCnt != 0) Err("Error in transCnt=$db->transCnt (should be 0)");
+
+
+ printf("<p>Total queries=%d; total cached=%d</p>",$EXECS+$CACHED, $CACHED);
+ flush();
+}
+
+function adodb_test_err($dbms, $fn, $errno, $errmsg, $p1=false, $p2=false)
+{
+global $TESTERRS,$ERRNO;
+
+ $ERRNO = $errno;
+ $TESTERRS += 1;
+ print "<i>** $dbms ($fn): errno=$errno &nbsp; errmsg=$errmsg ($p1,$p2)</i><br>";
+}
+
+//--------------------------------------------------------------------------------------
+
+
+@set_time_limit(240); // increase timeout
+
+include("../tohtml.inc.php");
+include("../adodb.inc.php");
+include("../rsfilter.inc.php");
+
+/* White Space Check */
+
+if (isset($_SERVER['argv'][1])) {
+ //print_r($_SERVER['argv']);
+ $_GET[$_SERVER['argv'][1]] = 1;
+}
+
+if (@$_SERVER['COMPUTERNAME'] == 'TIGRESS') {
+ CheckWS('mysqlt');
+ CheckWS('postgres');
+ CheckWS('oci8po');
+
+ CheckWS('firebird');
+ CheckWS('sybase');
+ if (!ini_get('safe_mode')) CheckWS('informix');
+
+ CheckWS('ado_mssql');
+ CheckWS('ado_access');
+ CheckWS('mssql');
+
+ CheckWS('vfp');
+ CheckWS('sqlanywhere');
+ CheckWS('db2');
+ CheckWS('access');
+ CheckWS('odbc_mssql');
+ CheckWS('firebird15');
+ //
+ CheckWS('oracle');
+ CheckWS('proxy');
+ CheckWS('fbsql');
+ print "White Space Check complete<p>";
+}
+if (sizeof($_GET) == 0) $testmysql = true;
+
+
+foreach($_GET as $k=>$v) {
+ // XSS protection (see Github issue #274) - only set variables for
+ // expected get parameters used in testdatabases.inc.php
+ if(preg_match('/^(test|no)\w+$/', $k)) {
+ $$k = $v;
+ }
+}
+
+?>
+<html>
+<title>ADODB Testing</title>
+<body bgcolor=white>
+<H1>ADODB Test</H1>
+
+This script tests the following databases: Interbase, Oracle, Visual FoxPro, Microsoft Access (ODBC and ADO), MySQL, MSSQL (ODBC, native, ADO).
+There is also support for Sybase, PostgreSQL.</p>
+For the latest version of ADODB, visit <a href=http://adodb.org//>adodb.org</a>.</p>
+
+Test <a href=test4.php>GetInsertSQL/GetUpdateSQL</a> &nbsp;
+ <a href=testsessions.php>Sessions</a> &nbsp;
+ <a href=testpaging.php>Paging</a> &nbsp;
+ <a href=test-perf.php>Perf Monitor</a><p>
+<?php
+
+
+include_once('../adodb-time.inc.php');
+if (isset($_GET['time'])) adodb_date_test();
+flush();
+
+include_once('./testdatabases.inc.php');
+
+echo "<br>vers=",ADOConnection::Version();
+
+
+
+?>
+<p><i>ADODB Database Library (c) 2000-2014 John Lim. All rights reserved. Released under BSD and LGPL, PHP <?php echo PHP_VERSION ?>.</i></p>
+</body>
+</html>
diff --git a/vendor/adodb/adodb-php/tests/test2.php b/vendor/adodb/adodb-php/tests/test2.php
new file mode 100644
index 0000000..eb3b025
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test2.php
@@ -0,0 +1,25 @@
+<?php
+
+// BASIC ADO test
+
+ include_once('../adodb.inc.php');
+
+ $db = ADONewConnection("ado_access");
+ $db->debug=1;
+ $access = 'd:\inetpub\wwwroot\php\NWIND.MDB';
+ $myDSN = 'PROVIDER=Microsoft.Jet.OLEDB.4.0;'
+ . 'DATA SOURCE=' . $access . ';';
+
+ echo "<p>PHP ",PHP_VERSION,"</p>";
+
+ $db->Connect($myDSN) || die('fail');
+
+ print_r($db->ServerInfo());
+
+ try {
+ $rs = $db->Execute("select $db->sysTimeStamp,* from adoxyz where id>02xx");
+ print_r($rs->fields);
+ } catch(exception $e) {
+ print_r($e);
+ echo "<p> Date m/d/Y =",$db->UserDate($rs->fields[4],'m/d/Y');
+ }
diff --git a/vendor/adodb/adodb-php/tests/test3.php b/vendor/adodb/adodb-php/tests/test3.php
new file mode 100644
index 0000000..7fe6739
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test3.php
@@ -0,0 +1,44 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 8.
+ */
+
+
+error_reporting(E_ALL);
+
+$path = dirname(__FILE__);
+
+include("$path/../adodb-exceptions.inc.php");
+include("$path/../adodb.inc.php");
+
+try {
+$db = NewADOConnection("oci8");
+$db->Connect('','scott','natsoft');
+$db->debug=1;
+
+$cnt = $db->GetOne("select count(*) from adoxyz");
+$rs = $db->Execute("select * from adoxyz order by id");
+
+$i = 0;
+foreach($rs as $k => $v) {
+ $i += 1;
+ echo $k; adodb_pr($v);
+ flush();
+}
+
+if ($i != $cnt) die("actual cnt is $i, cnt should be $cnt\n");
+
+
+
+$rs = $db->Execute("select bad from badder");
+
+} catch (exception $e) {
+ adodb_pr($e);
+ $e = adodb_backtrace($e->trace);
+}
diff --git a/vendor/adodb/adodb-php/tests/test4.php b/vendor/adodb/adodb-php/tests/test4.php
new file mode 100644
index 0000000..843094b
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test4.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * Test GetUpdateSQL and GetInsertSQL.
+ */
+
+error_reporting(E_ALL);
+function testsql()
+{
+
+
+include('../adodb.inc.php');
+include('../tohtml.inc.php');
+
+global $ADODB_FORCE_TYPE;
+
+
+//==========================
+// This code tests an insert
+
+$sql = "
+SELECT *
+FROM ADOXYZ WHERE id = -1";
+// Select an empty record from the database
+
+
+#$conn = ADONewConnection("mssql"); // create a connection
+#$conn->PConnect("", "sa", "natsoft", "northwind"); // connect to MySQL, testdb
+
+$conn = ADONewConnection("mysql"); // create a connection
+$conn->PConnect("localhost", "root", "", "test"); // connect to MySQL, testdb
+
+
+#$conn = ADONewConnection('oci8po');
+#$conn->Connect('','scott','natsoft');
+
+if (PHP_VERSION >= 5) {
+ $connstr = "mysql:dbname=northwind";
+ $u = 'root';$p='';
+ $conn = ADONewConnection('pdo');
+ $conn->Connect($connstr, $u, $p);
+}
+//$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+
+$conn->debug=1;
+$conn->Execute("delete from adoxyz where lastname like 'Smi%'");
+
+$rs = $conn->Execute($sql); // Execute the query and get the empty recordset
+$record = array(); // Initialize an array to hold the record data to insert
+
+if (strpos($conn->databaseType,'mysql')===false) $record['id'] = 751;
+$record["firstname"] = 'Jann';
+$record["lastname"] = "Smitts";
+$record["created"] = time();
+
+$insertSQL = $conn->GetInsertSQL($rs, $record);
+$conn->Execute($insertSQL); // Insert the record into the database
+
+if (strpos($conn->databaseType,'mysql')===false) $record['id'] = 752;
+// Set the values for the fields in the record
+$record["firstname"] = 'anull';
+$record["lastname"] = "Smith\$@//";
+$record["created"] = time();
+
+if (isset($_GET['f'])) $ADODB_FORCE_TYPE = $_GET['f'];
+
+//$record["id"] = -1;
+
+// Pass the empty recordset and the array containing the data to insert
+// into the GetInsertSQL function. The function will process the data and return
+// a fully formatted insert sql statement.
+$insertSQL = $conn->GetInsertSQL($rs, $record);
+$conn->Execute($insertSQL); // Insert the record into the database
+
+
+
+$insertSQL2 = $conn->GetInsertSQL($table='ADOXYZ', $record);
+if ($insertSQL != $insertSQL2) echo "<p><b>Walt's new stuff failed</b>: $insertSQL2</p>";
+//==========================
+// This code tests an update
+
+$sql = "
+SELECT *
+FROM ADOXYZ WHERE lastname=".$conn->Param('var'). " ORDER BY 1";
+// Select a record to update
+
+$varr = array('var'=>$record['lastname'].'');
+$rs = $conn->Execute($sql,$varr); // Execute the query and get the existing record to update
+if (!$rs || $rs->EOF) print "<p><b>No record found!</b></p>";
+
+$record = array(); // Initialize an array to hold the record data to update
+
+
+// Set the values for the fields in the record
+$record["firstName"] = "Caroline".rand();
+//$record["lasTname"] = ""; // Update Caroline's lastname from Miranda to Smith
+$record["creAted"] = '2002-12-'.(rand()%30+1);
+$record['num'] = '';
+// Pass the single record recordset and the array containing the data to update
+// into the GetUpdateSQL function. The function will process the data and return
+// a fully formatted update sql statement.
+// If the data has not changed, no recordset is returned
+
+$updateSQL = $conn->GetUpdateSQL($rs, $record);
+$conn->Execute($updateSQL,$varr); // Update the record in the database
+if ($conn->Affected_Rows() != 1)print "<p><b>Error1 </b>: Rows Affected=".$conn->Affected_Rows().", should be 1</p>";
+
+$record["firstName"] = "Caroline".rand();
+$record["lasTname"] = "Smithy Jones"; // Update Caroline's lastname from Miranda to Smith
+$record["creAted"] = '2002-12-'.(rand()%30+1);
+$record['num'] = 331;
+$updateSQL = $conn->GetUpdateSQL($rs, $record);
+$conn->Execute($updateSQL,$varr); // Update the record in the database
+if ($conn->Affected_Rows() != 1)print "<p><b>Error 2</b>: Rows Affected=".$conn->Affected_Rows().", should be 1</p>";
+
+$rs = $conn->Execute("select * from ADOXYZ where lastname like 'Sm%'");
+//adodb_pr($rs);
+rs2html($rs);
+
+$record["firstName"] = "Carol-new-".rand();
+$record["lasTname"] = "Smithy"; // Update Caroline's lastname from Miranda to Smith
+$record["creAted"] = '2002-12-'.(rand()%30+1);
+$record['num'] = 331;
+
+$conn->AutoExecute('ADOXYZ',$record,'UPDATE', "lastname like 'Sm%'");
+$rs = $conn->Execute("select * from ADOXYZ where lastname like 'Sm%'");
+//adodb_pr($rs);
+rs2html($rs);
+}
+
+
+testsql();
diff --git a/vendor/adodb/adodb-php/tests/test5.php b/vendor/adodb/adodb-php/tests/test5.php
new file mode 100644
index 0000000..1f0daa1
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test5.php
@@ -0,0 +1,48 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+
+// Select an empty record from the database
+
+include('../adodb.inc.php');
+include('../tohtml.inc.php');
+
+include('../adodb-errorpear.inc.php');
+
+if (0) {
+ $conn = ADONewConnection('mysql');
+ $conn->debug=1;
+ $conn->PConnect("localhost","root","","xphplens");
+ print $conn->databaseType.':'.$conn->GenID().'<br>';
+}
+
+if (0) {
+ $conn = ADONewConnection("oci8"); // create a connection
+ $conn->debug=1;
+ $conn->PConnect("falcon", "scott", "tiger", "juris8.ecosystem.natsoft.com.my"); // connect to MySQL, testdb
+ print $conn->databaseType.':'.$conn->GenID();
+}
+
+if (0) {
+ $conn = ADONewConnection("ibase"); // create a connection
+ $conn->debug=1;
+ $conn->Connect("localhost:c:\\Interbase\\Examples\\Database\\employee.gdb", "sysdba", "masterkey", ""); // connect to MySQL, testdb
+ print $conn->databaseType.':'.$conn->GenID().'<br>';
+}
+
+if (0) {
+ $conn = ADONewConnection('postgres');
+ $conn->debug=1;
+ @$conn->PConnect("susetikus","tester","test","test");
+ print $conn->databaseType.':'.$conn->GenID().'<br>';
+}
diff --git a/vendor/adodb/adodb-php/tests/test_rs_array.php b/vendor/adodb/adodb-php/tests/test_rs_array.php
new file mode 100644
index 0000000..547b20a
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/test_rs_array.php
@@ -0,0 +1,46 @@
+<?php
+
+include_once('../adodb.inc.php');
+$rs = new ADORecordSet_array();
+
+$array = array(
+array ('Name', 'Age'),
+array ('John', '12'),
+array ('Jill', '8'),
+array ('Bill', '49')
+);
+
+$typearr = array('C','I');
+
+
+$rs->InitArray($array,$typearr);
+
+while (!$rs->EOF) {
+ print_r($rs->fields);echo "<br>";
+ $rs->MoveNext();
+}
+
+echo "<hr /> 1 Seek<br>";
+$rs->Move(1);
+while (!$rs->EOF) {
+ print_r($rs->fields);echo "<br>";
+ $rs->MoveNext();
+}
+
+echo "<hr /> 2 Seek<br>";
+$rs->Move(2);
+while (!$rs->EOF) {
+ print_r($rs->fields);echo "<br>";
+ $rs->MoveNext();
+}
+
+echo "<hr /> 3 Seek<br>";
+$rs->Move(3);
+while (!$rs->EOF) {
+ print_r($rs->fields);echo "<br>";
+ $rs->MoveNext();
+}
+
+
+
+die();
diff --git a/vendor/adodb/adodb-php/tests/testcache.php b/vendor/adodb/adodb-php/tests/testcache.php
new file mode 100644
index 0000000..931d272
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testcache.php
@@ -0,0 +1,30 @@
+<html>
+<body>
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+$ADODB_CACHE_DIR = dirname(tempnam('/tmp',''));
+include("../adodb.inc.php");
+
+if (isset($access)) {
+ $db=ADONewConnection('access');
+ $db->PConnect('nwind');
+} else {
+ $db = ADONewConnection('mysql');
+ $db->PConnect('mangrove','root','','xphplens');
+}
+if (isset($cache)) $rs = $db->CacheExecute(120,'select * from products');
+else $rs = $db->Execute('select * from products');
+
+$arr = $rs->GetArray();
+print sizeof($arr);
diff --git a/vendor/adodb/adodb-php/tests/testdatabases.inc.php b/vendor/adodb/adodb-php/tests/testdatabases.inc.php
new file mode 100644
index 0000000..47b6b64
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testdatabases.inc.php
@@ -0,0 +1,478 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+*/
+
+ /* this file is used by the ADODB test program: test.php */
+ ?>
+
+<table><tr valign=top><td>
+<form method=get>
+<input type=checkbox name="testaccess" value=1 <?php echo !empty($testaccess) ? 'checked' : '' ?>> <b>Access</b><br>
+<input type=checkbox name="testibase" value=1 <?php echo !empty($testibase) ? 'checked' : '' ?>> <b>Interbase</b><br>
+<input type=checkbox name="testmssql" value=1 <?php echo !empty($testmssql) ? 'checked' : '' ?>> <b>MSSQL</b><br>
+<input type=checkbox name="testmysql" value=1 <?php echo !empty($testmysql) ? 'checked' : '' ?>> <b>MySQL</b><br>
+<input type=checkbox name="testmysqlodbc" value=1 <?php echo !empty($testmysqlodbc) ? 'checked' : '' ?>> <b>MySQL ODBC</b><br>
+<input type=checkbox name="testmysqli" value=1 <?php echo !empty($testmysqli) ? 'checked' : '' ?>> <b>MySQLi</b>
+<br>
+<td><input type=checkbox name="testsqlite" value=1 <?php echo !empty($testsqlite) ? 'checked' : '' ?>> <b>SQLite</b><br>
+<input type=checkbox name="testproxy" value=1 <?php echo !empty($testproxy) ? 'checked' : '' ?>> <b>MySQL Proxy</b><br>
+<input type=checkbox name="testoracle" value=1 <?php echo !empty($testoracle) ? 'checked' : '' ?>> <b>Oracle (oci8)</b> <br>
+<input type=checkbox name="testpostgres" value=1 <?php echo !empty($testpostgres) ? 'checked' : '' ?>> <b>PostgreSQL</b><br>
+<input type=checkbox name="testpostgres9" value=1 <?php echo !empty($testpostgres9) ? 'checked' : '' ?>> <b>PostgreSQL 9</b><br>
+<input type=checkbox name="testpgodbc" value=1 <?php echo !empty($testpgodbc) ? 'checked' : '' ?>> <b>PostgreSQL ODBC</b><br>
+<td>
+<input type=checkbox name="testpdopgsql" value=1 <?php echo !empty($testpdopgsql) ? 'checked' : '' ?>> <b>PgSQL PDO</b><br>
+<input type=checkbox name="testpdomysql" value=1 <?php echo !empty($testpdomysql) ? 'checked' : '' ?>> <b>MySQL PDO</b><br>
+<input type=checkbox name="testpdosqlite" value=1 <?php echo !empty($testpdosqlite) ? 'checked' : '' ?>> <b>SQLite PDO</b><br>
+<input type=checkbox name="testpdoaccess" value=1 <?php echo !empty($testpdoaccess) ? 'checked' : '' ?>> <b>Access PDO</b><br>
+<input type=checkbox name="testpdomssql" value=1 <?php echo !empty($testpdomssql) ? 'checked' : '' ?>> <b>MSSQL PDO</b><br>
+
+<input type=checkbox name="testpdoora" value=1 <?php echo !empty($testpdoora) ? 'checked' : '' ?>> <b>OCI PDO</b><br>
+
+<td><input type=checkbox name="testdb2" value=1 <?php echo !empty($testdb2) ? 'checked' : '' ?>> DB2<br>
+<input type=checkbox name="testvfp" value=1 <?php echo !empty($testvfp) ? 'checked' : '' ?>> VFP+ODBTP<br>
+<input type=checkbox name="testado" value=1 <?php echo !empty($testado) ? 'checked' : '' ?>> ADO (for mssql and access)<br>
+<input type=checkbox name="nocountrecs" value=1 <?php echo !empty($nocountrecs) ? 'checked' : '' ?>> $ADODB_COUNTRECS=false<br>
+<input type=checkbox name="nolog" value=1 <?php echo !empty($nolog) ? 'checked' : '' ?>> No SQL Logging<br>
+<input type=checkbox name="time" value=1 <?php echo !empty($_GET['time']) ? 'checked' : '' ?>> ADOdb time test
+</table>
+<input type=submit>
+</form>
+
+<?php
+
+if ($ADODB_FETCH_MODE != ADODB_FETCH_DEFAULT) print "<h3>FETCH MODE IS NOT ADODB_FETCH_DEFAULT</h3>";
+
+if (isset($nocountrecs)) $ADODB_COUNTRECS = false;
+
+// cannot test databases below, but we include them anyway to check
+// if they parse ok...
+
+if (sizeof($_GET) || !isset($_SERVER['HTTP_HOST'])) {
+ echo "<BR>";
+ ADOLoadCode2("sybase");
+ ADOLoadCode2("postgres");
+ ADOLoadCode2("postgres7");
+ ADOLoadCode2("firebird");
+ ADOLoadCode2("borland_ibase");
+ ADOLoadCode2("informix");
+ ADOLoadCode2('mysqli');
+ if (defined('ODBC_BINMODE_RETURN')) {
+ ADOLoadCode2("sqlanywhere");
+ ADOLoadCode2("access");
+ }
+ ADOLoadCode2("mysql");
+ ADOLoadCode2("oci8");
+}
+
+function ADOLoadCode2($d)
+{
+ ADOLoadCode($d);
+ $c = ADONewConnection($d);
+ echo "Loaded $d ",($c ? 'ok' : 'extension not installed'),"<br>";
+}
+
+flush();
+
+// dregad 2014-04-15 added serial field to avoid error with lastval()
+$pg_test_table = "create table ADOXYZ (id integer, firstname char(24), lastname varchar,created date, ser serial)";
+$pg_hostname = 'localhost';
+$pg_user = 'tester';
+$pg_password = 'test';
+$pg_database = 'northwind';
+$pg_errmsg = "ERROR: PostgreSQL requires a database called '$pg_database' "
+ . "on server '$pg_hostname', user '$pg_user', password '$pg_password'.<BR>";
+
+if (!empty($testpostgres)) {
+ //ADOLoadCode("postgres");
+
+ $db = ADONewConnection('postgres');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($db->Connect($pg_hostname, $pg_user, $pg_password, $pg_database)) {
+ testdb($db, $pg_test_table);
+ } else {
+ print $pg_errmsg . $db->ErrorMsg();
+ }
+}
+
+if (!empty($testpostgres9)) {
+ //ADOLoadCode("postgres");
+
+ $db = ADONewConnection('postgres9');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($db->Connect($pg_hostname, $pg_user, $pg_password, $pg_database)) {
+ testdb($db, $pg_test_table);
+ } else {
+ print $pg_errmsg . $db->ErrorMsg();
+ }
+}
+
+if (!empty($testpgodbc)) {
+
+ $db = ADONewConnection('odbc');
+ $db->hasTransactions = false;
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ if ($db->PConnect('Postgresql')) {
+ $db->hasTransactions = true;
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date) type=innodb");
+ } else print "ERROR: PostgreSQL requires a database called test on server, user tester, password test.<BR>".$db->ErrorMsg();
+}
+
+if (!empty($testibase)) {
+ //$_GET['nolog'] = true;
+ $db = ADONewConnection('firebird');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($db->PConnect("localhost:d:\\firebird\\151\\examples\\EMPLOYEE.fdb", "sysdba", "masterkey", ""))
+ testdb($db,"create table ADOXYZ (id integer, firstname char(24), lastname char(24),price numeric(12,2),created date)");
+ else print "ERROR: Interbase test requires a database called employee.gdb".'<BR>'.$db->ErrorMsg();
+
+}
+
+
+if (!empty($testsqlite)) {
+ $path =urlencode('d:\inetpub\adodb\sqlite.db');
+ $dsn = "sqlite://$path/";
+ $db = ADONewConnection($dsn);
+ //echo $dsn;
+
+ //$db = ADONewConnection('sqlite');
+
+
+ if ($db && $db->PConnect("d:\\inetpub\\adodb\\sqlite.db", "", "", "")) {
+ print "<h1>Connecting $db->databaseType...</h1>";
+ testdb($db,"create table ADOXYZ (id int, firstname char(24), lastname char(24),created datetime)");
+ } else
+ print "ERROR: SQLite";
+
+}
+
+if (!empty($testpdopgsql)) {
+ $connstr = "pgsql:dbname=test";
+ $u = 'tester';$p='test';
+ $db = ADONewConnection('pdo');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+if (!empty($testpdomysql)) {
+ $connstr = "mysql:dbname=northwind";
+ $u = 'root';$p='';
+ $db = ADONewConnection('pdo');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+if (!empty($testpdomssql)) {
+ $connstr = "mssql:dbname=northwind";
+ $u = 'sa';$p='natsoft';
+ $db = ADONewConnection('pdo');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+if (!empty($testpdosqlite)) {
+ $connstr = "sqlite:d:/inetpub/adodb/sqlite-pdo.db3";
+ $u = '';$p='';
+ $db = ADONewConnection('pdo');
+ $db->hasTransactions = false;
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+if (!empty($testpdoaccess)) {
+ $connstr = 'odbc:nwind';
+ $u = '';$p='';
+ $db = ADONewConnection('pdo');
+ $db->hasTransactions = false;
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+if (!empty($testpdoora)) {
+ $connstr = 'oci:';
+ $u = 'scott';$p='natsoft';
+ $db = ADONewConnection('pdo');
+ #$db->hasTransactions = false;
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $db->Connect($connstr,$u,$p) || die("CONNECT FAILED");
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+}
+
+// REQUIRES ODBC DSN CALLED nwind
+if (!empty($testaccess)) {
+ $db = ADONewConnection('access');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ $access = 'd:\inetpub\wwwroot\php\NWIND.MDB';
+ $dsn = "nwind";
+ $dsn = "Driver={Microsoft Access Driver (*.mdb)};Dbq=$access;Uid=Admin;Pwd=;";
+
+ //$dsn = 'Provider=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=' . $access . ';';
+ if ($db->PConnect($dsn, "", "", ""))
+ testdb($db,"create table ADOXYZ (id int, firstname char(24), lastname char(24),created datetime)");
+ else print "ERROR: Access test requires a Windows ODBC DSN=nwind, Access driver";
+
+}
+
+if (!empty($testaccess) && !empty($testado)) { // ADO ACCESS
+
+ $db = ADONewConnection("ado_access");
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ $access = 'd:\inetpub\wwwroot\php\NWIND.MDB';
+ $myDSN = 'PROVIDER=Microsoft.Jet.OLEDB.4.0;'
+ . 'DATA SOURCE=' . $access . ';';
+ //. 'USER ID=;PASSWORD=;';
+ $_GET['nolog'] = 1;
+ if ($db->PConnect($myDSN, "", "", "")) {
+ print "ADO version=".$db->_connectionID->version."<br>";
+ testdb($db,"create table ADOXYZ (id int, firstname char(24), lastname char(24),created datetime)");
+ } else print "ERROR: Access test requires a Access database $access".'<BR>'.$db->ErrorMsg();
+
+}
+
+if (!empty($testvfp)) { // ODBC
+ $db = ADONewConnection('vfp');
+ print "<h1>Connecting $db->databaseType...</h1>";flush();
+
+ if ( $db->PConnect("vfp-adoxyz")) {
+ testdb($db,"create table d:\\inetpub\\adodb\\ADOXYZ (id int, firstname char(24), lastname char(24),created date)");
+ } else print "ERROR: Visual FoxPro test requires a Windows ODBC DSN=vfp-adoxyz, VFP driver";
+
+ echo "<hr />";
+ $db = ADONewConnection('odbtp');
+
+ if ( $db->PConnect('localhost','DRIVER={Microsoft Visual FoxPro Driver};SOURCETYPE=DBF;SOURCEDB=d:\inetpub\adodb;EXCLUSIVE=NO;')) {
+ print "<h1>Connecting $db->databaseType...</h1>";flush();
+ testdb($db,"create table d:\\inetpub\\adodb\\ADOXYZ (id int, firstname char(24), lastname char(24),created date)");
+ } else print "ERROR: Visual FoxPro odbtp requires a Windows ODBC DSN=vfp-adoxyz, VFP driver";
+
+}
+
+
+// REQUIRES MySQL server at localhost with database 'test'
+if (!empty($testmysql)) { // MYSQL
+
+
+ if (PHP_VERSION >= 5 || $_SERVER['HTTP_HOST'] == 'localhost') $server = 'localhost';
+ else $server = "mangrove";
+ $user = 'root'; $password = ''; $database = 'northwind';
+ $db = ADONewConnection("mysqlt://$user:$password@$server/$database?persist");
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ if (true || $db->PConnect($server, "root", "", "northwind")) {
+ //$db->Execute("DROP TABLE ADOXYZ") || die('fail drop');
+ //$db->debug=1;$db->Execute('drop table ADOXYZ');
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date) Type=InnoDB");
+ } else print "ERROR: MySQL test requires a MySQL server on localhost, userid='admin', password='', database='test'".'<BR>'.$db->ErrorMsg();
+}
+
+// REQUIRES MySQL server at localhost with database 'test'
+if (!empty($testmysqli)) { // MYSQL
+
+ $db = ADONewConnection('mysqli');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if (PHP_VERSION >= 5 || $_SERVER['HTTP_HOST'] == 'localhost') $server = 'localhost';
+ else $server = "mangrove";
+ if ($db->PConnect($server, "root", "", "northwind")) {
+ //$db->debug=1;$db->Execute('drop table ADOXYZ');
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)");
+ } else print "ERROR: MySQL test requires a MySQL server on localhost, userid='admin', password='', database='test'".'<BR>'.$db->ErrorMsg();
+}
+
+
+// REQUIRES MySQL server at localhost with database 'test'
+if (!empty($testmysqlodbc)) { // MYSQL
+
+ $db = ADONewConnection('odbc');
+ $db->hasTransactions = false;
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($_SERVER['HTTP_HOST'] == 'localhost') $server = 'localhost';
+ else $server = "mangrove";
+ if ($db->PConnect('mysql', "root", ""))
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date) type=innodb");
+ else print "ERROR: MySQL test requires a MySQL server on localhost, userid='admin', password='', database='test'".'<BR>'.$db->ErrorMsg();
+}
+
+if (!empty($testproxy)){
+ $db = ADONewConnection('proxy');
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($_SERVER['HTTP_HOST'] == 'localhost') $server = 'localhost';
+
+ if ($db->PConnect('http://localhost/php/phplens/adodb/server.php'))
+ testdb($db,
+ "create table ADOXYZ (id int, firstname char(24), lastname char(24), created date) type=innodb");
+ else print "ERROR: MySQL test requires a MySQL server on localhost, userid='admin', password='', database='test'".'<BR>'.$db->ErrorMsg();
+
+}
+
+ADOLoadCode('oci805');
+ADOLoadCode("oci8po");
+
+if (!empty($testoracle)) {
+ $dsn = "oci8";//://scott:natsoft@kk2?persist";
+ $db = ADONewConnection($dsn );//'oci8');
+
+ //$db->debug=1;
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($db->Connect('mobydick', "scott", "natsoft",'SID=mobydick'))
+ testdb($db,"create table ADOXYZ (id int, firstname varchar(24), lastname varchar(24),created date)");
+ else
+ print "ERROR: Oracle test requires an Oracle server setup with scott/natsoft".'<BR>'.$db->ErrorMsg();
+
+}
+ADOLoadCode("oracle"); // no longer supported
+if (false && !empty($testoracle)) {
+
+ $db = ADONewConnection();
+ print "<h1>Connecting $db->databaseType...</h1>";
+ if ($db->PConnect("", "scott", "tiger", "natsoft.domain"))
+ testdb($db,"create table ADOXYZ (id int, firstname varchar(24), lastname varchar(24),created date)");
+ else print "ERROR: Oracle test requires an Oracle server setup with scott/tiger".'<BR>'.$db->ErrorMsg();
+
+}
+
+ADOLoadCode("odbc_db2"); // no longer supported
+if (!empty($testdb2)) {
+ if (PHP_VERSION>=5.1) {
+ $db = ADONewConnection("db2");
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ #$db->curMode = SQL_CUR_USE_ODBC;
+ #$dsn = "driver={IBM db2 odbc DRIVER};Database=test;hostname=localhost;port=50000;protocol=TCPIP; uid=natsoft; pwd=guest";
+ if ($db->Connect('localhost','natsoft','guest','test')) {
+ testdb($db,"create table ADOXYZ (id int, firstname varchar(24), lastname varchar(24),created date)");
+ } else print "ERROR: DB2 test requires an server setup with odbc data source db2_sample".'<BR>'.$db->ErrorMsg();
+ } else {
+ $db = ADONewConnection("odbc_db2");
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ $dsn = "db2test";
+ #$db->curMode = SQL_CUR_USE_ODBC;
+ #$dsn = "driver={IBM db2 odbc DRIVER};Database=test;hostname=localhost;port=50000;protocol=TCPIP; uid=natsoft; pwd=guest";
+ if ($db->Connect($dsn)) {
+ testdb($db,"create table ADOXYZ (id int, firstname varchar(24), lastname varchar(24),created date)");
+ } else print "ERROR: DB2 test requires an server setup with odbc data source db2_sample".'<BR>'.$db->ErrorMsg();
+ }
+echo "<hr />";
+flush();
+ $dsn = "driver={IBM db2 odbc DRIVER};Database=sample;hostname=localhost;port=50000;protocol=TCPIP; uid=root; pwd=natsoft";
+
+ $db = ADONewConnection('odbtp');
+ if ($db->Connect('127.0.0.1',$dsn)) {
+
+ $db->debug=1;
+ $arr = $db->GetArray( "||SQLProcedures" ); adodb_pr($arr);
+ $arr = $db->GetArray( "||SQLProcedureColumns|||GET_ROUTINE_SAR" );adodb_pr($arr);
+
+ testdb($db,"create table ADOXYZ (id int, firstname varchar(24), lastname varchar(24),created date)");
+ } else echo ("ERROR Connection");
+ echo $db->ErrorMsg();
+}
+
+
+$server = 'localhost';
+
+
+
+ADOLoadCode("mssqlpo");
+if (false && !empty($testmssql)) { // MS SQL Server -- the extension is buggy -- probably better to use ODBC
+ $db = ADONewConnection("mssqlpo");
+ //$db->debug=1;
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ $ok = $db->Connect('','sa','natsoft','northwind');
+ echo $db->ErrorMsg();
+ if ($ok /*or $db->PConnect("mangrove", "sa", "natsoft", "ai")*/) {
+ AutoDetect_MSSQL_Date_Order($db);
+ // $db->Execute('drop table adoxyz');
+ testdb($db,"create table ADOXYZ (id int, firstname char(24) null, lastname char(24) null,created datetime null)");
+ } else print "ERROR: MSSQL test 2 requires a MS SQL 7 on a server='$server', userid='adodb', password='natsoft', database='ai'".'<BR>'.$db->ErrorMsg();
+
+}
+
+
+ADOLoadCode('odbc_mssql');
+if (!empty($testmssql)) { // MS SQL Server via ODBC
+ $db = ADONewConnection();
+
+ print "<h1>Connecting $db->databaseType...</h1>";
+
+ $dsn = "PROVIDER=MSDASQL;Driver={SQL Server};Server=$server;Database=northwind;";
+ $dsn = 'condor';
+ if ($db->PConnect($dsn, "sa", "natsoft", "")) {
+ testdb($db,"create table ADOXYZ (id int, firstname char(24) null, lastname char(24) null,created datetime null)");
+ }
+ else print "ERROR: MSSQL test 1 requires a MS SQL 7 server setup with DSN setup";
+
+}
+
+ADOLoadCode("ado_mssql");
+if (!empty($testmssql) && !empty($testado) ) { // ADO ACCESS MSSQL -- thru ODBC -- DSN-less
+
+ $db = ADONewConnection("ado_mssql");
+ //$db->debug=1;
+ print "<h1>Connecting DSN-less $db->databaseType...</h1>";
+
+ $myDSN="PROVIDER=MSDASQL;DRIVER={SQL Server};"
+ . "SERVER=$server;DATABASE=NorthWind;UID=adodb;PWD=natsoft;Trusted_Connection=No";
+
+
+ if ($db->PConnect($myDSN, "", "", ""))
+ testdb($db,"create table ADOXYZ (id int, firstname char(24) null, lastname char(24) null,created datetime null)");
+ else print "ERROR: MSSQL test 2 requires MS SQL 7";
+
+}
+
+if (!empty($testmssql) && !empty($testado)) { // ADO ACCESS MSSQL with OLEDB provider
+
+ $db = ADONewConnection("ado_mssql");
+ print "<h1>Connecting DSN-less OLEDB Provider $db->databaseType...</h1>";
+ //$db->debug=1;
+ $myDSN="SERVER=localhost;DATABASE=northwind;Trusted_Connection=yes";
+ if ($db->PConnect($myDSN, "adodb", "natsoft", 'SQLOLEDB')) {
+ testdb($db,"create table ADOXYZ (id int, firstname char(24), lastname char(24),created datetime)");
+ } else print "ERROR: MSSQL test 2 requires a MS SQL 7 on a server='mangrove', userid='sa', password='', database='ai'";
+
+}
+
+
+if (extension_loaded('odbtp') && !empty($testmssql)) { // MS SQL Server via ODBC
+ $db = ADONewConnection('odbtp');
+
+ $dsn = "PROVIDER=MSDASQL;Driver={SQL Server};Server=$server;Database=northwind;uid=adodb;pwd=natsoft";
+
+ if ($db->PConnect('localhost',$dsn, "", "")) {
+ print "<h1>Connecting $db->databaseType...</h1>";
+ testdb($db,"create table ADOXYZ (id int, firstname char(24) null, lastname char(24) null,created datetime null)");
+ }
+ else print "ERROR: MSSQL test 1 requires a MS SQL 7 server setup with DSN setup";
+
+}
+
+
+print "<h3>Tests Completed</h3>";
diff --git a/vendor/adodb/adodb-php/tests/testgenid.php b/vendor/adodb/adodb-php/tests/testgenid.php
new file mode 100644
index 0000000..3310734
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testgenid.php
@@ -0,0 +1,35 @@
+<?php
+/*
+ V4.50 6 July 2004
+
+ Run multiple copies of this php script at the same time
+ to test unique generation of id's in multiuser mode
+*/
+include_once('../adodb.inc.php');
+$testaccess = true;
+include_once('testdatabases.inc.php');
+
+function testdb(&$db,$createtab="create table ADOXYZ (id int, firstname char(24), lastname char(24), created date)")
+{
+ $table = 'adodbseq';
+
+ $db->Execute("drop table $table");
+ //$db->debug=true;
+
+ $ctr = 5000;
+ $lastnum = 0;
+
+ while (--$ctr >= 0) {
+ $num = $db->GenID($table);
+ if ($num === false) {
+ print "GenID returned false";
+ break;
+ }
+ if ($lastnum + 1 == $num) print " $num ";
+ else {
+ print " <font color=red>$num</font> ";
+ flush();
+ }
+ $lastnum = $num;
+ }
+}
diff --git a/vendor/adodb/adodb-php/tests/testmssql.php b/vendor/adodb/adodb-php/tests/testmssql.php
new file mode 100644
index 0000000..af40f61
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testmssql.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Set tabs to 4 for best viewing.
+ *
+ * Latest version is available at http://adodb.org/
+ *
+ * Test GetUpdateSQL and GetInsertSQL.
+ */
+
+error_reporting(E_ALL);
+
+
+include('../adodb.inc.php');
+include('../tohtml.inc.php');
+
+//==========================
+// This code tests an insert
+
+
+
+$conn = ADONewConnection("mssql"); // create a connection
+$conn->Connect('127.0.0.1','adodb','natsoft','northwind') or die('Fail');
+
+$conn->debug =1;
+$query = 'select * from products';
+$conn->SetFetchMode(ADODB_FETCH_ASSOC);
+$rs = $conn->Execute($query);
+echo "<pre>";
+while( !$rs->EOF ) {
+ $output[] = $rs->fields;
+ var_dump($rs->fields);
+ $rs->MoveNext();
+ print "<p>";
+}
+die();
+
+
+$p = $conn->Prepare('insert into products (productname,unitprice,dcreated) values (?,?,?)');
+echo "<pre>";
+print_r($p);
+
+$conn->debug=1;
+$conn->Execute($p,array('John'.rand(),33.3,$conn->DBDate(time())));
+
+$p = $conn->Prepare('select * from products where productname like ?');
+$arr = $conn->getarray($p,array('V%'));
+print_r($arr);
+die();
+
+//$conn = ADONewConnection("mssql");
+//$conn->Connect('mangrove','sa','natsoft','ai');
+
+//$conn->Connect('mangrove','sa','natsoft','ai');
+$conn->debug=1;
+$conn->Execute('delete from blobtest');
+
+$conn->Execute('insert into blobtest (id) values(1)');
+$conn->UpdateBlobFile('blobtest','b1','../cute_icons_for_site/adodb.gif','id=1');
+$rs = $conn->Execute('select b1 from blobtest where id=1');
+
+$output = "c:\\temp\\test_out-".date('H-i-s').".gif";
+print "Saving file <b>$output</b>, size=".strlen($rs->fields[0])."<p>";
+$fd = fopen($output, "wb");
+fwrite($fd, $rs->fields[0]);
+fclose($fd);
+
+print " <a href=file://$output>View Image</a>";
+//$rs = $conn->Execute('SELECT id,SUBSTRING(b1, 1, 10) FROM blobtest');
+//rs2html($rs);
diff --git a/vendor/adodb/adodb-php/tests/testoci8.php b/vendor/adodb/adodb-php/tests/testoci8.php
new file mode 100644
index 0000000..af748e9
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testoci8.php
@@ -0,0 +1,84 @@
+<html>
+<body>
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+error_reporting(E_ALL | E_STRICT);
+include("../adodb.inc.php");
+include("../tohtml.inc.php");
+
+if (0) {
+ $db = ADONewConnection('oci8po');
+
+ $db->PConnect('','scott','natsoft');
+ if (!empty($testblob)) {
+ $varHoldingBlob = 'ABC DEF GEF John TEST';
+ $num = time()%10240;
+ // create table atable (id integer, ablob blob);
+ $db->Execute('insert into ATABLE (id,ablob) values('.$num.',empty_blob())');
+ $db->UpdateBlob('ATABLE', 'ablob', $varHoldingBlob, 'id='.$num, 'BLOB');
+
+ $rs = $db->Execute('select * from atable');
+
+ if (!$rs) die("Empty RS");
+ if ($rs->EOF) die("EOF RS");
+ rs2html($rs);
+ }
+ $stmt = $db->Prepare('select * from adoxyz where id=?');
+ for ($i = 1; $i <= 10; $i++) {
+ $rs = $db->Execute(
+ $stmt,
+ array($i));
+
+ if (!$rs) die("Empty RS");
+ if ($rs->EOF) die("EOF RS");
+ rs2html($rs);
+ }
+}
+if (1) {
+ $db = ADONewConnection('oci8');
+ $db->PConnect('','scott','natsoft');
+ $db->debug = true;
+ $db->Execute("delete from emp where ename='John'");
+ print $db->Affected_Rows().'<BR>';
+ $stmt = $db->Prepare('insert into emp (empno, ename) values (:empno, :ename)');
+ $rs = $db->Execute($stmt,array('empno'=>4321,'ename'=>'John'));
+ // prepare not quite ready for prime time
+ //$rs = $db->Execute($stmt,array('empno'=>3775,'ename'=>'John'));
+ if (!$rs) die("Empty RS");
+
+ $db->setfetchmode(ADODB_FETCH_NUM);
+
+ $vv = 'A%';
+ $stmt = $db->PrepareSP("BEGIN adodb.open_tab2(:rs,:tt); END;",true);
+ $db->OutParameter($stmt, $cur, 'rs', -1, OCI_B_CURSOR);
+ $db->OutParameter($stmt, $vv, 'tt');
+ $rs = $db->Execute($stmt);
+ while (!$rs->EOF) {
+ adodb_pr($rs->fields);
+ $rs->MoveNext();
+ }
+ echo " val = $vv";
+
+}
+
+if (0) {
+ $db = ADONewConnection('odbc_oracle');
+ if (!$db->PConnect('local_oracle','scott','tiger')) die('fail connect');
+ $db->debug = true;
+ $rs = $db->Execute(
+ 'select * from adoxyz where firstname=? and trim(lastname)=?',
+ array('first'=>'Caroline','last'=>'Miranda'));
+ if (!$rs) die("Empty RS");
+ if ($rs->EOF) die("EOF RS");
+ rs2html($rs);
+}
diff --git a/vendor/adodb/adodb-php/tests/testoci8cursor.php b/vendor/adodb/adodb-php/tests/testoci8cursor.php
new file mode 100644
index 0000000..1ea59c0
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testoci8cursor.php
@@ -0,0 +1,110 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+/*
+ Test for Oracle Variable Cursors, which are treated as ADOdb recordsets.
+
+ We have 2 examples. The first shows us using the Parameter statement.
+ The second shows us using the new ExecuteCursor($sql, $cursorName)
+ function.
+
+------------------------------------------------------------------
+-- TEST PACKAGE YOU NEED TO INSTALL ON ORACLE - run from sql*plus
+------------------------------------------------------------------
+
+
+-- TEST PACKAGE
+CREATE OR REPLACE PACKAGE adodb AS
+TYPE TabType IS REF CURSOR RETURN tab%ROWTYPE;
+PROCEDURE open_tab (tabcursor IN OUT TabType,tablenames in varchar);
+PROCEDURE data_out(input IN varchar, output OUT varchar);
+
+procedure myproc (p1 in number, p2 out number);
+END adodb;
+/
+
+CREATE OR REPLACE PACKAGE BODY adodb AS
+PROCEDURE open_tab (tabcursor IN OUT TabType,tablenames in varchar) IS
+ BEGIN
+ OPEN tabcursor FOR SELECT * FROM tab where tname like tablenames;
+ END open_tab;
+
+PROCEDURE data_out(input IN varchar, output OUT varchar) IS
+ BEGIN
+ output := 'Cinta Hati '||input;
+ END;
+
+procedure myproc (p1 in number, p2 out number) as
+begin
+p2 := p1;
+end;
+END adodb;
+/
+
+------------------------------------------------------------------
+-- END PACKAGE
+------------------------------------------------------------------
+
+*/
+
+include('../adodb.inc.php');
+include('../tohtml.inc.php');
+
+ error_reporting(E_ALL);
+ $db = ADONewConnection('oci8');
+ $db->PConnect('','scott','natsoft');
+ $db->debug = 99;
+
+
+/*
+*/
+
+ define('MYNUM',5);
+
+
+ $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:RS,'A%'); END;");
+
+ if ($rs && !$rs->EOF) {
+ print "Test 1 RowCount: ".$rs->RecordCount()."<p>";
+ } else {
+ print "<b>Error in using Cursor Variables 1</b><p>";
+ }
+
+ print "<h4>Testing Stored Procedures for oci8</h4>";
+
+ $stid = $db->PrepareSP('BEGIN adodb.myproc('.MYNUM.', :myov); END;');
+ $db->OutParameter($stid, $myov, 'myov');
+ $db->Execute($stid);
+ if ($myov != MYNUM) print "<p><b>Error with myproc</b></p>";
+
+
+ $stmt = $db->PrepareSP("BEGIN adodb.data_out(:a1, :a2); END;",true);
+ $a1 = 'Malaysia';
+ //$a2 = ''; # a2 doesn't even need to be defined!
+ $db->InParameter($stmt,$a1,'a1');
+ $db->OutParameter($stmt,$a2,'a2');
+ $rs = $db->Execute($stmt);
+ if ($rs) {
+ if ($a2 !== 'Cinta Hati Malaysia') print "<b>Stored Procedure Error: a2 = $a2</b><p>";
+ else echo "OK: a2=$a2<p>";
+ } else {
+ print "<b>Error in using Stored Procedure IN/Out Variables</b><p>";
+ }
+
+
+ $tname = 'A%';
+
+ $stmt = $db->PrepareSP('select * from tab where tname like :tablename');
+ $db->Parameter($stmt,$tname,'tablename');
+ $rs = $db->Execute($stmt);
+ rs2html($rs);
diff --git a/vendor/adodb/adodb-php/tests/testpaging.php b/vendor/adodb/adodb-php/tests/testpaging.php
new file mode 100644
index 0000000..fe579d5
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testpaging.php
@@ -0,0 +1,87 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+error_reporting(E_ALL);
+
+
+include_once('../adodb.inc.php');
+include_once('../adodb-pager.inc.php');
+
+$driver = 'oci8';
+$sql = 'select ID, firstname as "First Name", lastname as "Last Name" from adoxyz order by id';
+//$sql = 'select count(*),firstname from adoxyz group by firstname order by 2 ';
+//$sql = 'select distinct firstname, lastname from adoxyz order by firstname';
+
+if ($driver == 'postgres') {
+ $db = NewADOConnection('postgres');
+ $db->PConnect('localhost','tester','test','test');
+}
+
+if ($driver == 'access') {
+ $db = NewADOConnection('access');
+ $db->PConnect("nwind", "", "", "");
+}
+
+if ($driver == 'ibase') {
+ $db = NewADOConnection('ibase');
+ $db->PConnect("localhost:e:\\firebird\\examples\\employee.gdb", "sysdba", "masterkey", "");
+ $sql = 'select distinct firstname, lastname from adoxyz order by firstname';
+
+}
+if ($driver == 'mssql') {
+ $db = NewADOConnection('mssql');
+ $db->Connect('JAGUAR\vsdotnet','adodb','natsoft','northwind');
+}
+if ($driver == 'oci8') {
+ $db = NewADOConnection('oci8');
+ $db->Connect('','scott','natsoft');
+
+$sql = "select * from (select ID, firstname as \"First Name\", lastname as \"Last Name\" from adoxyz
+ order by 1)";
+}
+
+if ($driver == 'access') {
+ $db = NewADOConnection('access');
+ $db->Connect('nwind');
+}
+
+if (empty($driver) or $driver == 'mysql') {
+ $db = NewADOConnection('mysql');
+ $db->Connect('localhost','root','','test');
+}
+
+//$db->pageExecuteCountRows = false;
+
+$db->debug = true;
+
+if (0) {
+$rs = $db->Execute($sql);
+include_once('../toexport.inc.php');
+print "<pre>";
+print rs2csv($rs); # return a string
+
+print '<hr />';
+$rs->MoveFirst(); # note, some databases do not support MoveFirst
+print rs2tab($rs); # return a string
+
+print '<hr />';
+$rs->MoveFirst();
+rs2tabout($rs); # send to stdout directly
+print "</pre>";
+}
+
+$pager = new ADODB_Pager($db,$sql);
+$pager->showPageLinks = true;
+$pager->linksPerPage = 10;
+$pager->cache = 60;
+$pager->Render($rows=7);
diff --git a/vendor/adodb/adodb-php/tests/testpear.php b/vendor/adodb/adodb-php/tests/testpear.php
new file mode 100644
index 0000000..3f209c1
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testpear.php
@@ -0,0 +1,35 @@
+<?php
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+error_reporting(E_ALL);
+
+include_once('../adodb-pear.inc.php');
+$username = 'root';
+$password = '';
+$hostname = 'localhost';
+$databasename = 'xphplens';
+$driver = 'mysql';
+
+$dsn = "$driver://$username:$password@$hostname/$databasename";
+
+$db = DB::Connect($dsn);
+$db->setFetchMode(ADODB_FETCH_ASSOC);
+$rs = $db->Query('select firstname,lastname from adoxyz');
+$cnt = 0;
+while ($arr = $rs->FetchRow()) {
+ print_r($arr);
+ print "<br>";
+ $cnt += 1;
+}
+
+if ($cnt != 50) print "<b>Error in \$cnt = $cnt</b>";
diff --git a/vendor/adodb/adodb-php/tests/testsessions.php b/vendor/adodb/adodb-php/tests/testsessions.php
new file mode 100644
index 0000000..2ca7342
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/testsessions.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+@version v5.20.14 06-Jan-2019
+@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+@copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+ Set tabs to 4 for best viewing.
+
+ Latest version is available at http://adodb.org/
+*/
+
+function NotifyExpire($ref,$key)
+{
+ print "<p><b>Notify Expiring=$ref, sessionkey=$key</b></p>";
+}
+
+//-------------------------------------------------------------------
+
+error_reporting(E_ALL);
+
+
+ob_start();
+include('../session/adodb-cryptsession2.php');
+
+
+$options['debug'] = 1;
+$db = 'postgres';
+
+#### CONNECTION
+switch($db) {
+case 'oci8':
+ $options['table'] = 'adodb_sessions2';
+ ADOdb_Session::config('oci8', 'mobydick', 'jdev', 'natsoft', 'mobydick',$options);
+ break;
+
+case 'postgres':
+ $options['table'] = 'sessions2';
+ ADOdb_Session::config('postgres', 'localhost', 'postgres', 'natsoft', 'northwind',$options);
+ break;
+
+case 'mysql':
+default:
+ $options['table'] = 'sessions2';
+ ADOdb_Session::config('mysql', 'localhost', 'root', '', 'xphplens_2',$options);
+ break;
+
+
+}
+
+
+
+#### SETUP NOTIFICATION
+ $USER = 'JLIM'.rand();
+ $ADODB_SESSION_EXPIRE_NOTIFY = array('USER','NotifyExpire');
+
+ adodb_session_create_table();
+ session_start();
+
+ adodb_session_regenerate_id();
+
+### SETUP SESSION VARIABLES
+ if (empty($_SESSION['MONKEY'])) $_SESSION['MONKEY'] = array(1,'abc',44.41);
+ else $_SESSION['MONKEY'][0] += 1;
+ if (!isset($_GET['nochange'])) @$_SESSION['AVAR'] += 1;
+
+
+### START DISPLAY
+ print "<h3>PHP ".PHP_VERSION."</h3>";
+ print "<p><b>\$_SESSION['AVAR']={$_SESSION['AVAR']}</b></p>";
+
+ print "<hr /> <b>Cookies</b>: ";
+ print_r($_COOKIE);
+
+ var_dump($_SESSION['MONKEY']);
+
+### RANDOMLY PERFORM Garbage Collection
+### In real-production environment, this is done for you
+### by php's session extension, which calls adodb_sess_gc()
+### automatically for you. See php.ini's
+### session.cookie_lifetime and session.gc_probability
+
+ if (rand() % 5 == 0) {
+
+ print "<hr /><p><b>Garbage Collection</b></p>";
+ adodb_sess_gc(10);
+
+ if (rand() % 2 == 0) {
+ print "<p>Random own session destroy</p>";
+ session_destroy();
+ }
+ } else {
+ $DB = ADODB_Session::_conn();
+ $sessk = $DB->qstr('%AZ'.rand().time());
+ $olddate = $DB->DBTimeStamp(time()-30*24*3600);
+ $rr = $DB->qstr(rand());
+ $DB->Execute("insert into {$options['table']} (sesskey,expiry,expireref,sessdata,created,modified) values ($sessk,$olddate, $rr,'',$olddate,$olddate)");
+ }
diff --git a/vendor/adodb/adodb-php/tests/time.php b/vendor/adodb/adodb-php/tests/time.php
new file mode 100644
index 0000000..8261e1e
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/time.php
@@ -0,0 +1,16 @@
+<?php
+
+include_once('../adodb-time.inc.php');
+adodb_date_test();
+?>
+<?php
+//require("adodb-time.inc.php");
+
+$datestring = "2063-12-24"; // string normally from mySQL
+$stringArray = explode("-", $datestring);
+$date = adodb_mktime(0,0,0,$stringArray[1],$stringArray[2],$stringArray[0]);
+
+$convertedDate = adodb_date("d-M-Y", $date); // converted string to UK style date
+
+echo( "Original: $datestring<br>" );
+echo( "Converted: $convertedDate" ); //why is string returned as one day (3 not 4) less for this example??
diff --git a/vendor/adodb/adodb-php/tests/tmssql.php b/vendor/adodb/adodb-php/tests/tmssql.php
new file mode 100644
index 0000000..0f81311
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/tmssql.php
@@ -0,0 +1,79 @@
+<?php
+error_reporting(E_ALL);
+ini_set('mssql.datetimeconvert',0);
+
+function tmssql()
+{
+ print "<h3>mssql</h3>";
+ $db = mssql_connect('JAGUAR\vsdotnet','adodb','natsoft') or die('No Connection');
+ mssql_select_db('northwind',$db);
+
+ $rs = mssql_query('select getdate() as date',$db);
+ $o = mssql_fetch_row($rs);
+ print_r($o);
+ mssql_free_result($rs);
+
+ print "<p>Delete</p>"; flush();
+ $rs2 = mssql_query('delete from adoxyz',$db);
+ $p = mssql_num_rows($rs2);
+ mssql_free_result($rs2);
+
+}
+
+function tpear()
+{
+include_once('DB.php');
+
+ print "<h3>PEAR</h3>";
+ $username = 'adodb';
+ $password = 'natsoft';
+ $hostname = 'JAGUAR\vsdotnet';
+ $databasename = 'northwind';
+
+ $dsn = "mssql://$username:$password@$hostname/$databasename";
+ $conn = DB::connect($dsn);
+ print "date=".$conn->GetOne('select getdate()')."<br>";
+ @$conn->query('create table tester (id integer)');
+ print "<p>Delete</p>"; flush();
+ $rs = $conn->query('delete from tester');
+ print "date=".$conn->GetOne('select getdate()')."<br>";
+}
+
+function tadodb()
+{
+include_once('../adodb.inc.php');
+
+ print "<h3>ADOdb</h3>";
+ $conn = NewADOConnection('mssql');
+ $conn->Connect('JAGUAR\vsdotnet','adodb','natsoft','northwind');
+// $conn->debug=1;
+ print "date=".$conn->GetOne('select getdate()')."<br>";
+ $conn->Execute('create table tester (id integer)');
+ print "<p>Delete</p>"; flush();
+ $rs = $conn->Execute('delete from tester');
+ print "date=".$conn->GetOne('select getdate()')."<br>";
+}
+
+
+$ACCEPTIP = '127.0.0.1';
+
+$remote = $_SERVER["REMOTE_ADDR"];
+
+if (!empty($ACCEPTIP))
+ if ($remote != '127.0.0.1' && $remote != $ACCEPTIP)
+ die("Unauthorised client: '$remote'");
+
+?>
+<a href=tmssql.php?do=tmssql>mssql</a>
+<a href=tmssql.php?do=tpear>pear</a>
+<a href=tmssql.php?do=tadodb>adodb</a>
+<?php
+if (!empty($_GET['do'])) {
+ $do = $_GET['do'];
+ switch($do) {
+ case 'tpear':
+ case 'tadodb':
+ case 'tmssql':
+ $do();
+ }
+}
diff --git a/vendor/adodb/adodb-php/tests/xmlschema-mssql.xml b/vendor/adodb/adodb-php/tests/xmlschema-mssql.xml
new file mode 100644
index 0000000..db2c343
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/xmlschema-mssql.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+<table name="simple_table">
+<field name="id" type="I" size="11">
+<KEY/>
+<AUTOINCREMENT/>
+</field>
+<field name="name" type="C" size="3">
+<DEFAULT value="no"/>
+</field>
+<field name="description" type="X"></field>
+<index name="id">
+<UNIQUE/>
+<col>id</col>
+</index>
+<index name="id_2">
+<col>id</col>
+</index>
+<data>
+</data>
+</table>
+ <sql>
+ <descr>SQL to be executed only on specific platforms</descr>
+ <query platform="postgres|postgres7">
+ insert into mytable ( row1, row2 ) values ( 12, 'postgres stuff' )
+ </query>
+ <query platform="mysql">
+ insert into mytable ( row1, row2 ) values ( 12, 'mysql stuff' )
+ </query>
+ <query platform="mssql">
+ INSERT into simple_table ( name, description ) values ( '12', 'Microsoft stuff' )
+ </query>
+ </sql>
+</schema> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/tests/xmlschema.xml b/vendor/adodb/adodb-php/tests/xmlschema.xml
new file mode 100644
index 0000000..ea48ae2
--- /dev/null
+++ b/vendor/adodb/adodb-php/tests/xmlschema.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="mytable">
+ <field name="row1" type="I">
+ <descr>An integer row that's a primary key and autoincrements</descr>
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="row2" type="C" size="16">
+ <descr>A 16 character varchar row that can't be null</descr>
+ <NOTNULL/>
+ </field>
+ <index name="myindex">
+ <col>row1</col>
+ <col>row2</col>
+ </index>
+ </table>
+ <sql>
+ <descr>SQL to be executed only on specific platforms</descr>
+ <query platform="postgres|postgres7">
+ insert into mytable ( row1, row2 ) values ( 12, 'postgres stuff' )
+ </query>
+ <query platform="mysql">
+ insert into mytable ( row1, row2 ) values ( 12, 'mysql stuff' )
+ </query>
+ <query platform="mssql">
+ insert into mytable ( row1, row2 ) values ( 12, 'Microsoft stuff' )
+ </query>
+ </sql>
+ <table name="obsoletetable">
+ <DROP/>
+ </table>
+</schema> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/toexport.inc.php b/vendor/adodb/adodb-php/toexport.inc.php
new file mode 100644
index 0000000..7bf6d3a
--- /dev/null
+++ b/vendor/adodb/adodb-php/toexport.inc.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @version v5.20.14 06-Jan-2019
+ * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ * @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ * Released under both BSD license and Lesser GPL library license.
+ * Whenever there is any discrepancy between the two licenses,
+ * the BSD license will take precedence.
+ *
+ * Code to export recordsets in several formats:
+ *
+ * AS VARIABLE
+ * $s = rs2csv($rs); # comma-separated values
+ * $s = rs2tab($rs); # tab delimited
+ *
+ * TO A FILE
+ * $f = fopen($path,'w');
+ * rs2csvfile($rs,$f);
+ * fclose($f);
+ *
+ * TO STDOUT
+ * rs2csvout($rs);
+ */
+
+// returns a recordset as a csv string
+function rs2csv(&$rs,$addtitles=true)
+{
+ return _adodb_export($rs,',',',',false,$addtitles);
+}
+
+// writes recordset to csv file
+function rs2csvfile(&$rs,$fp,$addtitles=true)
+{
+ _adodb_export($rs,',',',',$fp,$addtitles);
+}
+
+// write recordset as csv string to stdout
+function rs2csvout(&$rs,$addtitles=true)
+{
+ $fp = fopen('php://stdout','wb');
+ _adodb_export($rs,',',',',true,$addtitles);
+ fclose($fp);
+}
+
+function rs2tab(&$rs,$addtitles=true)
+{
+ return _adodb_export($rs,"\t",',',false,$addtitles);
+}
+
+// to file pointer
+function rs2tabfile(&$rs,$fp,$addtitles=true)
+{
+ _adodb_export($rs,"\t",',',$fp,$addtitles);
+}
+
+// to stdout
+function rs2tabout(&$rs,$addtitles=true)
+{
+ $fp = fopen('php://stdout','wb');
+ _adodb_export($rs,"\t",' ',true,$addtitles);
+ if ($fp) fclose($fp);
+}
+
+function _adodb_export(&$rs,$sep,$sepreplace,$fp=false,$addtitles=true,$quote = '"',$escquote = '"',$replaceNewLine = ' ')
+{
+ if (!$rs) return '';
+ //----------
+ // CONSTANTS
+ $NEWLINE = "\r\n";
+ $BUFLINES = 100;
+ $escquotequote = $escquote.$quote;
+ $s = '';
+
+ if ($addtitles) {
+ $fieldTypes = $rs->FieldTypesArray();
+ reset($fieldTypes);
+ $i = 0;
+ $elements = array();
+ foreach ($fieldTypes as $o) {
+
+ $v = ($o) ? $o->name : 'Field'.($i++);
+ if ($escquote) $v = str_replace($quote,$escquotequote,$v);
+ $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v))));
+ $elements[] = $v;
+
+ }
+ $s .= implode($sep, $elements).$NEWLINE;
+ }
+ $hasNumIndex = isset($rs->fields[0]);
+
+ $line = 0;
+ $max = $rs->FieldCount();
+
+ while (!$rs->EOF) {
+ $elements = array();
+ $i = 0;
+
+ if ($hasNumIndex) {
+ for ($j=0; $j < $max; $j++) {
+ $v = $rs->fields[$j];
+ if (!is_object($v)) $v = trim($v);
+ else $v = 'Object';
+ if ($escquote) $v = str_replace($quote,$escquotequote,$v);
+ $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v))));
+
+ if (strpos($v,$sep) !== false || strpos($v,$quote) !== false) $elements[] = "$quote$v$quote";
+ else $elements[] = $v;
+ }
+ } else { // ASSOCIATIVE ARRAY
+ foreach($rs->fields as $v) {
+ if ($escquote) $v = str_replace($quote,$escquotequote,trim($v));
+ $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v))));
+
+ if (strpos($v,$sep) !== false || strpos($v,$quote) !== false) $elements[] = "$quote$v$quote";
+ else $elements[] = $v;
+ }
+ }
+ $s .= implode($sep, $elements).$NEWLINE;
+ $rs->MoveNext();
+ $line += 1;
+ if ($fp && ($line % $BUFLINES) == 0) {
+ if ($fp === true) echo $s;
+ else fwrite($fp,$s);
+ $s = '';
+ }
+ }
+
+ if ($fp) {
+ if ($fp === true) echo $s;
+ else fwrite($fp,$s);
+ $s = '';
+ }
+
+ return $s;
+}
diff --git a/vendor/adodb/adodb-php/tohtml.inc.php b/vendor/adodb/adodb-php/tohtml.inc.php
new file mode 100644
index 0000000..73d61aa
--- /dev/null
+++ b/vendor/adodb/adodb-php/tohtml.inc.php
@@ -0,0 +1,201 @@
+<?php
+/*
+ @version v5.20.14 06-Jan-2019
+ @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
+ @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
+ Released under both BSD license and Lesser GPL library license.
+ Whenever there is any discrepancy between the two licenses,
+ the BSD license will take precedence.
+
+ Some pretty-printing by Chris Oxenreider <oxenreid@state.net>
+*/
+
+// specific code for tohtml
+GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND;
+
+$ADODB_ROUND=4; // rounding
+$gSQLMaxRows = 1000; // max no of rows to download
+$gSQLBlockRows=20; // max no of rows per table block
+
+// RecordSet to HTML Table
+//------------------------------------------------------------
+// Convert a recordset to a html table. Multiple tables are generated
+// if the number of rows is > $gSQLBlockRows. This is because
+// web browsers normally require the whole table to be downloaded
+// before it can be rendered, so we break the output into several
+// smaller faster rendering tables.
+//
+// $rs: the recordset
+// $ztabhtml: the table tag attributes (optional)
+// $zheaderarray: contains the replacement strings for the headers (optional)
+//
+// USAGE:
+// include('adodb.inc.php');
+// $db = ADONewConnection('mysql');
+// $db->Connect('mysql','userid','password','database');
+// $rs = $db->Execute('select col1,col2,col3 from table');
+// rs2html($rs, 'BORDER=2', array('Title1', 'Title2', 'Title3'));
+// $rs->Close();
+//
+// RETURNS: number of rows displayed
+
+
+function rs2html(&$rs,$ztabhtml=false,$zheaderarray=false,$htmlspecialchars=true,$echo = true)
+{
+$s ='';$rows=0;$docnt = false;
+GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND;
+
+ if (!$rs) {
+ printf(ADODB_BAD_RS,'rs2html');
+ return false;
+ }
+
+ if (! $ztabhtml) $ztabhtml = "BORDER='1' WIDTH='98%'";
+ //else $docnt = true;
+ $typearr = array();
+ $ncols = $rs->FieldCount();
+ $hdr = "<TABLE COLS=$ncols $ztabhtml><tr>\n\n";
+ for ($i=0; $i < $ncols; $i++) {
+ $field = $rs->FetchField($i);
+ if ($field) {
+ if ($zheaderarray) $fname = $zheaderarray[$i];
+ else $fname = htmlspecialchars($field->name);
+ $typearr[$i] = $rs->MetaType($field->type,$field->max_length);
+ //print " $field->name $field->type $typearr[$i] ";
+ } else {
+ $fname = 'Field '.($i+1);
+ $typearr[$i] = 'C';
+ }
+ if (strlen($fname)==0) $fname = '&nbsp;';
+ $hdr .= "<TH>$fname</TH>";
+ }
+ $hdr .= "\n</tr>";
+ if ($echo) print $hdr."\n\n";
+ else $html = $hdr;
+
+ // smart algorithm - handles ADODB_FETCH_MODE's correctly by probing...
+ $numoffset = isset($rs->fields[0]) ||isset($rs->fields[1]) || isset($rs->fields[2]);
+ while (!$rs->EOF) {
+
+ $s .= "<TR valign=top>\n";
+
+ for ($i=0; $i < $ncols; $i++) {
+ if ($i===0) $v=($numoffset) ? $rs->fields[0] : reset($rs->fields);
+ else $v = ($numoffset) ? $rs->fields[$i] : next($rs->fields);
+
+ $type = $typearr[$i];
+ switch($type) {
+ case 'D':
+ if (strpos($v,':') !== false);
+ else {
+ if (empty($v)) {
+ $s .= "<TD> &nbsp; </TD>\n";
+ } else {
+ $s .= " <TD>".$rs->UserDate($v,"D d, M Y") ."</TD>\n";
+ }
+ break;
+ }
+ case 'T':
+ if (empty($v)) $s .= "<TD> &nbsp; </TD>\n";
+ else $s .= " <TD>".$rs->UserTimeStamp($v,"D d, M Y, H:i:s") ."</TD>\n";
+ break;
+
+ case 'N':
+ if (abs(abs($v) - round($v,0)) < 0.00000001)
+ $v = round($v);
+ else
+ $v = round($v,$ADODB_ROUND);
+ case 'I':
+ $vv = stripslashes((trim($v)));
+ if (strlen($vv) == 0) $vv .= '&nbsp;';
+ $s .= " <TD align=right>".$vv ."</TD>\n";
+
+ break;
+ /*
+ case 'B':
+ if (substr($v,8,2)=="BM" ) $v = substr($v,8);
+ $mtime = substr(str_replace(' ','_',microtime()),2);
+ $tmpname = "tmp/".uniqid($mtime).getmypid();
+ $fd = @fopen($tmpname,'a');
+ @ftruncate($fd,0);
+ @fwrite($fd,$v);
+ @fclose($fd);
+ if (!function_exists ("mime_content_type")) {
+ function mime_content_type ($file) {
+ return exec("file -bi ".escapeshellarg($file));
+ }
+ }
+ $t = mime_content_type($tmpname);
+ $s .= (substr($t,0,5)=="image") ? " <td><img src='$tmpname' alt='$t'></td>\\n" : " <td><a
+ href='$tmpname'>$t</a></td>\\n";
+ break;
+ */
+
+ default:
+ if ($htmlspecialchars) $v = htmlspecialchars(trim($v));
+ $v = trim($v);
+ if (strlen($v) == 0) $v = '&nbsp;';
+ $s .= " <TD>". str_replace("\n",'<br>',stripslashes($v)) ."</TD>\n";
+
+ }
+ } // for
+ $s .= "</TR>\n\n";
+
+ $rows += 1;
+ if ($rows >= $gSQLMaxRows) {
+ $rows = "<p>Truncated at $gSQLMaxRows</p>";
+ break;
+ } // switch
+
+ $rs->MoveNext();
+
+ // additional EOF check to prevent a widow header
+ if (!$rs->EOF && $rows % $gSQLBlockRows == 0) {
+
+ //if (connection_aborted()) break;// not needed as PHP aborts script, unlike ASP
+ if ($echo) print $s . "</TABLE>\n\n";
+ else $html .= $s ."</TABLE>\n\n";
+ $s = $hdr;
+ }
+ } // while
+
+ if ($echo) print $s."</TABLE>\n\n";
+ else $html .= $s."</TABLE>\n\n";
+
+ if ($docnt) if ($echo) print "<H2>".$rows." Rows</H2>";
+
+ return ($echo) ? $rows : $html;
+ }
+
+// pass in 2 dimensional array
+function arr2html(&$arr,$ztabhtml='',$zheaderarray='')
+{
+ if (!$ztabhtml) $ztabhtml = 'BORDER=1';
+
+ $s = "<TABLE $ztabhtml>";//';print_r($arr);
+
+ if ($zheaderarray) {
+ $s .= '<TR>';
+ for ($i=0; $i<sizeof($zheaderarray); $i++) {
+ $s .= " <TH>{$zheaderarray[$i]}</TH>\n";
+ }
+ $s .= "\n</TR>";
+ }
+
+ for ($i=0; $i<sizeof($arr); $i++) {
+ $s .= '<TR>';
+ $a = $arr[$i];
+ if (is_array($a))
+ for ($j=0; $j<sizeof($a); $j++) {
+ $val = $a[$j];
+ if (empty($val)) $val = '&nbsp;';
+ $s .= " <TD>$val</TD>\n";
+ }
+ else if ($a) {
+ $s .= ' <TD>'.$a."</TD>\n";
+ } else $s .= " <TD>&nbsp;</TD>\n";
+ $s .= "\n</TR>\n";
+ }
+ $s .= '</TABLE>';
+ print $s;
+}
diff --git a/vendor/adodb/adodb-php/xmlschema.dtd b/vendor/adodb/adodb-php/xmlschema.dtd
new file mode 100644
index 0000000..2d0b579
--- /dev/null
+++ b/vendor/adodb/adodb-php/xmlschema.dtd
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!DOCTYPE adodb_schema [
+<!ELEMENT schema (table*, sql*)>
+<!ATTLIST schema version CDATA #REQUIRED>
+<!ELEMENT table ((field+|DROP), CONSTRAINT*, descr?, index*, data*)>
+<!ELEMENT field ((NOTNULL|KEY|PRIMARY)?, (AUTO|AUTOINCREMENT)?, (DEFAULT|DEFDATE|DEFTIMESTAMP)?,
+NOQUOTE?, CONSTRAINT*, descr?)>
+<!ELEMENT data (row+)>
+<!ELEMENT row (f+)>
+<!ELEMENT f (#CDATA)>
+<!ELEMENT descr (#CDATA)>
+<!ELEMENT NOTNULL EMPTY>
+<!ELEMENT KEY EMPTY>
+<!ELEMENT PRIMARY EMPTY>
+<!ELEMENT AUTO EMPTY>
+<!ELEMENT AUTOINCREMENT EMPTY>
+<!ELEMENT DEFAULT EMPTY>
+<!ELEMENT DEFDATE EMPTY>
+<!ELEMENT DEFTIMESTAMP EMPTY>
+<!ELEMENT NOQUOTE EMPTY>
+<!ELEMENT DROP EMPTY>
+<!ELEMENT CONSTRAINT (#CDATA)>
+<!ATTLIST table name CDATA #REQUIRED platform CDATA #IMPLIED version CDATA #IMPLIED>
+<!ATTLIST field name CDATA #REQUIRED type (C|C2|X|X2|B|D|T|L|I|F|N) #REQUIRED size CDATA #IMPLIED>
+<!ATTLIST data platform CDATA #IMPLIED>
+<!ATTLIST f name CDATA #IMPLIED>
+<!ATTLIST DEFAULT VALUE CDATA #REQUIRED>
+<!ELEMENT index ((col+|DROP), CLUSTERED?, BITMAP?, UNIQUE?, FULLTEXT?, HASH?, descr?)>
+<!ELEMENT col (#CDATA)>
+<!ELEMENT CLUSTERED EMPTY>
+<!ELEMENT BITMAP EMPTY>
+<!ELEMENT UNIQUE EMPTY>
+<!ELEMENT FULLTEXT EMPTY>
+<!ELEMENT HASH EMPTY>
+<!ATTLIST index name CDATA #REQUIRED platform CDATA #IMPLIED>
+<!ELEMENT sql (query+, descr?)>
+<!ELEMENT query (#CDATA)>
+<!ATTLIST sql name CDATA #IMPLIED platform CDATA #IMPLIED, key CDATA, prefixmethod (AUTO|MANUAL|NONE) >
+] > \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xmlschema03.dtd b/vendor/adodb/adodb-php/xmlschema03.dtd
new file mode 100644
index 0000000..97850bc
--- /dev/null
+++ b/vendor/adodb/adodb-php/xmlschema03.dtd
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE adodb_schema [
+<!ELEMENT schema (table*, sql*)>
+<!ATTLIST schema version CDATA #REQUIRED>
+<!ELEMENT table (descr?, (field+|DROP), constraint*, opt*, index*, data*)>
+<!ATTLIST table name CDATA #REQUIRED platform CDATA #IMPLIED version CDATA #IMPLIED>
+<!ELEMENT field (descr?, (NOTNULL|KEY|PRIMARY)?, (AUTO|AUTOINCREMENT)?, (DEFAULT|DEFDATE|DEFTIMESTAMP)?, NOQUOTE?, UNSIGNED?, constraint*, opt*)>
+<!ATTLIST field name CDATA #REQUIRED type (C|C2|X|X2|B|D|T|L|I|F|N) #REQUIRED size CDATA #IMPLIED opts CDATA #IMPLIED>
+<!ELEMENT data (descr?, row+)>
+<!ATTLIST data platform CDATA #IMPLIED>
+<!ELEMENT row (f+)>
+<!ELEMENT f (#CDATA)>
+<!ATTLIST f name CDATA #IMPLIED>
+<!ELEMENT descr (#CDATA)>
+<!ELEMENT NOTNULL EMPTY>
+<!ELEMENT KEY EMPTY>
+<!ELEMENT PRIMARY EMPTY>
+<!ELEMENT AUTO EMPTY>
+<!ELEMENT AUTOINCREMENT EMPTY>
+<!ELEMENT DEFAULT EMPTY>
+<!ATTLIST DEFAULT value CDATA #REQUIRED>
+<!ELEMENT DEFDATE EMPTY>
+<!ELEMENT DEFTIMESTAMP EMPTY>
+<!ELEMENT NOQUOTE EMPTY>
+<!ELEMENT UNSIGNED EMPTY>
+<!ELEMENT DROP EMPTY>
+<!ELEMENT constraint (#CDATA)>
+<!ATTLIST constraint platform CDATA #IMPLIED>
+<!ELEMENT opt (#CDATA)>
+<!ATTLIST opt platform CDATA #IMPLIED>
+<!ELEMENT index ((col+|DROP), CLUSTERED?, BITMAP?, UNIQUE?, FULLTEXT?, HASH?, descr?)>
+<!ATTLIST index name CDATA #REQUIRED platform CDATA #IMPLIED>
+<!ELEMENT col (#CDATA)>
+<!ELEMENT CLUSTERED EMPTY>
+<!ELEMENT BITMAP EMPTY>
+<!ELEMENT UNIQUE EMPTY>
+<!ELEMENT FULLTEXT EMPTY>
+<!ELEMENT HASH EMPTY>
+<!ELEMENT sql (query+, descr?)>
+<!ATTLIST sql name CDATA #IMPLIED platform CDATA #IMPLIED, key CDATA, prefixmethod (AUTO|MANUAL|NONE)>
+<!ELEMENT query (#CDATA)>
+<!ATTLIST query platform CDATA #IMPLIED>
+]> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/convert-0.1-0.2.xsl b/vendor/adodb/adodb-php/xsl/convert-0.1-0.2.xsl
new file mode 100644
index 0000000..5b2e3ce
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/convert-0.1-0.2.xsl
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.2</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table|schema/sql"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:variable name="table_name" select="@name"/>
+
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="$table_name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="field"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="constraint"/>
+
+ <xsl:apply-templates select="../index[@table=$table_name]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Field -->
+ <xsl:template match="field">
+ <xsl:element name="field">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="@type"/></xsl:attribute>
+
+ <xsl:if test="string-length(@size) > 0">
+ <xsl:attribute name="size"><xsl:value-of select="@size"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(PRIMARY) > 0">
+ <xsl:element name="PRIMARY"/>
+ </xsl:when>
+ <xsl:when test="count(KEY) > 0">
+ <xsl:element name="KEY"/>
+ </xsl:when>
+ <xsl:when test="count(NOTNULL) > 0">
+ <xsl:element name="NOTNULL"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(AUTO) > 0">
+ <xsl:element name="AUTO"/>
+ </xsl:when>
+ <xsl:when test="count(AUTOINCREMENT) > 0">
+ <xsl:element name="AUTOINCREMENT"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(DEFAULT) > 0">
+ <xsl:element name="DEFAULT">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFAULT[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFDATE) > 0">
+ <xsl:element name="DEFDATE">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFDATE[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFTIMESTAMP) > 0">
+ <xsl:element name="DEFTIMESTAMP">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFTIMESTAMP[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:if test="count(NOQUOTE) > 0">
+ <xsl:element name="NOQUOTE"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="constraint"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Constraint -->
+ <xsl:template match="constraint">
+ <xsl:element name="constraint">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index -->
+ <xsl:template match="index">
+ <xsl:element name="index">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:if test="count(CLUSTERED) > 0">
+ <xsl:element name="CLUSTERED"/>
+ </xsl:if>
+
+ <xsl:if test="count(BITMAP) > 0">
+ <xsl:element name="BITMAP"/>
+ </xsl:if>
+
+ <xsl:if test="count(UNIQUE) > 0">
+ <xsl:element name="UNIQUE"/>
+ </xsl:if>
+
+ <xsl:if test="count(FULLTEXT) > 0">
+ <xsl:element name="FULLTEXT"/>
+ </xsl:if>
+
+ <xsl:if test="count(HASH) > 0">
+ <xsl:element name="HASH"/>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="col"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index Column -->
+ <xsl:template match="col">
+ <xsl:element name="col">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- SQL QuerySet -->
+ <xsl:template match="sql">
+ <xsl:element name="sql">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@key) > 0">
+ <xsl:attribute name="key"><xsl:value-of select="@key"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@prefixmethod) > 0">
+ <xsl:attribute name="prefixmethod"><xsl:value-of select="@prefixmethod"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+ <xsl:apply-templates select="query"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Query -->
+ <xsl:template match="query">
+ <xsl:element name="query">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/convert-0.1-0.3.xsl b/vendor/adodb/adodb-php/xsl/convert-0.1-0.3.xsl
new file mode 100644
index 0000000..3202dce
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/convert-0.1-0.3.xsl
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.3</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table|schema/sql"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:variable name="table_name" select="@name"/>
+
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="$table_name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="field"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="constraint"/>
+
+ <xsl:apply-templates select="../index[@table=$table_name]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Field -->
+ <xsl:template match="field">
+ <xsl:element name="field">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="@type"/></xsl:attribute>
+
+ <xsl:if test="string-length(@size) > 0">
+ <xsl:attribute name="size"><xsl:value-of select="@size"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="string-length(@opts) = 0"/>
+ <xsl:when test="@opts = 'UNSIGNED'">
+ <xsl:element name="UNSIGNED"/>
+ </xsl:when>
+ <xsl:when test="contains(@opts,'UNSIGNED')">
+ <xsl:attribute name="opts">
+ <xsl:value-of select="concat(substring-before(@opts,'UNSIGNED'),substring-after(@opts,'UNSIGNED'))"/>
+ </xsl:attribute>
+ <xsl:element name="UNSIGNED"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="opts"><xsl:value-of select="@opts"/></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(PRIMARY) > 0">
+ <xsl:element name="PRIMARY"/>
+ </xsl:when>
+ <xsl:when test="count(KEY) > 0">
+ <xsl:element name="KEY"/>
+ </xsl:when>
+ <xsl:when test="count(NOTNULL) > 0">
+ <xsl:element name="NOTNULL"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(AUTO) > 0">
+ <xsl:element name="AUTO"/>
+ </xsl:when>
+ <xsl:when test="count(AUTOINCREMENT) > 0">
+ <xsl:element name="AUTOINCREMENT"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(DEFAULT) > 0">
+ <xsl:element name="DEFAULT">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFAULT[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFDATE) > 0">
+ <xsl:element name="DEFDATE">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFDATE[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFTIMESTAMP) > 0">
+ <xsl:element name="DEFTIMESTAMP">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFTIMESTAMP[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:if test="count(NOQUOTE) > 0">
+ <xsl:element name="NOQUOTE"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="constraint"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Constraint -->
+ <xsl:template match="constraint">
+ <xsl:element name="constraint">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index -->
+ <xsl:template match="index">
+ <xsl:element name="index">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:if test="count(CLUSTERED) > 0">
+ <xsl:element name="CLUSTERED"/>
+ </xsl:if>
+
+ <xsl:if test="count(BITMAP) > 0">
+ <xsl:element name="BITMAP"/>
+ </xsl:if>
+
+ <xsl:if test="count(UNIQUE) > 0">
+ <xsl:element name="UNIQUE"/>
+ </xsl:if>
+
+ <xsl:if test="count(FULLTEXT) > 0">
+ <xsl:element name="FULLTEXT"/>
+ </xsl:if>
+
+ <xsl:if test="count(HASH) > 0">
+ <xsl:element name="HASH"/>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="col"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index Column -->
+ <xsl:template match="col">
+ <xsl:element name="col">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- SQL QuerySet -->
+ <xsl:template match="sql">
+ <xsl:element name="sql">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@key) > 0">
+ <xsl:attribute name="key"><xsl:value-of select="@key"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@prefixmethod) > 0">
+ <xsl:attribute name="prefixmethod"><xsl:value-of select="@prefixmethod"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+ <xsl:apply-templates select="query"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Query -->
+ <xsl:template match="query">
+ <xsl:element name="query">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/convert-0.2-0.1.xsl b/vendor/adodb/adodb-php/xsl/convert-0.2-0.1.xsl
new file mode 100644
index 0000000..6398e3e
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/convert-0.2-0.1.xsl
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.1</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table|schema/sql"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:variable name="table_name" select="@name"/>
+
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="$table_name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="field"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="constraint"/>
+
+ </xsl:element>
+
+ <xsl:apply-templates select="index"/>
+ </xsl:template>
+
+ <!-- Field -->
+ <xsl:template match="field">
+ <xsl:element name="field">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="@type"/></xsl:attribute>
+
+ <xsl:if test="string-length(@size) > 0">
+ <xsl:attribute name="size"><xsl:value-of select="@size"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(PRIMARY) > 0">
+ <xsl:element name="PRIMARY"/>
+ </xsl:when>
+ <xsl:when test="count(KEY) > 0">
+ <xsl:element name="KEY"/>
+ </xsl:when>
+ <xsl:when test="count(NOTNULL) > 0">
+ <xsl:element name="NOTNULL"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(AUTO) > 0">
+ <xsl:element name="AUTO"/>
+ </xsl:when>
+ <xsl:when test="count(AUTOINCREMENT) > 0">
+ <xsl:element name="AUTOINCREMENT"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(DEFAULT) > 0">
+ <xsl:element name="DEFAULT">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFAULT[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFDATE) > 0">
+ <xsl:element name="DEFDATE">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFDATE[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFTIMESTAMP) > 0">
+ <xsl:element name="DEFDTIMESTAMP">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFTIMESTAMP[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:if test="count(NOQUOTE) > 0">
+ <xsl:element name="NOQUOTE"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="constraint"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Constraint -->
+ <xsl:template match="constraint">
+ <xsl:element name="constraint">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index -->
+ <xsl:template match="index">
+ <xsl:element name="index">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ <xsl:attribute name="table"><xsl:value-of select="../@name"/></xsl:attribute>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:if test="count(CLUSTERED) > 0">
+ <xsl:element name="CLUSTERED"/>
+ </xsl:if>
+
+ <xsl:if test="count(BITMAP) > 0">
+ <xsl:element name="BITMAP"/>
+ </xsl:if>
+
+ <xsl:if test="count(UNIQUE) > 0">
+ <xsl:element name="UNIQUE"/>
+ </xsl:if>
+
+ <xsl:if test="count(FULLTEXT) > 0">
+ <xsl:element name="FULLTEXT"/>
+ </xsl:if>
+
+ <xsl:if test="count(HASH) > 0">
+ <xsl:element name="HASH"/>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="col"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index Column -->
+ <xsl:template match="col">
+ <xsl:element name="col">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- SQL QuerySet -->
+ <xsl:template match="sql">
+ <xsl:element name="sql">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@key) > 0">
+ <xsl:attribute name="key"><xsl:value-of select="@key"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@prefixmethod) > 0">
+ <xsl:attribute name="prefixmethod"><xsl:value-of select="@prefixmethod"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+ <xsl:apply-templates select="query"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Query -->
+ <xsl:template match="query">
+ <xsl:element name="query">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/convert-0.2-0.3.xsl b/vendor/adodb/adodb-php/xsl/convert-0.2-0.3.xsl
new file mode 100644
index 0000000..9e1f2ae
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/convert-0.2-0.3.xsl
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.3</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table|schema/sql"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="field"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="constraint"/>
+
+ <xsl:apply-templates select="opt"/>
+
+ <xsl:apply-templates select="index"/>
+
+ <xsl:apply-templates select="data"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Field -->
+ <xsl:template match="field">
+ <xsl:element name="field">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="@type"/></xsl:attribute>
+
+ <xsl:if test="string-length(@size) > 0">
+ <xsl:attribute name="size"><xsl:value-of select="@size"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="string-length(@opts) = 0">
+ <xsl:if test="count(UNSIGNED) > 0">
+ <xsl:element name="UNSIGNED"/>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="@opts = 'UNSIGNED'">
+ <xsl:element name="UNSIGNED"/>
+ </xsl:when>
+ <xsl:when test="contains(@opts,'UNSIGNED')">
+ <xsl:attribute name="opts">
+ <xsl:value-of select="concat(substring-before(@opts,'UNSIGNED'),substring-after(@opts,'UNSIGNED'))"/>
+ </xsl:attribute>
+ <xsl:element name="UNSIGNED"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="opts"><xsl:value-of select="@opts"/></xsl:attribute>
+ <xsl:if test="count(UNSIGNED) > 0">
+ <xsl:element name="UNSIGNED"/>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:choose>
+ <xsl:when test="count(PRIMARY) > 0">
+ <xsl:element name="PRIMARY"/>
+ </xsl:when>
+ <xsl:when test="count(KEY) > 0">
+ <xsl:element name="KEY"/>
+ </xsl:when>
+ <xsl:when test="count(NOTNULL) > 0">
+ <xsl:element name="NOTNULL"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(AUTO) > 0">
+ <xsl:element name="AUTO"/>
+ </xsl:when>
+ <xsl:when test="count(AUTOINCREMENT) > 0">
+ <xsl:element name="AUTOINCREMENT"/>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="count(DEFAULT) > 0">
+ <xsl:element name="DEFAULT">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFAULT[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFDATE) > 0">
+ <xsl:element name="DEFDATE">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFDATE[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="count(DEFTIMESTAMP) > 0">
+ <xsl:element name="DEFTIMESTAMP">
+ <xsl:attribute name="value">
+ <xsl:value-of select="DEFTIMESTAMP[1]/@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:if test="count(NOQUOTE) > 0">
+ <xsl:element name="NOQUOTE"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="constraint"/>
+
+ <xsl:apply-templates select="opt"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Constraint -->
+ <xsl:template match="constraint">
+ <xsl:element name="constraint">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Opt -->
+ <xsl:template match="opt">
+ <xsl:element name="opt">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index -->
+ <xsl:template match="index">
+ <xsl:element name="index">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:if test="count(CLUSTERED) > 0">
+ <xsl:element name="CLUSTERED"/>
+ </xsl:if>
+
+ <xsl:if test="count(BITMAP) > 0">
+ <xsl:element name="BITMAP"/>
+ </xsl:if>
+
+ <xsl:if test="count(UNIQUE) > 0">
+ <xsl:element name="UNIQUE"/>
+ </xsl:if>
+
+ <xsl:if test="count(FULLTEXT) > 0">
+ <xsl:element name="FULLTEXT"/>
+ </xsl:if>
+
+ <xsl:if test="count(HASH) > 0">
+ <xsl:element name="HASH"/>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="count(DROP) > 0">
+ <xsl:element name="DROP"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="col"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Index Column -->
+ <xsl:template match="col">
+ <xsl:element name="col">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- SQL QuerySet -->
+ <xsl:template match="sql">
+ <xsl:element name="sql">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@key) > 0">
+ <xsl:attribute name="key"><xsl:value-of select="@key"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@prefixmethod) > 0">
+ <xsl:attribute name="prefixmethod"><xsl:value-of select="@prefixmethod"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:apply-templates select="query"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Query -->
+ <xsl:template match="query">
+ <xsl:element name="query">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Data -->
+ <xsl:template match="data">
+ <xsl:element name="data">
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:apply-templates select="row"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Data Row -->
+ <xsl:template match="row">
+ <xsl:element name="row">
+ <xsl:apply-templates select="f"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Data Field -->
+ <xsl:template match="f">
+ <xsl:element name="f">
+ <xsl:if test="string-length(@name) > 0">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/remove-0.2.xsl b/vendor/adodb/adodb-php/xsl/remove-0.2.xsl
new file mode 100644
index 0000000..c82c3ad
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/remove-0.2.xsl
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:comment>
+Uninstallation Schema
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.2</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table">
+ <xsl:sort select="position()" data-type="number" order="descending"/>
+ </xsl:apply-templates>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:if test="count(DROP) = 0">
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:element name="DROP"/>
+ </xsl:element>
+ </xsl:if>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/adodb/adodb-php/xsl/remove-0.3.xsl b/vendor/adodb/adodb-php/xsl/remove-0.3.xsl
new file mode 100644
index 0000000..4b1cd02
--- /dev/null
+++ b/vendor/adodb/adodb-php/xsl/remove-0.3.xsl
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+ <xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
+
+ <!-- Schema -->
+ <xsl:template match="/">
+ <xsl:comment>
+ADODB XMLSchema
+http://adodb-xmlschema.sourceforge.net
+</xsl:comment>
+
+ <xsl:comment>
+Uninstallation Schema
+</xsl:comment>
+
+ <xsl:element name="schema">
+ <xsl:attribute name="version">0.3</xsl:attribute>
+
+ <xsl:apply-templates select="schema/table">
+ <xsl:sort select="position()" data-type="number" order="descending"/>
+ </xsl:apply-templates>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- Table -->
+ <xsl:template match="table">
+ <xsl:if test="count(DROP) = 0">
+ <xsl:element name="table">
+ <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
+
+ <xsl:if test="string-length(@platform) > 0">
+ <xsl:attribute name="platform"><xsl:value-of select="@platform"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="string-length(@version) > 0">
+ <xsl:attribute name="version"><xsl:value-of select="@version"/></xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="descr[1]"/>
+
+ <xsl:element name="DROP"/>
+ </xsl:element>
+ </xsl:if>
+ </xsl:template>
+
+ <!-- Description -->
+ <xsl:template match="descr">
+ <xsl:element name="descr">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet> \ No newline at end of file
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000..9dbae1b
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit8fd54980ab492e3fe0292af79bf4050d::getLoader();
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000..fce8549
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+<?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;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..9526c1f
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,32 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Backend' => $baseDir . '/includes/class.backend.php',
+ 'ContentSecurityPolicy' => $baseDir . '/includes/class.csp.php',
+ 'Cookie' => $baseDir . '/includes/class.gpc.php',
+ 'Database' => $baseDir . '/includes/class.database.php',
+ 'FSTpl' => $baseDir . '/includes/class.tpl.php',
+ 'Filters' => $baseDir . '/includes/class.gpc.php',
+ 'FlySprayI18N' => $baseDir . '/includes/i18n.inc.php',
+ 'Flyspray' => $baseDir . '/includes/class.flyspray.php',
+ 'Get' => $baseDir . '/includes/class.gpc.php',
+ 'GithubProvider' => $baseDir . '/includes/GithubProvider.php',
+ 'Jabber' => $baseDir . '/includes/class.jabber2.php',
+ 'Notifications' => $baseDir . '/includes/class.notify.php',
+ 'Post' => $baseDir . '/includes/class.gpc.php',
+ 'Project' => $baseDir . '/includes/class.project.php',
+ 'Req' => $baseDir . '/includes/class.gpc.php',
+ 'Securimage' => $vendorDir . '/dapphp/securimage/securimage.php',
+ 'Securimage_Color' => $vendorDir . '/dapphp/securimage/securimage.php',
+ 'TextFormatter' => $baseDir . '/includes/class.tpl.php',
+ 'Tpl' => $baseDir . '/includes/class.tpl.php',
+ 'Url' => $baseDir . '/includes/class.tpl.php',
+ 'User' => $baseDir . '/includes/class.user.php',
+ 'effort' => $baseDir . '/includes/class.effort.php',
+ 'recaptcha' => $baseDir . '/includes/class.recaptcha.php',
+);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
new file mode 100644
index 0000000..7691bad
--- /dev/null
+++ b/vendor/composer/autoload_files.php
@@ -0,0 +1,13 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'bf9f5270ae66ac6fa0290b4bf47867b7' => $vendorDir . '/adodb/adodb-php/adodb.inc.php',
+ '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+ '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
+ 'd1c03ca96ded033379de1f19323a3063' => $baseDir . '/includes/utf8.inc.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..86e09c4
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,12 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
+ 'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
+ 'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000..ff54b64
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,12 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
+ 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'),
+ 'Flyspray\\' => array($baseDir . '/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..546e9d6
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit8fd54980ab492e3fe0292af79bf4050d
+{
+ 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('ComposerAutoloaderInit8fd54980ab492e3fe0292af79bf4050d', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+ spl_autoload_unregister(array('ComposerAutoloaderInit8fd54980ab492e3fe0292af79bf4050d', '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\ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::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\ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequire8fd54980ab492e3fe0292af79bf4050d($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequire8fd54980ab492e3fe0292af79bf4050d($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..187a8ee
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,103 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d
+{
+ public static $files = array (
+ 'bf9f5270ae66ac6fa0290b4bf47867b7' => __DIR__ . '/..' . '/adodb/adodb-php/adodb.inc.php',
+ '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+ '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
+ 'd1c03ca96ded033379de1f19323a3063' => __DIR__ . '/../..' . '/includes/utf8.inc.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'S' =>
+ array (
+ 'Symfony\\Component\\EventDispatcher\\' => 34,
+ ),
+ 'L' =>
+ array (
+ 'League\\OAuth2\\Client\\' => 21,
+ ),
+ 'F' =>
+ array (
+ 'Flyspray\\' => 9,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Symfony\\Component\\EventDispatcher\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
+ ),
+ 'League\\OAuth2\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/league/oauth2-client/src',
+ ),
+ 'Flyspray\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/src',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'H' =>
+ array (
+ 'HTMLPurifier' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
+ ),
+ ),
+ 'G' =>
+ array (
+ 'Guzzle\\Tests' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzle/guzzle/tests',
+ ),
+ 'Guzzle' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzle/guzzle/src',
+ ),
+ ),
+ );
+
+ public static $classMap = array (
+ 'Backend' => __DIR__ . '/../..' . '/includes/class.backend.php',
+ 'ContentSecurityPolicy' => __DIR__ . '/../..' . '/includes/class.csp.php',
+ 'Cookie' => __DIR__ . '/../..' . '/includes/class.gpc.php',
+ 'Database' => __DIR__ . '/../..' . '/includes/class.database.php',
+ 'FSTpl' => __DIR__ . '/../..' . '/includes/class.tpl.php',
+ 'Filters' => __DIR__ . '/../..' . '/includes/class.gpc.php',
+ 'FlySprayI18N' => __DIR__ . '/../..' . '/includes/i18n.inc.php',
+ 'Flyspray' => __DIR__ . '/../..' . '/includes/class.flyspray.php',
+ 'Get' => __DIR__ . '/../..' . '/includes/class.gpc.php',
+ 'GithubProvider' => __DIR__ . '/../..' . '/includes/GithubProvider.php',
+ 'Jabber' => __DIR__ . '/../..' . '/includes/class.jabber2.php',
+ 'Notifications' => __DIR__ . '/../..' . '/includes/class.notify.php',
+ 'Post' => __DIR__ . '/../..' . '/includes/class.gpc.php',
+ 'Project' => __DIR__ . '/../..' . '/includes/class.project.php',
+ 'Req' => __DIR__ . '/../..' . '/includes/class.gpc.php',
+ 'Securimage' => __DIR__ . '/..' . '/dapphp/securimage/securimage.php',
+ 'Securimage_Color' => __DIR__ . '/..' . '/dapphp/securimage/securimage.php',
+ 'TextFormatter' => __DIR__ . '/../..' . '/includes/class.tpl.php',
+ 'Tpl' => __DIR__ . '/../..' . '/includes/class.tpl.php',
+ 'Url' => __DIR__ . '/../..' . '/includes/class.tpl.php',
+ 'User' => __DIR__ . '/../..' . '/includes/class.user.php',
+ 'effort' => __DIR__ . '/../..' . '/includes/class.effort.php',
+ 'recaptcha' => __DIR__ . '/../..' . '/includes/class.recaptcha.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::$prefixesPsr0;
+ $loader->classMap = ComposerStaticInit8fd54980ab492e3fe0292af79bf4050d::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000..bb8aa71
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,473 @@
+[
+ {
+ "name": "adodb/adodb-php",
+ "version": "v5.20.14",
+ "version_normalized": "5.20.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ADOdb/ADOdb.git",
+ "reference": "a32113c6b3e4e2cb8ae6b49752527d5668a9d7a6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ADOdb/ADOdb/zipball/a32113c6b3e4e2cb8ae6b49752527d5668a9d7a6",
+ "reference": "a32113c6b3e4e2cb8ae6b49752527d5668a9d7a6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "time": "2019-01-05T23:16:44+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "adodb.inc.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "LGPL-2.1-or-later"
+ ],
+ "authors": [
+ {
+ "name": "John Lim",
+ "role": "Author",
+ "email": "jlim@natsoft.com"
+ },
+ {
+ "name": "Damien Regad",
+ "role": "Current maintainer"
+ },
+ {
+ "name": "Mark Newnham",
+ "role": "Developer"
+ }
+ ],
+ "description": "ADOdb is a PHP database abstraction layer library",
+ "homepage": "http://adodb.org/",
+ "keywords": [
+ "abstraction",
+ "database",
+ "layer",
+ "library",
+ "php"
+ ]
+ },
+ {
+ "name": "dapphp/securimage",
+ "version": "3.6.6",
+ "version_normalized": "3.6.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dapphp/securimage.git",
+ "reference": "6eea2798f56540fa88356c98f282d6391a72be15"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dapphp/securimage/zipball/6eea2798f56540fa88356c98f282d6391a72be15",
+ "reference": "6eea2798f56540fa88356c98f282d6391a72be15",
+ "shasum": ""
+ },
+ "require": {
+ "ext-gd": "*",
+ "php": ">=5.2.0"
+ },
+ "suggest": {
+ "ext-pdo": "For database storage support",
+ "ext-pdo_mysql": "For MySQL database support",
+ "ext-pdo_sqlite": "For SQLite3 database support"
+ },
+ "time": "2017-11-21T02:29:19+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "securimage.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "authors": [
+ {
+ "name": "Drew Phillips",
+ "email": "drew@drew-phillips.com"
+ }
+ ],
+ "description": "PHP CAPTCHA Library",
+ "homepage": "https://www.phpcaptcha.org",
+ "keywords": [
+ "captcha",
+ "security"
+ ]
+ },
+ {
+ "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": "guzzle/guzzle",
+ "version": "v3.9.3",
+ "version_normalized": "3.9.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle3.git",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.3",
+ "symfony/event-dispatcher": "~2.1"
+ },
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "monolog/monolog": "~1.0",
+ "phpunit/phpunit": "3.7.*",
+ "psr/log": "~1.0",
+ "symfony/class-loader": "~2.1",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+ "time": "2015-03-18T18:23:50+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "abandoned": "guzzlehttp/guzzle"
+ },
+ {
+ "name": "jamiebicknell/Sparkline",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jamiebicknell/Sparkline.git",
+ "reference": "852a799b0c7e09aeba668e939e85ccea5af1fa1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jamiebicknell/Sparkline/zipball/852a799b0c7e09aeba668e939e85ccea5af1fa1b",
+ "reference": "852a799b0c7e09aeba668e939e85ccea5af1fa1b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "time": "2017-12-29T18:37:51+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jamie Bicknell",
+ "homepage": "http://www.jamiebicknell.com"
+ }
+ ],
+ "description": "PHP script to generate sparklines",
+ "homepage": "http://github.com/jamiebicknell/Sparkline",
+ "keywords": [
+ "gd",
+ "php",
+ "sparkline",
+ "sparklines"
+ ],
+ "support": {
+ "source": "https://github.com/jamiebicknell/Sparkline/tree/1.0.1",
+ "issues": "https://github.com/jamiebicknell/Sparkline/issues"
+ }
+ },
+ {
+ "name": "league/oauth2-client",
+ "version": "0.12.1",
+ "version_normalized": "0.12.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/oauth2-client.git",
+ "reference": "670ec6e743f5c95441263440afcdabc3fc720547"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/670ec6e743f5c95441263440afcdabc3fc720547",
+ "reference": "670ec6e743f5c95441263440afcdabc3fc720547",
+ "shasum": ""
+ },
+ "require": {
+ "guzzle/guzzle": "~3.7",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "mockery/mockery": "~0.9",
+ "phpunit/phpunit": "~4.0",
+ "satooshi/php-coveralls": "0.6.*",
+ "squizlabs/php_codesniffer": "~2.0"
+ },
+ "time": "2015-06-20T16:06:31+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "League\\OAuth2\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Bilbie",
+ "email": "hello@alexbilbie.com",
+ "homepage": "http://www.alexbilbie.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "OAuth 2.0 Client Library",
+ "keywords": [
+ "Authentication",
+ "SSO",
+ "authorization",
+ "identity",
+ "idp",
+ "oauth",
+ "oauth2",
+ "single sign on"
+ ]
+ },
+ {
+ "name": "swiftmailer/swiftmailer",
+ "version": "v5.4.12",
+ "version_normalized": "5.4.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swiftmailer/swiftmailer.git",
+ "reference": "181b89f18a90f8925ef805f950d47a7190e9b950"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950",
+ "reference": "181b89f18a90f8925ef805f950d47a7190e9b950",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.1",
+ "symfony/phpunit-bridge": "~3.2"
+ },
+ "time": "2018-07-31T09:26:32+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "lib/swift_required.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Corbyn"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Swiftmailer, free feature-rich PHP mailer",
+ "homepage": "https://swiftmailer.symfony.com",
+ "keywords": [
+ "email",
+ "mail",
+ "mailer"
+ ]
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v2.8.50",
+ "version_normalized": "2.8.50.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0",
+ "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^2.0.5|~3.0.0",
+ "symfony/dependency-injection": "~2.6|~3.0.0",
+ "symfony/expression-language": "~2.6|~3.0.0",
+ "symfony/stopwatch": "~2.3|~3.0.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "time": "2018-11-21T14:20:20+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "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 EventDispatcher Component",
+ "homepage": "https://symfony.com"
+ }
+]
diff --git a/vendor/dapphp/securimage/.gitattributes b/vendor/dapphp/securimage/.gitattributes
new file mode 100644
index 0000000..c3c14a0
--- /dev/null
+++ b/vendor/dapphp/securimage/.gitattributes
@@ -0,0 +1,5 @@
+/examples/ export-ignore
+/captcha.html export-ignore
+/config.inc.php.SAMPLE export-ignore
+/example_form.ajax.php export-ignore
+/example_form.php export-ignore
diff --git a/vendor/dapphp/securimage/AHGBold.ttf b/vendor/dapphp/securimage/AHGBold.ttf
new file mode 100644
index 0000000..764b23d
--- /dev/null
+++ b/vendor/dapphp/securimage/AHGBold.ttf
Binary files differ
diff --git a/vendor/dapphp/securimage/LICENSE.txt b/vendor/dapphp/securimage/LICENSE.txt
new file mode 100644
index 0000000..889bc2c
--- /dev/null
+++ b/vendor/dapphp/securimage/LICENSE.txt
@@ -0,0 +1,25 @@
+COPYRIGHT:
+ Copyright (c) 2011 Drew Phillips
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/vendor/dapphp/securimage/README.FONT.txt b/vendor/dapphp/securimage/README.FONT.txt
new file mode 100644
index 0000000..d4770de
--- /dev/null
+++ b/vendor/dapphp/securimage/README.FONT.txt
@@ -0,0 +1,12 @@
+AHGBold.ttf is used by Securimage under the following license:
+
+Alte Haas Grotesk is a typeface that look like an helvetica printed in an old Muller-Brockmann Book.
+
+These fonts are freeware and can be distributed as long as they are
+together with this text file.
+
+I would appreciate very much to see what you have done with it anyway.
+
+yann le coroller
+www.yannlecoroller.com
+yann@lecoroller.com \ No newline at end of file
diff --git a/vendor/dapphp/securimage/README.md b/vendor/dapphp/securimage/README.md
new file mode 100644
index 0000000..e935ea2
--- /dev/null
+++ b/vendor/dapphp/securimage/README.md
@@ -0,0 +1,244 @@
+## Name:
+
+**Securimage** - A PHP class for creating captcha images and audio with many options.
+
+## Version:
+
+**3.6.6**
+
+## Author:
+
+Drew Phillips <drew@drew-phillips.com>
+
+## Download:
+
+The latest version can always be found at [phpcaptcha.org](https://www.phpcaptcha.org)
+
+## Documentation:
+
+Online documentation of the class, methods, and variables can be found
+at http://www.phpcaptcha.org/Securimage_Docs/
+
+## Requirements:
+
+* PHP 5.2 or greater
+* GD 2.0
+* FreeType (Required, for TTF fonts)
+* PDO (if using Sqlite, MySQL, or PostgreSQL)
+
+## Synopsis:
+
+**Within your HTML form**
+
+ <form method="post" action="">
+ .. form elements
+
+ <div>
+ <?php
+ require_once 'securimage.php';
+ echo Securimage::getCaptchaHtml();
+ ?>
+ </div>
+ </form>
+
+
+**Within your PHP form processor**
+
+ require_once 'securimage.php';
+
+ // Code Validation
+
+ $image = new Securimage();
+ if ($image->check($_POST['captcha_code']) == true) {
+ echo "Correct!";
+ } else {
+ echo "Sorry, wrong code.";
+ }
+
+## Description:
+
+What is **Securimage**?
+
+Securimage is a PHP class that is used to generate and validate CAPTCHA images.
+
+The classes uses an existing PHP session or creates its own if none is found to
+store the CAPTCHA code. In addition, a database can be used instead of
+session storage.
+
+Variables within the class are used to control the style and display of the
+image. The class uses TTF fonts and effects for strengthening the security of
+the image.
+
+It also creates audible codes which are played for visually impared users.
+
+## UPGRADE NOTICE:
+
+**3.6.3 and below:**
+Securimage 3.6.4 fixed a XSS vulnerability in example_form.ajax.php. It is
+recommended to upgrade to the latest version or delete example_form.ajax.php
+from the securimage directory on your website.
+
+**3.6.2 and above:**
+
+If you are upgrading to 3.6.2 or greater *AND* are using database storage,
+the table structure has changed in 3.6.2 adding an audio_data column for
+storing audio files in the database in order to support HTTP range
+requests. Delete your tables and have Securimage recreate them or see
+the function createDatabaseTables() in securimage.php for the new structure
+depending on which database backend you are using and alter the tables as
+needed. If using SQLite, just overwrite your existing securimage.sq3 file
+with the one from this release.
+
+*If you are not using database tables for storage, ignore this notice.*
+
+## Copyright:
+Script
+ Copyright (c) 2016 Drew Phillips
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+## Licenses:
+
+**WavFile.php**
+
+ The WavFile.php class used in Securimage by Drew Phillips and Paul Voegler
+ is used under the BSD License. See WavFile.php for details.
+ Many thanks to Paul Voegler (http://www.voegler.eu/) for contributing to
+ Securimage.
+Script
+---------------------------------------------------------------------------
+
+**Flash code for Securimage**
+
+Flash code created by Age Bosma & Mario Romero (animario@hotmail.com)
+Many thanks for releasing this to the project!
+
+---------------------------------------------------------------------------
+
+**HKCaptcha**
+
+Portions of Securimage contain code from Han-Kwang Nienhuys' PHP captcha
+
+ Han-Kwang Nienhuys' PHP captcha
+ Copyright June 2007
+
+ This copyright message and attribution must be preserved upon
+ modification. Redistribution under other licenses is expressly allowed.
+ Other licenses include GPL 2 or higher, BSD, and non-free licenses.
+ The original, unrestricted version can be obtained from
+ http://www.lagom.nl/linux/hkcaptcha/
+
+---------------------------------------------------------------------------
+
+**AHGBold.ttf**
+
+ AHGBold.ttf (AlteHaasGroteskBold.ttf) font was created by Yann Le Coroller
+ and is distributed as freeware.
+
+ Alte Haas Grotesk is a typeface that look like an helvetica printed in an
+ old Muller-Brockmann Book.
+
+ These fonts are freeware and can be distributed as long as they are
+ together with this text file.
+
+ I would appreciate very much to see what you have done with it anyway.
+
+ yann le coroller
+ www.yannlecoroller.com
+ yann@lecoroller.com
+
+---------------------------------------------------------------------------
+
+**PopForge Flash Library**
+
+Portions of securimage_play.swf use the PopForge flash library for playing audio
+
+ /**
+ * Copyright(C) 2007 Andre Michelle and Joa Ebert
+ *
+ * PopForge is an ActionScript3 code sandbox developed by Andre Michelle
+ * and Joa Ebert
+ * http://sandbox.popforge.de
+ *
+ * PopforgeAS3Audio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PopforgeAS3Audio 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ */
+
+--------------------------------------------------------------------------
+
+**Graphics**
+
+Some graphics used are from the Humility Icon Pack by WorLord
+
+ License: GNU/GPL (http://findicons.com/pack/1723/humility)
+ http://findicons.com/icon/192558/gnome_volume_control
+ http://findicons.com/icon/192562/gtk_refresh
+
+--------------------------------------------------------------------------
+
+
+**Background noise sound files are from SoundJay.com**
+
+http://www.soundjay.com/tos.html
+
+ All sound effects on this website are created by us and protected under
+ the copyright laws, international treaty provisions and other applicable
+ laws. By downloading sounds, music or any material from this site implies
+ that you have read and accepted these terms and conditions:
+
+ Sound Effects
+ You are allowed to use the sounds free of charge and royalty free in your
+ projects (such as films, videos, games, presentations, animations, stage
+ plays, radio plays, audio books, apps) be it for commercial or
+ non-commercial purposes.
+
+ But you are NOT allowed to
+ - post the sounds (as sound effects or ringtones) on any website for
+ others to download, copy or use
+ - use them as a raw material to create sound effects or ringtones that
+ you will sell, distribute or offer for downloading
+ - sell, re-sell, license or re-license the sounds (as individual sound
+ effects or as a sound effects library) to anyone else
+ - claim the sounds as yours
+ - link directly to individual sound files
+ - distribute the sounds in apps or computer programs that are clearly
+ sound related in nature (such as sound machine, sound effect
+ generator, ringtone maker, funny sounds app, sound therapy app, etc.)
+ or in apps or computer programs that use the sounds as the program's
+ sound resource library for other people's use (such as animation
+ creator, digital book creator, song maker software, etc.). If you are
+ developing such computer programs, contact us for licensing options.
+
+ If you use the sound effects, please consider giving us a credit and
+ linking back to us but it's not required.
+
diff --git a/vendor/dapphp/securimage/README.txt b/vendor/dapphp/securimage/README.txt
new file mode 100644
index 0000000..57898cd
--- /dev/null
+++ b/vendor/dapphp/securimage/README.txt
@@ -0,0 +1,222 @@
+NAME:
+
+ Securimage - A PHP class for creating captcha images and audio with many options.
+
+VERSION:
+
+ 3.6.6
+
+AUTHOR:
+
+ Drew Phillips <drew@drew-phillips.com>
+
+DOWNLOAD:
+
+ The latest version can always be
+ found at http://www.phpcaptcha.org
+
+DOCUMENTATION:
+
+ Online documentation of the class, methods, and variables can
+ be found at http://www.phpcaptcha.org/Securimage_Docs/
+
+REQUIREMENTS:
+
+ PHP 5.2 or greater
+ GD 2.0
+ FreeType (Required, for TTF fonts)
+ PDO (if using Sqlite, MySQL, or PostgreSQL)
+
+SYNOPSIS:
+
+ require_once 'securimage.php';
+
+ **Within your HTML form**
+
+ <form method="post" action="">
+ .. form elements
+
+ <div>
+ <?php echo Securimage::getCaptchaHtml() ?>
+ </div>
+ </form>
+
+
+ **Within your PHP form processor**
+
+ // Code Validation
+
+ $image = new Securimage();
+ if ($image->check($_POST['captcha_code']) == true) {
+ echo "Correct!";
+ } else {
+ echo "Sorry, wrong code.";
+ }
+
+DESCRIPTION:
+
+ What is Securimage?
+
+ Securimage is a PHP class that is used to generate and validate CAPTCHA
+ images.
+
+ The classes uses an existing PHP session or creates its own if
+ none is found to store the CAPTCHA code. In addition, a database can be
+ used instead of session storage.
+
+ Variables within the class are used to control the style and display of
+ the image. The class uses TTF fonts and effects for strengthening the
+ security of the image.
+
+ It also creates audible codes which are played for visually impared users.
+
+UPGRADE NOTICE:
+ 3.6.3 and below:
+ Securimage 3.6.4 fixed a XSS vulnerability in example_form.ajax.php. It is
+ recommended to upgrade to the latest version or delete example_form.ajax.php
+ from the securimage directory on your website.
+
+ 3.6.2 and above:
+ If you are upgrading to 3.6.2 or greater AND are using database storage,
+ the table structure has changed in 3.6.2 adding an audio_data column for
+ storing audio files in the database in order to support HTTP range
+ requests. Delete your tables and have Securimage recreate them or see
+ the function createDatabaseTables() in securimage.php for the new structure
+ depending on which database backend you are using and alter the tables as
+ needed. If using SQLite, just overwrite your existing securimage.sq3 file
+ with the one from this release.
+
+ If you are not using database tables for storage, ignore this notice.
+
+COPYRIGHT:
+
+ Copyright (c) 2016 Drew Phillips
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+LICENSES:
+
+ The WavFile.php class used in Securimage by Drew Phillips and Paul Voegler
+ is used under the BSD License. See WavFile.php for details.
+ Many thanks to Paul Voegler (http://www.voegler.eu/) for contributing to
+ Securimage.
+
+ ---------------------------------------------------------------------------
+ Flash code created by Age Bosma & Mario Romero (animario@hotmail.com)
+ Many thanks for releasing this to the project!
+
+ ---------------------------------------------------------------------------
+ Portions of Securimage contain code from Han-Kwang Nienhuys' PHP captcha
+
+ Han-Kwang Nienhuys' PHP captcha
+ Copyright June 2007
+
+ This copyright message and attribution must be preserved upon
+ modification. Redistribution under other licenses is expressly allowed.
+ Other licenses include GPL 2 or higher, BSD, and non-free licenses.
+ The original, unrestricted version can be obtained from
+ http://www.lagom.nl/linux/hkcaptcha/
+
+ ---------------------------------------------------------------------------
+ AHGBold.ttf (AlteHaasGroteskBold.ttf) font was created by Yann Le Coroller
+ and is distributed as freeware.
+
+ Alte Haas Grotesk is a typeface that look like an helvetica printed in an
+ old Muller-Brockmann Book.
+
+ These fonts are freeware and can be distributed as long as they are
+ together with this text file.
+
+ I would appreciate very much to see what you have done with it anyway.
+
+ yann le coroller
+ www.yannlecoroller.com
+ yann@lecoroller.com
+
+ ---------------------------------------------------------------------------
+ Portions of securimage_play.swf use the PopForge flash library for
+ playing audio
+
+ /**
+ * Copyright(C) 2007 Andre Michelle and Joa Ebert
+ *
+ * PopForge is an ActionScript3 code sandbox developed by Andre Michelle
+ * and Joa Ebert
+ * http://sandbox.popforge.de
+ *
+ * PopforgeAS3Audio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PopforgeAS3Audio 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ */
+
+ --------------------------------------------------------------------------
+ Some graphics used are from the Humility Icon Pack by WorLord
+
+ License: GNU/GPL (http://findicons.com/pack/1723/humility)
+ http://findicons.com/icon/192558/gnome_volume_control
+ http://findicons.com/icon/192562/gtk_refresh
+
+ --------------------------------------------------------------------------
+ Background noise sound files are from SoundJay.com
+ http://www.soundjay.com/tos.html
+
+ All sound effects on this website are created by us and protected under
+ the copyright laws, international treaty provisions and other applicable
+ laws. By downloading sounds, music or any material from this site implies
+ that you have read and accepted these terms and conditions:
+
+ Sound Effects
+ You are allowed to use the sounds free of charge and royalty free in your
+ projects (such as films, videos, games, presentations, animations, stage
+ plays, radio plays, audio books, apps) be it for commercial or
+ non-commercial purposes.
+
+ But you are NOT allowed to
+ - post the sounds (as sound effects or ringtones) on any website for
+ others to download, copy or use
+ - use them as a raw material to create sound effects or ringtones that
+ you will sell, distribute or offer for downloading
+ - sell, re-sell, license or re-license the sounds (as individual sound
+ effects or as a sound effects library) to anyone else
+ - claim the sounds as yours
+ - link directly to individual sound files
+ - distribute the sounds in apps or computer programs that are clearly
+ sound related in nature (such as sound machine, sound effect
+ generator, ringtone maker, funny sounds app, sound therapy app, etc.)
+ or in apps or computer programs that use the sounds as the program's
+ sound resource library for other people's use (such as animation
+ creator, digital book creator, song maker software, etc.). If you are
+ developing such computer programs, contact us for licensing options.
+
+ If you use the sound effects, please consider giving us a credit and
+ linking back to us but it's not required.
+
diff --git a/vendor/dapphp/securimage/WavFile.php b/vendor/dapphp/securimage/WavFile.php
new file mode 100644
index 0000000..8702d22
--- /dev/null
+++ b/vendor/dapphp/securimage/WavFile.php
@@ -0,0 +1,1913 @@
+<?php
+
+// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
+
+/**
+* Project: PHPWavUtils: Classes for creating, reading, and manipulating WAV files in PHP<br />
+* File: WavFile.php<br />
+*
+* Copyright (c) 2014, Drew Phillips
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+* - Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* - Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+*
+* Any modifications to the library should be indicated clearly in the source code
+* to inform users that the changes are not a part of the original software.<br /><br />
+*
+* @copyright 2014 Drew Phillips
+* @author Drew Phillips <drew@drew-phillips.com>
+* @author Paul Voegler <http://www.voegler.eu/>
+* @version 1.1.1 (Sep 2015)
+* @package PHPWavUtils
+* @license BSD License
+*
+* Changelog:
+* 1.1.1 (09/08/2015)
+* - Fix degrade() method to call filter correctly (Rasmus Lerdorf)
+*
+* 1.1 (02/8/2014)
+* - Add method setIgnoreChunkSizes() to allow reading of wav data with bogus chunk sizes set.
+* This allows streamed wav data to be processed where the chunk sizes were not known when
+* writing the header. Instead calculates the chunk sizes automatically.
+* - Add simple volume filter to attenuate or amplify the audio signal.
+*
+* 1.0 (10/2/2012)
+* - Fix insertSilence() creating invalid block size
+*
+* 1.0 RC1 (4/20/2012)
+* - Initial release candidate
+* - Supports 8, 16, 24, 32 bit PCM, 32-bit IEEE FLOAT, Extensible Format
+* - Support for 18 channels of audio
+* - Ability to read an offset from a file to reduce memory footprint with large files
+* - Single-pass audio filter processing
+* - Highly accurate and efficient mix and normalization filters (http://www.voegler.eu/pub/audio/)
+* - Utility filters for degrading audio, and inserting silence
+*
+* 0.6 (4/12/2012)
+* - Support 8, 16, 24, 32 bit and PCM float (Paul Voegler)
+* - Add normalize filter, misc improvements and fixes (Paul Voegler)
+* - Normalize parameters to filter() to use filter constants as array indices
+* - Add option to mix filter to loop the target file if the source is longer
+*
+* 0.5 (4/3/2012)
+* - Fix binary pack routine (Paul Voegler)
+* - Add improved mixing function (Paul Voegler)
+*
+*/
+
+class WavFile
+{
+ /*%******************************************************************************************%*/
+ // Class constants
+
+ /** @var int Filter flag for mixing two files */
+ const FILTER_MIX = 0x01;
+
+ /** @var int Filter flag for normalizing audio data */
+ const FILTER_NORMALIZE = 0x02;
+
+ /** @var int Filter flag for degrading audio data */
+ const FILTER_DEGRADE = 0x04;
+
+ /** @var int Filter flag for amplifying or attenuating audio data. */
+ const FILTER_VOLUME = 0x08;
+
+ /** @var int Maximum number of channels */
+ const MAX_CHANNEL = 18;
+
+ /** @var int Maximum sample rate */
+ const MAX_SAMPLERATE = 192000;
+
+ /** Channel Locations for ChannelMask */
+ const SPEAKER_DEFAULT = 0x000000;
+ const SPEAKER_FRONT_LEFT = 0x000001;
+ const SPEAKER_FRONT_RIGHT = 0x000002;
+ const SPEAKER_FRONT_CENTER = 0x000004;
+ const SPEAKER_LOW_FREQUENCY = 0x000008;
+ const SPEAKER_BACK_LEFT = 0x000010;
+ const SPEAKER_BACK_RIGHT = 0x000020;
+ const SPEAKER_FRONT_LEFT_OF_CENTER = 0x000040;
+ const SPEAKER_FRONT_RIGHT_OF_CENTER = 0x000080;
+ const SPEAKER_BACK_CENTER = 0x000100;
+ const SPEAKER_SIDE_LEFT = 0x000200;
+ const SPEAKER_SIDE_RIGHT = 0x000400;
+ const SPEAKER_TOP_CENTER = 0x000800;
+ const SPEAKER_TOP_FRONT_LEFT = 0x001000;
+ const SPEAKER_TOP_FRONT_CENTER = 0x002000;
+ const SPEAKER_TOP_FRONT_RIGHT = 0x004000;
+ const SPEAKER_TOP_BACK_LEFT = 0x008000;
+ const SPEAKER_TOP_BACK_CENTER = 0x010000;
+ const SPEAKER_TOP_BACK_RIGHT = 0x020000;
+ const SPEAKER_ALL = 0x03FFFF;
+
+ /** @var int PCM Audio Format */
+ const WAVE_FORMAT_PCM = 0x0001;
+
+ /** @var int IEEE FLOAT Audio Format */
+ const WAVE_FORMAT_IEEE_FLOAT = 0x0003;
+
+ /** @var int EXTENSIBLE Audio Format - actual audio format defined by SubFormat */
+ const WAVE_FORMAT_EXTENSIBLE = 0xFFFE;
+
+ /** @var string PCM Audio Format SubType - LE hex representation of GUID {00000001-0000-0010-8000-00AA00389B71} */
+ const WAVE_SUBFORMAT_PCM = "0100000000001000800000aa00389b71";
+
+ /** @var string IEEE FLOAT Audio Format SubType - LE hex representation of GUID {00000003-0000-0010-8000-00AA00389B71} */
+ const WAVE_SUBFORMAT_IEEE_FLOAT = "0300000000001000800000aa00389b71";
+
+
+ /*%******************************************************************************************%*/
+ // Properties
+
+ /** @var array Log base modifier lookup table for a given threshold (in 0.05 steps) used by normalizeSample.
+ * Adjusts the slope (1st derivative) of the log function at the threshold to 1 for a smooth transition
+ * from linear to logarithmic amplitude output. */
+ protected static $LOOKUP_LOGBASE = array(
+ 2.513, 2.667, 2.841, 3.038, 3.262,
+ 3.520, 3.819, 4.171, 4.589, 5.093,
+ 5.711, 6.487, 7.483, 8.806, 10.634,
+ 13.302, 17.510, 24.970, 41.155, 96.088
+ );
+
+ /** @var int The actual physical file size */
+ protected $_actualSize;
+
+ /** @var int The size of the file in RIFF header */
+ protected $_chunkSize;
+
+ /** @var int The size of the "fmt " chunk */
+ protected $_fmtChunkSize;
+
+ /** @var int The size of the extended "fmt " data */
+ protected $_fmtExtendedSize;
+
+ /** @var int The size of the "fact" chunk */
+ protected $_factChunkSize;
+
+ /** @var int Size of the data chunk */
+ protected $_dataSize;
+
+ /** @var int Size of the data chunk in the opened wav file */
+ protected $_dataSize_fp;
+
+ /** @var bool Does _dataSize really reflect strlen($_samples)? Case when a wav file is read with readData = false */
+ protected $_dataSize_valid;
+
+ /** @var int Starting offset of data chunk */
+ protected $_dataOffset;
+
+ /** @var int The audio format - WavFile::WAVE_FORMAT_* */
+ protected $_audioFormat;
+
+ /** @var int|string|null The audio subformat - WavFile::WAVE_SUBFORMAT_* */
+ protected $_audioSubFormat;
+
+ /** @var int Number of channels in the audio file */
+ protected $_numChannels;
+
+ /** @var int The channel mask */
+ protected $_channelMask;
+
+ /** @var int Samples per second */
+ protected $_sampleRate;
+
+ /** @var int Number of bits per sample */
+ protected $_bitsPerSample;
+
+ /** @var int Number of valid bits per sample */
+ protected $_validBitsPerSample;
+
+ /** @var int NumChannels * BitsPerSample/8 */
+ protected $_blockAlign;
+
+ /** @var int Number of sample blocks */
+ protected $_numBlocks;
+
+ /** @var int Bytes per second */
+ protected $_byteRate;
+
+ /** @var bool Ignore chunk sizes when reading wav data (useful when reading data from a stream where chunk sizes contain dummy values) */
+ protected $_ignoreChunkSizes;
+
+ /** @var string Binary string of samples */
+ protected $_samples;
+
+ /** @var resource|null The file pointer used for reading wavs from file or memory */
+ protected $_fp;
+
+
+ /*%******************************************************************************************%*/
+ // Special methods
+
+ /**
+ * WavFile Constructor.
+ *
+ * <code>
+ * $wav1 = new WavFile(2, 44100, 16); // new wav with 2 channels, at 44100 samples/sec and 16 bits per sample
+ * $wav2 = new WavFile('./audio/sound.wav'); // open and read wav file
+ * </code>
+ *
+ * @param string|int $numChannelsOrFileName (Optional) If string, the filename of the wav file to open. The number of channels otherwise. Defaults to 1.
+ * @param int|bool $sampleRateOrReadData (Optional) If opening a file and boolean, decides whether to read the data chunk or not. Defaults to true. The sample rate in samples per second otherwise. 8000 = standard telephone, 16000 = wideband telephone, 32000 = FM radio and 44100 = CD quality. Defaults to 8000.
+ * @param int $bitsPerSample (Optional) The number of bits per sample. Has to be 8, 16 or 24 for PCM audio or 32 for IEEE FLOAT audio. 8 = telephone, 16 = CD and 24 or 32 = studio quality. Defaults to 8.
+ * @throws WavFormatException
+ * @throws WavFileException
+ */
+ public function __construct($numChannelsOrFileName = null, $sampleRateOrReadData = null, $bitsPerSample = null)
+ {
+ $this->_actualSize = 44;
+ $this->_chunkSize = 36;
+ $this->_fmtChunkSize = 16;
+ $this->_fmtExtendedSize = 0;
+ $this->_factChunkSize = 0;
+ $this->_dataSize = 0;
+ $this->_dataSize_fp = 0;
+ $this->_dataSize_valid = true;
+ $this->_dataOffset = 44;
+ $this->_audioFormat = self::WAVE_FORMAT_PCM;
+ $this->_audioSubFormat = null;
+ $this->_numChannels = 1;
+ $this->_channelMask = self::SPEAKER_DEFAULT;
+ $this->_sampleRate = 8000;
+ $this->_bitsPerSample = 8;
+ $this->_validBitsPerSample = 8;
+ $this->_blockAlign = 1;
+ $this->_numBlocks = 0;
+ $this->_byteRate = 8000;
+ $this->_ignoreChunkSizes = false;
+ $this->_samples = '';
+ $this->_fp = null;
+
+
+ if (is_string($numChannelsOrFileName)) {
+ $this->openWav($numChannelsOrFileName, is_bool($sampleRateOrReadData) ? $sampleRateOrReadData : true);
+
+ } else {
+ $this->setNumChannels(is_null($numChannelsOrFileName) ? 1 : $numChannelsOrFileName)
+ ->setSampleRate(is_null($sampleRateOrReadData) ? 8000 : $sampleRateOrReadData)
+ ->setBitsPerSample(is_null($bitsPerSample) ? 8 : $bitsPerSample);
+ }
+ }
+
+ public function __destruct() {
+ if (is_resource($this->_fp)) $this->closeWav();
+ }
+
+ public function __clone() {
+ $this->_fp = null;
+ }
+
+ /**
+ * Output the wav file headers and data.
+ *
+ * @return string The encoded file.
+ */
+ public function __toString()
+ {
+ return $this->makeHeader() .
+ $this->getDataSubchunk();
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Static methods
+
+ /**
+ * Unpacks a single binary sample to numeric value.
+ *
+ * @param string $sampleBinary (Required) The sample to decode.
+ * @param int $bitDepth (Optional) The bits per sample to decode. If omitted, derives it from the length of $sampleBinary.
+ * @return int|float|null The numeric sample value. Float for 32-bit samples. Returns null for unsupported bit depths.
+ */
+ public static function unpackSample($sampleBinary, $bitDepth = null)
+ {
+ if ($bitDepth === null) {
+ $bitDepth = strlen($sampleBinary) * 8;
+ }
+
+ switch ($bitDepth) {
+ case 8:
+ // unsigned char
+ return ord($sampleBinary);
+
+ case 16:
+ // signed short, little endian
+ $data = unpack('v', $sampleBinary);
+ $sample = $data[1];
+ if ($sample >= 0x8000) {
+ $sample -= 0x10000;
+ }
+ return $sample;
+
+ case 24:
+ // 3 byte packed signed integer, little endian
+ $data = unpack('C3', $sampleBinary);
+ $sample = $data[1] | ($data[2] << 8) | ($data[3] << 16);
+ if ($sample >= 0x800000) {
+ $sample -= 0x1000000;
+ }
+ return $sample;
+
+ case 32:
+ // 32-bit float
+ $data = unpack('f', $sampleBinary);
+ return $data[1];
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Packs a single numeric sample to binary.
+ *
+ * @param int|float $sample (Required) The sample to encode. Has to be within valid range for $bitDepth. Float values only for 32 bits.
+ * @param int $bitDepth (Required) The bits per sample to encode with.
+ * @return string|null The encoded binary sample. Returns null for unsupported bit depths.
+ */
+ public static function packSample($sample, $bitDepth)
+ {
+ switch ($bitDepth) {
+ case 8:
+ // unsigned char
+ return chr($sample);
+
+ case 16:
+ // signed short, little endian
+ if ($sample < 0) {
+ $sample += 0x10000;
+ }
+ return pack('v', $sample);
+
+ case 24:
+ // 3 byte packed signed integer, little endian
+ if ($sample < 0) {
+ $sample += 0x1000000;
+ }
+ return pack('C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
+
+ case 32:
+ // 32-bit float
+ return pack('f', $sample);
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Unpacks a binary sample block to numeric values.
+ *
+ * @param string $sampleBlock (Required) The binary sample block (all channels).
+ * @param int $bitDepth (Required) The bits per sample to decode.
+ * @param int $numChannels (Optional) The number of channels to decode. If omitted, derives it from the length of $sampleBlock and $bitDepth.
+ * @return array The sample values as an array of integers of floats for 32 bits. First channel is array index 1.
+ */
+ public static function unpackSampleBlock($sampleBlock, $bitDepth, $numChannels = null) {
+ $sampleBytes = $bitDepth / 8;
+ if ($numChannels === null) {
+ $numChannels = strlen($sampleBlock) / $sampleBytes;
+ }
+
+ $samples = array();
+ for ($i = 0; $i < $numChannels; $i++) {
+ $sampleBinary = substr($sampleBlock, $i * $sampleBytes, $sampleBytes);
+ $samples[$i + 1] = self::unpackSample($sampleBinary, $bitDepth);
+ }
+
+ return $samples;
+ }
+
+ /**
+ * Packs an array of numeric channel samples to a binary sample block.
+ *
+ * @param array $samples (Required) The array of channel sample values. Expects float values for 32 bits and integer otherwise.
+ * @param int $bitDepth (Required) The bits per sample to encode with.
+ * @return string The encoded binary sample block.
+ */
+ public static function packSampleBlock($samples, $bitDepth) {
+ $sampleBlock = '';
+ foreach($samples as $sample) {
+ $sampleBlock .= self::packSample($sample, $bitDepth);
+ }
+
+ return $sampleBlock;
+ }
+
+ /**
+ * Normalizes a float audio sample. Maximum input range assumed for compression is [-2, 2].
+ * See http://www.voegler.eu/pub/audio/ for more information.
+ *
+ * @param float $sampleFloat (Required) The float sample to normalize.
+ * @param float $threshold (Required) The threshold or gain factor for normalizing the amplitude. <ul>
+ * <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br />
+ * A value of 1 in effect means no normalization (and results in clipping). </li>
+ * <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br />
+ * A factor of 2 (-2) is about 6dB reduction in volume.</li>
+ * <li> [0, 1) - (open inverval - not including 1) - The threshold
+ * above which amplitudes are comressed logarithmically. <br />
+ * e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li>
+ * <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold
+ * above which amplitudes are comressed linearly. <br />
+ * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul>
+ * @return float The normalized sample.
+ **/
+ public static function normalizeSample($sampleFloat, $threshold) {
+ // apply positive gain
+ if ($threshold >= 1) {
+ return $sampleFloat * $threshold;
+ }
+
+ // apply negative gain
+ if ($threshold <= -1) {
+ return $sampleFloat / -$threshold;
+ }
+
+ $sign = $sampleFloat < 0 ? -1 : 1;
+ $sampleAbs = abs($sampleFloat);
+
+ // logarithmic compression
+ if ($threshold >= 0 && $threshold < 1 && $sampleAbs > $threshold) {
+ $loga = self::$LOOKUP_LOGBASE[(int)($threshold * 20)]; // log base modifier
+ return $sign * ($threshold + (1 - $threshold) * log(1 + $loga * ($sampleAbs - $threshold) / (2 - $threshold)) / log(1 + $loga));
+ }
+
+ // linear compression
+ $thresholdAbs = abs($threshold);
+ if ($threshold > -1 && $threshold < 0 && $sampleAbs > $thresholdAbs) {
+ return $sign * ($thresholdAbs + (1 - $thresholdAbs) / (2 - $thresholdAbs) * ($sampleAbs - $thresholdAbs));
+ }
+
+ // else ?
+ return $sampleFloat;
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Getter and Setter methods for properties
+
+ public function getActualSize() {
+ return $this->_actualSize;
+ }
+
+ /** @param int $actualSize */
+ protected function setActualSize($actualSize = null) {
+ if (is_null($actualSize)) {
+ $this->_actualSize = 8 + $this->_chunkSize; // + "RIFF" header (ID + size)
+ } else {
+ $this->_actualSize = $actualSize;
+ }
+
+ return $this;
+ }
+
+ public function getChunkSize() {
+ return $this->_chunkSize;
+ }
+
+ /** @param int $chunkSize */
+ protected function setChunkSize($chunkSize = null) {
+ if (is_null($chunkSize)) {
+ $this->_chunkSize = 4 + // "WAVE" chunk
+ 8 + $this->_fmtChunkSize + // "fmt " subchunk
+ ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) + // "fact" subchunk
+ 8 + $this->_dataSize + // "data" subchunk
+ ($this->_dataSize & 1); // padding byte
+ } else {
+ $this->_chunkSize = $chunkSize;
+ }
+
+ $this->setActualSize();
+
+ return $this;
+ }
+
+ public function getFmtChunkSize() {
+ return $this->_fmtChunkSize;
+ }
+
+ /** @param int $fmtChunkSize */
+ protected function setFmtChunkSize($fmtChunkSize = null) {
+ if (is_null($fmtChunkSize)) {
+ $this->_fmtChunkSize = 16 + $this->_fmtExtendedSize;
+ } else {
+ $this->_fmtChunkSize = $fmtChunkSize;
+ }
+
+ $this->setChunkSize() // implicit setActualSize()
+ ->setDataOffset();
+
+ return $this;
+ }
+
+ public function getFmtExtendedSize() {
+ return $this->_fmtExtendedSize;
+ }
+
+ /** @param int $fmtExtendedSize */
+ protected function setFmtExtendedSize($fmtExtendedSize = null) {
+ if (is_null($fmtExtendedSize)) {
+ if ($this->_audioFormat == self::WAVE_FORMAT_EXTENSIBLE) {
+ $this->_fmtExtendedSize = 2 + 22; // extension size for WAVE_FORMAT_EXTENSIBLE
+ } elseif ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
+ $this->_fmtExtendedSize = 2 + 0; // empty extension
+ } else {
+ $this->_fmtExtendedSize = 0; // no extension, only for WAVE_FORMAT_PCM
+ }
+ } else {
+ $this->_fmtExtendedSize = $fmtExtendedSize;
+ }
+
+ $this->setFmtChunkSize(); // implicit setSize(), setActualSize(), setDataOffset()
+
+ return $this;
+ }
+
+ public function getFactChunkSize() {
+ return $this->_factChunkSize;
+ }
+
+ /** @param int $factChunkSize */
+ protected function setFactChunkSize($factChunkSize = null) {
+ if (is_null($factChunkSize)) {
+ if ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
+ $this->_factChunkSize = 4;
+ } else {
+ $this->_factChunkSize = 0;
+ }
+ } else {
+ $this->_factChunkSize = $factChunkSize;
+ }
+
+ $this->setChunkSize() // implicit setActualSize()
+ ->setDataOffset();
+
+ return $this;
+ }
+
+ public function getDataSize() {
+ return $this->_dataSize;
+ }
+
+ /** @param int $dataSize */
+ protected function setDataSize($dataSize = null) {
+ if (is_null($dataSize)) {
+ $this->_dataSize = strlen($this->_samples);
+ } else {
+ $this->_dataSize = $dataSize;
+ }
+
+ $this->setChunkSize() // implicit setActualSize()
+ ->setNumBlocks();
+ $this->_dataSize_valid = true;
+
+ return $this;
+ }
+
+ public function getDataOffset() {
+ return $this->_dataOffset;
+ }
+
+ /** @param int $dataOffset */
+ protected function setDataOffset($dataOffset = null) {
+ if (is_null($dataOffset)) {
+ $this->_dataOffset = 8 + // "RIFF" header (ID + size)
+ 4 + // "WAVE" chunk
+ 8 + $this->_fmtChunkSize + // "fmt " subchunk
+ ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) + // "fact" subchunk
+ 8; // "data" subchunk
+ } else {
+ $this->_dataOffset = $dataOffset;
+ }
+
+ return $this;
+ }
+
+ public function getAudioFormat() {
+ return $this->_audioFormat;
+ }
+
+ /** @param int $audioFormat */
+ protected function setAudioFormat($audioFormat = null) {
+ if (is_null($audioFormat)) {
+ if (($this->_bitsPerSample <= 16 || $this->_bitsPerSample == 32)
+ && $this->_validBitsPerSample == $this->_bitsPerSample
+ && $this->_channelMask == self::SPEAKER_DEFAULT
+ && $this->_numChannels <= 2) {
+ if ($this->_bitsPerSample <= 16) {
+ $this->_audioFormat = self::WAVE_FORMAT_PCM;
+ } else {
+ $this->_audioFormat = self::WAVE_FORMAT_IEEE_FLOAT;
+ }
+ } else {
+ $this->_audioFormat = self::WAVE_FORMAT_EXTENSIBLE;
+ }
+ } else {
+ $this->_audioFormat = $audioFormat;
+ }
+
+ $this->setAudioSubFormat()
+ ->setFactChunkSize() // implicit setSize(), setActualSize(), setDataOffset()
+ ->setFmtExtendedSize(); // implicit setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
+
+ return $this;
+ }
+
+ public function getAudioSubFormat() {
+ return $this->_audioSubFormat;
+ }
+
+ /** @param int $audioSubFormat */
+ protected function setAudioSubFormat($audioSubFormat = null) {
+ if (is_null($audioSubFormat)) {
+ if ($this->_bitsPerSample == 32) {
+ $this->_audioSubFormat = self::WAVE_SUBFORMAT_IEEE_FLOAT; // 32 bits are IEEE FLOAT in this class
+ } else {
+ $this->_audioSubFormat = self::WAVE_SUBFORMAT_PCM; // 8, 16 and 24 bits are PCM in this class
+ }
+ } else {
+ $this->_audioSubFormat = $audioSubFormat;
+ }
+
+ return $this;
+ }
+
+ public function getNumChannels() {
+ return $this->_numChannels;
+ }
+
+ /** @param int $numChannels */
+ public function setNumChannels($numChannels) {
+ if ($numChannels < 1 || $numChannels > self::MAX_CHANNEL) {
+ throw new WavFileException('Unsupported number of channels. Only up to ' . self::MAX_CHANNEL . ' channels are supported.');
+ } elseif ($this->_samples !== '') {
+ trigger_error('Wav already has sample data. Changing the number of channels does not convert and may corrupt the data.', E_USER_NOTICE);
+ }
+
+ $this->_numChannels = (int)$numChannels;
+
+ $this->setAudioFormat() // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
+ ->setByteRate()
+ ->setBlockAlign(); // implicit setNumBlocks()
+
+ return $this;
+ }
+
+ public function getChannelMask() {
+ return $this->_channelMask;
+ }
+
+ public function setChannelMask($channelMask = self::SPEAKER_DEFAULT) {
+ if ($channelMask != 0) {
+ // count number of set bits - Hamming weight
+ $c = (int)$channelMask;
+ $n = 0;
+ while ($c > 0) {
+ $n += $c & 1;
+ $c >>= 1;
+ }
+ if ($n != $this->_numChannels || (((int)$channelMask | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
+ throw new WavFileException('Invalid channel mask. The number of channels does not match the number of locations in the mask.');
+ }
+ }
+
+ $this->_channelMask = (int)$channelMask;
+
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
+
+ return $this;
+ }
+
+ public function getSampleRate() {
+ return $this->_sampleRate;
+ }
+
+ public function setSampleRate($sampleRate) {
+ if ($sampleRate < 1 || $sampleRate > self::MAX_SAMPLERATE) {
+ throw new WavFileException('Invalid sample rate.');
+ } elseif ($this->_samples !== '') {
+ trigger_error('Wav already has sample data. Changing the sample rate does not convert the data and may yield undesired results.', E_USER_NOTICE);
+ }
+
+ $this->_sampleRate = (int)$sampleRate;
+
+ $this->setByteRate();
+
+ return $this;
+ }
+
+ public function getBitsPerSample() {
+ return $this->_bitsPerSample;
+ }
+
+ public function setBitsPerSample($bitsPerSample) {
+ if (!in_array($bitsPerSample, array(8, 16, 24, 32))) {
+ throw new WavFileException('Unsupported bits per sample. Only 8, 16, 24 and 32 bits are supported.');
+ } elseif ($this->_samples !== '') {
+ trigger_error('Wav already has sample data. Changing the bits per sample does not convert and may corrupt the data.', E_USER_NOTICE);
+ }
+
+ $this->_bitsPerSample = (int)$bitsPerSample;
+
+ $this->setValidBitsPerSample() // implicit setAudioFormat(), setAudioSubFormat(), setFmtChunkSize(), setFactChunkSize(), setSize(), setActualSize(), setDataOffset()
+ ->setByteRate()
+ ->setBlockAlign(); // implicit setNumBlocks()
+
+ return $this;
+ }
+
+ public function getValidBitsPerSample() {
+ return $this->_validBitsPerSample;
+ }
+
+ protected function setValidBitsPerSample($validBitsPerSample = null) {
+ if (is_null($validBitsPerSample)) {
+ $this->_validBitsPerSample = $this->_bitsPerSample;
+ } else {
+ if ($validBitsPerSample < 1 || $validBitsPerSample > $this->_bitsPerSample) {
+ throw new WavFileException('ValidBitsPerSample cannot be greater than BitsPerSample.');
+ }
+ $this->_validBitsPerSample = (int)$validBitsPerSample;
+ }
+
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
+
+ return $this;
+ }
+
+ public function getBlockAlign() {
+ return $this->_blockAlign;
+ }
+
+ /** @param int $blockAlign */
+ protected function setBlockAlign($blockAlign = null) {
+ if (is_null($blockAlign)) {
+ $this->_blockAlign = $this->_numChannels * $this->_bitsPerSample / 8;
+ } else {
+ $this->_blockAlign = $blockAlign;
+ }
+
+ $this->setNumBlocks();
+
+ return $this;
+ }
+
+ public function getNumBlocks()
+ {
+ return $this->_numBlocks;
+ }
+
+ /** @param int $numBlocks */
+ protected function setNumBlocks($numBlocks = null) {
+ if (is_null($numBlocks)) {
+ $this->_numBlocks = (int)($this->_dataSize / $this->_blockAlign); // do not count incomplete sample blocks
+ } else {
+ $this->_numBlocks = $numBlocks;
+ }
+
+ return $this;
+ }
+
+ public function getByteRate() {
+ return $this->_byteRate;
+ }
+
+ /** @param int $byteRate */
+ protected function setByteRate($byteRate = null) {
+ if (is_null($byteRate)) {
+ $this->_byteRate = $this->_sampleRate * $this->_numChannels * $this->_bitsPerSample / 8;
+ } else {
+ $this->_byteRate = $byteRate;
+ }
+
+ return $this;
+ }
+
+ public function getIgnoreChunkSizes()
+ {
+ return $this->_ignoreChunkSizes;
+ }
+
+ public function setIgnoreChunkSizes($ignoreChunkSizes)
+ {
+ $this->_ignoreChunkSizes = (bool)$ignoreChunkSizes;
+ return $this;
+ }
+
+ public function getSamples() {
+ return $this->_samples;
+ }
+
+ public function setSamples(&$samples = '') {
+ if (strlen($samples) % $this->_blockAlign != 0) {
+ throw new WavFileException('Incorrect samples size. Has to be a multiple of BlockAlign.');
+ }
+
+ $this->_samples = $samples;
+
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ return $this;
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Getters
+
+ public function getMinAmplitude()
+ {
+ if ($this->_bitsPerSample == 8) {
+ return 0;
+ } elseif ($this->_bitsPerSample == 32) {
+ return -1.0;
+ } else {
+ return -(1 << ($this->_bitsPerSample - 1));
+ }
+ }
+
+ public function getZeroAmplitude()
+ {
+ if ($this->_bitsPerSample == 8) {
+ return 0x80;
+ } elseif ($this->_bitsPerSample == 32) {
+ return 0.0;
+ } else {
+ return 0;
+ }
+ }
+
+ public function getMaxAmplitude()
+ {
+ if($this->_bitsPerSample == 8) {
+ return 0xFF;
+ } elseif($this->_bitsPerSample == 32) {
+ return 1.0;
+ } else {
+ return (1 << ($this->_bitsPerSample - 1)) - 1;
+ }
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Wave file methods
+
+ /**
+ * Construct a wav header from this object. Includes "fact" chunk if necessary.
+ * http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
+ *
+ * @return string The RIFF header data.
+ */
+ public function makeHeader()
+ {
+ // reset and recalculate
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
+ $this->setNumBlocks();
+
+ // RIFF header
+ $header = pack('N', 0x52494646); // ChunkID - "RIFF"
+ $header .= pack('V', $this->getChunkSize()); // ChunkSize
+ $header .= pack('N', 0x57415645); // Format - "WAVE"
+
+ // "fmt " subchunk
+ $header .= pack('N', 0x666d7420); // SubchunkID - "fmt "
+ $header .= pack('V', $this->getFmtChunkSize()); // SubchunkSize
+ $header .= pack('v', $this->getAudioFormat()); // AudioFormat
+ $header .= pack('v', $this->getNumChannels()); // NumChannels
+ $header .= pack('V', $this->getSampleRate()); // SampleRate
+ $header .= pack('V', $this->getByteRate()); // ByteRate
+ $header .= pack('v', $this->getBlockAlign()); // BlockAlign
+ $header .= pack('v', $this->getBitsPerSample()); // BitsPerSample
+ if($this->getFmtExtendedSize() == 24) {
+ $header .= pack('v', 22); // extension size = 24 bytes, cbSize: 24 - 2 = 22 bytes
+ $header .= pack('v', $this->getValidBitsPerSample()); // ValidBitsPerSample
+ $header .= pack('V', $this->getChannelMask()); // ChannelMask
+ $header .= pack('H32', $this->getAudioSubFormat()); // SubFormat
+ } elseif ($this->getFmtExtendedSize() == 2) {
+ $header .= pack('v', 0); // extension size = 2 bytes, cbSize: 2 - 2 = 0 bytes
+ }
+
+ // "fact" subchunk
+ if ($this->getFactChunkSize() == 4) {
+ $header .= pack('N', 0x66616374); // SubchunkID - "fact"
+ $header .= pack('V', 4); // SubchunkSize
+ $header .= pack('V', $this->getNumBlocks()); // SampleLength (per channel)
+ }
+
+ return $header;
+ }
+
+ /**
+ * Construct wav DATA chunk.
+ *
+ * @return string The DATA header and chunk.
+ */
+ public function getDataSubchunk()
+ {
+ // check preconditions
+ if (!$this->_dataSize_valid) {
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+ }
+
+
+ // create subchunk
+ return pack('N', 0x64617461) . // SubchunkID - "data"
+ pack('V', $this->getDataSize()) . // SubchunkSize
+ $this->_samples . // Subchunk data
+ ($this->getDataSize() & 1 ? chr(0) : ''); // padding byte
+ }
+
+ /**
+ * Save the wav data to a file.
+ *
+ * @param string $filename (Required) The file path to save the wav to.
+ * @throws WavFileException
+ */
+ public function save($filename)
+ {
+ $fp = @fopen($filename, 'w+b');
+ if (!is_resource($fp)) {
+ throw new WavFileException('Failed to open "' . $filename . '" for writing.');
+ }
+
+ fwrite($fp, $this->makeHeader());
+ fwrite($fp, $this->getDataSubchunk());
+ fclose($fp);
+
+ return $this;
+ }
+
+ /**
+ * Reads a wav header and data from a file.
+ *
+ * @param string $filename (Required) The path to the wav file to read.
+ * @param bool $readData (Optional) If true, also read the data chunk.
+ * @throws WavFormatException
+ * @throws WavFileException
+ */
+ public function openWav($filename, $readData = true)
+ {
+ // check preconditions
+ if (!file_exists($filename)) {
+ throw new WavFileException('Failed to open "' . $filename . '". File not found.');
+ } elseif (!is_readable($filename)) {
+ throw new WavFileException('Failed to open "' . $filename . '". File is not readable.');
+ } elseif (is_resource($this->_fp)) {
+ $this->closeWav();
+ }
+
+
+ // open the file
+ $this->_fp = @fopen($filename, 'rb');
+ if (!is_resource($this->_fp)) {
+ throw new WavFileException('Failed to open "' . $filename . '".');
+ }
+
+ // read the file
+ return $this->readWav($readData);
+ }
+
+ /**
+ * Close a with openWav() previously opened wav file or free the buffer of setWavData().
+ * Not necessary if the data has been read (readData = true) already.
+ */
+ public function closeWav() {
+ if (is_resource($this->_fp)) fclose($this->_fp);
+
+ return $this;
+ }
+
+ /**
+ * Set the wav file data and properties from a wav file in a string.
+ *
+ * @param string $data (Required) The wav file data. Passed by reference.
+ * @param bool $free (Optional) True to free the passed $data after copying.
+ * @throws WavFormatException
+ * @throws WavFileException
+ */
+ public function setWavData(&$data, $free = true)
+ {
+ // check preconditions
+ if (is_resource($this->_fp)) $this->closeWav();
+
+
+ // open temporary stream in memory
+ $this->_fp = @fopen('php://memory', 'w+b');
+ if (!is_resource($this->_fp)) {
+ throw new WavFileException('Failed to open memory stream to write wav data. Use openWav() instead.');
+ }
+
+ // prepare stream
+ fwrite($this->_fp, $data);
+ rewind($this->_fp);
+
+ // free the passed data
+ if ($free) $data = null;
+
+ // read the stream like a file
+ return $this->readWav(true);
+ }
+
+ /**
+ * Read wav file from a stream.
+ *
+ * @param bool $readData (Optional) If true, also read the data chunk.
+ * @throws WavFormatException
+ * @throws WavFileException
+ */
+ protected function readWav($readData = true)
+ {
+ if (!is_resource($this->_fp)) {
+ throw new WavFileException('No wav file open. Use openWav() first.');
+ }
+
+ try {
+ $this->readWavHeader();
+ } catch (WavFileException $ex) {
+ $this->closeWav();
+ throw $ex;
+ }
+
+ if ($readData) return $this->readWavData();
+
+ return $this;
+ }
+
+ /**
+ * Parse a wav header.
+ * http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
+ *
+ * @throws WavFormatException
+ * @throws WavFileException
+ */
+ protected function readWavHeader()
+ {
+ if (!is_resource($this->_fp)) {
+ throw new WavFileException('No wav file open. Use openWav() first.');
+ }
+
+ // get actual file size
+ $stat = fstat($this->_fp);
+ $actualSize = $stat['size'];
+
+ $this->_actualSize = $actualSize;
+
+
+ // read the common header
+ $header = fread($this->_fp, 36); // minimum size of the wav header
+ if (strlen($header) < 36) {
+ throw new WavFormatException('Not wav format. Header too short.', 1);
+ }
+
+
+ // check "RIFF" header
+ $RIFF = unpack('NChunkID/VChunkSize/NFormat', $header);
+
+ if ($RIFF['ChunkID'] != 0x52494646) { // "RIFF"
+ throw new WavFormatException('Not wav format. "RIFF" signature missing.', 2);
+ }
+
+ if ($this->getIgnoreChunkSizes()) {
+ $RIFF['ChunkSize'] = $actualSize - 8;
+ } else if ($actualSize - 8 < $RIFF['ChunkSize']) {
+ trigger_error('"RIFF" chunk size does not match actual file size. Found ' . $RIFF['ChunkSize'] . ', expected ' . ($actualSize - 8) . '.', E_USER_NOTICE);
+ $RIFF['ChunkSize'] = $actualSize - 8;
+ }
+
+ if ($RIFF['Format'] != 0x57415645) { // "WAVE"
+ throw new WavFormatException('Not wav format. "RIFF" chunk format is not "WAVE".', 4);
+ }
+
+ $this->_chunkSize = $RIFF['ChunkSize'];
+
+
+ // check common "fmt " subchunk
+ $fmt = unpack('NSubchunkID/VSubchunkSize/vAudioFormat/vNumChannels/'
+ .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
+ substr($header, 12));
+
+ if ($fmt['SubchunkID'] != 0x666d7420) { // "fmt "
+ throw new WavFormatException('Bad wav header. Expected "fmt " subchunk.', 11);
+ }
+
+ if ($fmt['SubchunkSize'] < 16) {
+ throw new WavFormatException('Bad "fmt " subchunk size.', 12);
+ }
+
+ if ( $fmt['AudioFormat'] != self::WAVE_FORMAT_PCM
+ && $fmt['AudioFormat'] != self::WAVE_FORMAT_IEEE_FLOAT
+ && $fmt['AudioFormat'] != self::WAVE_FORMAT_EXTENSIBLE)
+ {
+ throw new WavFormatException('Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
+ }
+
+ if ($fmt['NumChannels'] < 1 || $fmt['NumChannels'] > self::MAX_CHANNEL) {
+ throw new WavFormatException('Invalid number of channels in "fmt " subchunk.', 14);
+ }
+
+ if ($fmt['SampleRate'] < 1 || $fmt['SampleRate'] > self::MAX_SAMPLERATE) {
+ throw new WavFormatException('Invalid sample rate in "fmt " subchunk.', 15);
+ }
+
+ if ( ($fmt['AudioFormat'] == self::WAVE_FORMAT_PCM && !in_array($fmt['BitsPerSample'], array(8, 16, 24)))
+ || ($fmt['AudioFormat'] == self::WAVE_FORMAT_IEEE_FLOAT && $fmt['BitsPerSample'] != 32)
+ || ($fmt['AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE && !in_array($fmt['BitsPerSample'], array(8, 16, 24, 32))))
+ {
+ throw new WavFormatException('Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
+ }
+
+ $blockAlign = $fmt['NumChannels'] * $fmt['BitsPerSample'] / 8;
+ if ($blockAlign != $fmt['BlockAlign']) {
+ trigger_error('Invalid block align in "fmt " subchunk. Found ' . $fmt['BlockAlign'] . ', expected ' . $blockAlign . '.', E_USER_NOTICE);
+ $fmt['BlockAlign'] = $blockAlign;
+ }
+
+ $byteRate = $fmt['SampleRate'] * $blockAlign;
+ if ($byteRate != $fmt['ByteRate']) {
+ trigger_error('Invalid average byte rate in "fmt " subchunk. Found ' . $fmt['ByteRate'] . ', expected ' . $byteRate . '.', E_USER_NOTICE);
+ $fmt['ByteRate'] = $byteRate;
+ }
+
+ $this->_fmtChunkSize = $fmt['SubchunkSize'];
+ $this->_audioFormat = $fmt['AudioFormat'];
+ $this->_numChannels = $fmt['NumChannels'];
+ $this->_sampleRate = $fmt['SampleRate'];
+ $this->_byteRate = $fmt['ByteRate'];
+ $this->_blockAlign = $fmt['BlockAlign'];
+ $this->_bitsPerSample = $fmt['BitsPerSample'];
+
+
+ // read extended "fmt " subchunk data
+ $extendedFmt = '';
+ if ($fmt['SubchunkSize'] > 16) {
+ // possibly handle malformed subchunk without a padding byte
+ $extendedFmt = fread($this->_fp, $fmt['SubchunkSize'] - 16 + ($fmt['SubchunkSize'] & 1)); // also read padding byte
+ if (strlen($extendedFmt) < $fmt['SubchunkSize'] - 16) {
+ throw new WavFormatException('Not wav format. Header too short.', 1);
+ }
+ }
+
+
+ // check extended "fmt " for EXTENSIBLE Audio Format
+ if ($fmt['AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE) {
+ if (strlen($extendedFmt) < 24) {
+ throw new WavFormatException('Invalid EXTENSIBLE "fmt " subchunk size. Found ' . $fmt['SubchunkSize'] . ', expected at least 40.', 19);
+ }
+
+ $extensibleFmt = unpack('vSize/vValidBitsPerSample/VChannelMask/H32SubFormat', substr($extendedFmt, 0, 24));
+
+ if ( $extensibleFmt['SubFormat'] != self::WAVE_SUBFORMAT_PCM
+ && $extensibleFmt['SubFormat'] != self::WAVE_SUBFORMAT_IEEE_FLOAT)
+ {
+ throw new WavFormatException('Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
+ }
+
+ if ( ($extensibleFmt['SubFormat'] == self::WAVE_SUBFORMAT_PCM && !in_array($fmt['BitsPerSample'], array(8, 16, 24)))
+ || ($extensibleFmt['SubFormat'] == self::WAVE_SUBFORMAT_IEEE_FLOAT && $fmt['BitsPerSample'] != 32))
+ {
+ throw new WavFormatException('Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
+ }
+
+ if ($extensibleFmt['Size'] != 22) {
+ trigger_error('Invaid extension size in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
+ $extensibleFmt['Size'] = 22;
+ }
+
+ if ($extensibleFmt['ValidBitsPerSample'] != $fmt['BitsPerSample']) {
+ trigger_error('Invaid or unsupported valid bits per sample in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
+ $extensibleFmt['ValidBitsPerSample'] = $fmt['BitsPerSample'];
+ }
+
+ if ($extensibleFmt['ChannelMask'] != 0) {
+ // count number of set bits - Hamming weight
+ $c = (int)$extensibleFmt['ChannelMask'];
+ $n = 0;
+ while ($c > 0) {
+ $n += $c & 1;
+ $c >>= 1;
+ }
+ if ($n != $fmt['NumChannels'] || (((int)$extensibleFmt['ChannelMask'] | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
+ trigger_error('Invalid channel mask in EXTENSIBLE "fmt " subchunk. The number of channels does not match the number of locations in the mask.', E_USER_NOTICE);
+ $extensibleFmt['ChannelMask'] = 0;
+ }
+ }
+
+ $this->_fmtExtendedSize = strlen($extendedFmt);
+ $this->_validBitsPerSample = $extensibleFmt['ValidBitsPerSample'];
+ $this->_channelMask = $extensibleFmt['ChannelMask'];
+ $this->_audioSubFormat = $extensibleFmt['SubFormat'];
+
+ } else {
+ $this->_fmtExtendedSize = strlen($extendedFmt);
+ $this->_validBitsPerSample = $fmt['BitsPerSample'];
+ $this->_channelMask = 0;
+ $this->_audioSubFormat = null;
+ }
+
+
+ // read additional subchunks until "data" subchunk is found
+ $factSubchunk = array();
+ $dataSubchunk = array();
+
+ while (!feof($this->_fp)) {
+ $subchunkHeader = fread($this->_fp, 8);
+ if (strlen($subchunkHeader) < 8) {
+ throw new WavFormatException('Missing "data" subchunk.', 101);
+ }
+
+ $subchunk = unpack('NSubchunkID/VSubchunkSize', $subchunkHeader);
+
+ if ($subchunk['SubchunkID'] == 0x66616374) { // "fact"
+ // possibly handle malformed subchunk without a padding byte
+ $subchunkData = fread($this->_fp, $subchunk['SubchunkSize'] + ($subchunk['SubchunkSize'] & 1)); // also read padding byte
+ if (strlen($subchunkData) < 4) {
+ throw new WavFormatException('Invalid "fact" subchunk.', 102);
+ }
+
+ $factParams = unpack('VSampleLength', substr($subchunkData, 0, 4));
+ $factSubchunk = array_merge($subchunk, $factParams);
+
+ } elseif ($subchunk['SubchunkID'] == 0x64617461) { // "data"
+ $dataSubchunk = $subchunk;
+
+ break;
+
+ } elseif ($subchunk['SubchunkID'] == 0x7761766C) { // "wavl"
+ throw new WavFormatException('Wave List Chunk ("wavl" subchunk) is not supported.', 106);
+ } else {
+ // skip all other (unknown) subchunks
+ // possibly handle malformed subchunk without a padding byte
+ if ( $subchunk['SubchunkSize'] < 0
+ || fseek($this->_fp, $subchunk['SubchunkSize'] + ($subchunk['SubchunkSize'] & 1), SEEK_CUR) !== 0) { // also skip padding byte
+ throw new WavFormatException('Invalid subchunk (0x' . dechex($subchunk['SubchunkID']) . ') encountered.', 103);
+ }
+ }
+ }
+
+ if (empty($dataSubchunk)) {
+ throw new WavFormatException('Missing "data" subchunk.', 101);
+ }
+
+ // check "data" subchunk
+ $dataOffset = ftell($this->_fp);
+ if ($this->getIgnoreChunkSizes()) {
+ $dataSubchunk['SubchunkSize'] = $actualSize - $dataOffset;
+ } elseif ($dataSubchunk['SubchunkSize'] < 0 || $actualSize - $dataOffset < $dataSubchunk['SubchunkSize']) {
+ trigger_error("Invalid \"data\" subchunk size (found {$dataSubchunk['SubchunkSize']}.", E_USER_NOTICE);
+ $dataSubchunk['SubchunkSize'] = $actualSize - $dataOffset;
+ }
+
+ $this->_dataOffset = $dataOffset;
+ $this->_dataSize = $dataSubchunk['SubchunkSize'];
+ $this->_dataSize_fp = $dataSubchunk['SubchunkSize'];
+ $this->_dataSize_valid = false;
+ $this->_samples = '';
+
+
+ // check "fact" subchunk
+ $numBlocks = (int)($dataSubchunk['SubchunkSize'] / $fmt['BlockAlign']);
+
+ if (empty($factSubchunk)) { // construct fake "fact" subchunk
+ $factSubchunk = array('SubchunkSize' => 0, 'SampleLength' => $numBlocks);
+ }
+
+ if ($factSubchunk['SampleLength'] != $numBlocks) {
+ trigger_error('Invalid sample length in "fact" subchunk.', E_USER_NOTICE);
+ $factSubchunk['SampleLength'] = $numBlocks;
+ }
+
+ $this->_factChunkSize = $factSubchunk['SubchunkSize'];
+ $this->_numBlocks = $factSubchunk['SampleLength'];
+
+
+ return $this;
+
+ }
+
+ /**
+ * Read the wav data from the file into the buffer.
+ *
+ * @param int $dataOffset (Optional) The byte offset to skip before starting to read. Must be a multiple of BlockAlign.
+ * @param int $dataSize (Optional) The size of the data to read in bytes. Must be a multiple of BlockAlign. Defaults to all data.
+ * @throws WavFileException
+ */
+ public function readWavData($dataOffset = 0, $dataSize = null)
+ {
+ // check preconditions
+ if (!is_resource($this->_fp)) {
+ throw new WavFileException('No wav file open. Use openWav() first.');
+ }
+
+ if ($dataOffset < 0 || $dataOffset % $this->getBlockAlign() > 0) {
+ throw new WavFileException('Invalid data offset. Has to be a multiple of BlockAlign.');
+ }
+
+ if (is_null($dataSize)) {
+ $dataSize = $this->_dataSize_fp - ($this->_dataSize_fp % $this->getBlockAlign()); // only read complete blocks
+ } elseif ($dataSize < 0 || $dataSize % $this->getBlockAlign() > 0) {
+ throw new WavFileException('Invalid data size to read. Has to be a multiple of BlockAlign.');
+ }
+
+
+ // skip offset
+ if ($dataOffset > 0 && fseek($this->_fp, $dataOffset, SEEK_CUR) !== 0) {
+ throw new WavFileException('Seeking to data offset failed.');
+ }
+
+ // read data
+ $this->_samples .= fread($this->_fp, $dataSize); // allow appending
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ // close file or memory stream
+ return $this->closeWav();
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Sample manipulation methods
+
+ /**
+ * Return a single sample block from the file.
+ *
+ * @param int $blockNum (Required) The sample block number. Zero based.
+ * @return string|null The binary sample block (all channels). Returns null if the sample block number was out of range.
+ */
+ public function getSampleBlock($blockNum)
+ {
+ // check preconditions
+ if (!$this->_dataSize_valid) {
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+ }
+
+ $offset = $blockNum * $this->_blockAlign;
+ if ($offset + $this->_blockAlign > $this->_dataSize || $offset < 0) {
+ return null;
+ }
+
+
+ // read data
+ return substr($this->_samples, $offset, $this->_blockAlign);
+ }
+
+ /**
+ * Set a single sample block. <br />
+ * Allows to append a sample block.
+ *
+ * @param string $sampleBlock (Required) The binary sample block (all channels).
+ * @param int $blockNum (Required) The sample block number. Zero based.
+ * @throws WavFileException
+ */
+ public function setSampleBlock($sampleBlock, $blockNum)
+ {
+ // check preconditions
+ $blockAlign = $this->_blockAlign;
+ if (!isset($sampleBlock[$blockAlign - 1]) || isset($sampleBlock[$blockAlign])) { // faster than: if (strlen($sampleBlock) != $blockAlign)
+ throw new WavFileException('Incorrect sample block size. Got ' . strlen($sampleBlock) . ', expected ' . $blockAlign . '.');
+ }
+
+ if (!$this->_dataSize_valid) {
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+ }
+
+ $numBlocks = (int)($this->_dataSize / $blockAlign);
+ $offset = $blockNum * $blockAlign;
+ if ($blockNum > $numBlocks || $blockNum < 0) { // allow appending
+ throw new WavFileException('Sample block number is out of range.');
+ }
+
+
+ // replace or append data
+ if ($blockNum == $numBlocks) {
+ // append
+ $this->_samples .= $sampleBlock;
+ $this->_dataSize += $blockAlign;
+ $this->_chunkSize += $blockAlign;
+ $this->_actualSize += $blockAlign;
+ $this->_numBlocks++;
+ } else {
+ // replace
+ for ($i = 0; $i < $blockAlign; ++$i) {
+ $this->_samples[$offset + $i] = $sampleBlock[$i];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a float sample value for a specific sample block and channel number.
+ *
+ * @param int $blockNum (Required) The sample block number to fetch. Zero based.
+ * @param int $channelNum (Required) The channel number within the sample block to fetch. First channel is 1.
+ * @return float|null The float sample value. Returns null if the sample block number was out of range.
+ * @throws WavFileException
+ */
+ public function getSampleValue($blockNum, $channelNum)
+ {
+ // check preconditions
+ if ($channelNum < 1 || $channelNum > $this->_numChannels) {
+ throw new WavFileException('Channel number is out of range.');
+ }
+
+ if (!$this->_dataSize_valid) {
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+ }
+
+ $sampleBytes = $this->_bitsPerSample / 8;
+ $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
+ if ($offset + $sampleBytes > $this->_dataSize || $offset < 0) {
+ return null;
+ }
+
+ // read binary value
+ $sampleBinary = substr($this->_samples, $offset, $sampleBytes);
+
+ // convert binary to value
+ switch ($this->_bitsPerSample) {
+ case 8:
+ // unsigned char
+ return (float)((ord($sampleBinary) - 0x80) / 0x80);
+
+ case 16:
+ // signed short, little endian
+ $data = unpack('v', $sampleBinary);
+ $sample = $data[1];
+ if ($sample >= 0x8000) {
+ $sample -= 0x10000;
+ }
+ return (float)($sample / 0x8000);
+
+ case 24:
+ // 3 byte packed signed integer, little endian
+ $data = unpack('C3', $sampleBinary);
+ $sample = $data[1] | ($data[2] << 8) | ($data[3] << 16);
+ if ($sample >= 0x800000) {
+ $sample -= 0x1000000;
+ }
+ return (float)($sample / 0x800000);
+
+ case 32:
+ // 32-bit float
+ $data = unpack('f', $sampleBinary);
+ return (float)$data[1];
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Sets a float sample value for a specific sample block number and channel. <br />
+ * Converts float values to appropriate integer values and clips properly. <br />
+ * Allows to append samples (in order).
+ *
+ * @param float $sampleFloat (Required) The float sample value to set. Converts float values and clips if necessary.
+ * @param int $blockNum (Required) The sample block number to set or append. Zero based.
+ * @param int $channelNum (Required) The channel number within the sample block to set or append. First channel is 1.
+ * @throws WavFileException
+ */
+ public function setSampleValue($sampleFloat, $blockNum, $channelNum)
+ {
+ // check preconditions
+ if ($channelNum < 1 || $channelNum > $this->_numChannels) {
+ throw new WavFileException('Channel number is out of range.');
+ }
+
+ if (!$this->_dataSize_valid) {
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+ }
+
+ $dataSize = $this->_dataSize;
+ $bitsPerSample = $this->_bitsPerSample;
+ $sampleBytes = $bitsPerSample / 8;
+ $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
+ if (($offset + $sampleBytes > $dataSize && $offset != $dataSize) || $offset < 0) { // allow appending
+ throw new WavFileException('Sample block or channel number is out of range.');
+ }
+
+
+ // convert to value, quantize and clip
+ if ($bitsPerSample == 32) {
+ $sample = $sampleFloat < -1.0 ? -1.0 : ($sampleFloat > 1.0 ? 1.0 : $sampleFloat);
+ } else {
+ $p = 1 << ($bitsPerSample - 1); // 2 to the power of _bitsPerSample divided by 2
+
+ // project and quantize (round) float to integer values
+ $sample = $sampleFloat < 0 ? (int)($sampleFloat * $p - 0.5) : (int)($sampleFloat * $p + 0.5);
+
+ // clip if necessary to [-$p, $p - 1]
+ if ($sample < -$p) {
+ $sample = -$p;
+ } elseif ($sample > $p - 1) {
+ $sample = $p - 1;
+ }
+ }
+
+ // convert to binary
+ switch ($bitsPerSample) {
+ case 8:
+ // unsigned char
+ $sampleBinary = chr($sample + 0x80);
+ break;
+
+ case 16:
+ // signed short, little endian
+ if ($sample < 0) {
+ $sample += 0x10000;
+ }
+ $sampleBinary = pack('v', $sample);
+ break;
+
+ case 24:
+ // 3 byte packed signed integer, little endian
+ if ($sample < 0) {
+ $sample += 0x1000000;
+ }
+ $sampleBinary = pack('C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
+ break;
+
+ case 32:
+ // 32-bit float
+ $sampleBinary = pack('f', $sample);
+ break;
+
+ default:
+ $sampleBinary = null;
+ $sampleBytes = 0;
+ break;
+ }
+
+ // replace or append data
+ if ($offset == $dataSize) {
+ // append
+ $this->_samples .= $sampleBinary;
+ $this->_dataSize += $sampleBytes;
+ $this->_chunkSize += $sampleBytes;
+ $this->_actualSize += $sampleBytes;
+ $this->_numBlocks = (int)($this->_dataSize / $this->_blockAlign);
+ } else {
+ // replace
+ for ($i = 0; $i < $sampleBytes; ++$i) {
+ $this->_samples{$offset + $i} = $sampleBinary{$i};
+ }
+ }
+
+ return $this;
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Audio processing methods
+
+ /**
+ * Run samples through audio processing filters.
+ *
+ * <code>
+ * $wav->filter(
+ * array(
+ * WavFile::FILTER_MIX => array( // Filter for mixing 2 WavFile instances.
+ * 'wav' => $wav2, // (Required) The WavFile to mix into this WhavFile. If no optional arguments are given, can be passed without the array.
+ * 'loop' => true, // (Optional) Loop the selected portion (with warping to the beginning at the end).
+ * 'blockOffset' => 0, // (Optional) Block number to start mixing from.
+ * 'numBlocks' => null // (Optional) Number of blocks to mix in or to select for looping. Defaults to the end or all data for looping.
+ * ),
+ * WavFile::FILTER_NORMALIZE => 0.6, // (Required) Normalization of (mixed) audio samples - see threshold parameter for normalizeSample().
+ * WavFile::FILTER_DEGRADE => 0.9 // (Required) Introduce random noise. The quality relative to the amplitude. 1 = no noise, 0 = max. noise.
+ * WavFile::FILTER_VOLUME => 1.0 // (Required) Amplify or attenuate the audio signal. Beware of clipping when amplifying. Values range from >= 0 - <= 2. 1 = no change in volume; 0.5 = 50% reduction of volume; 1.5 = 150% increase in volume.
+ * ),
+ * 0, // (Optional) The block number of this WavFile to start with.
+ * null // (Optional) The number of blocks to process.
+ * );
+ * </code>
+ *
+ * @param array $filters (Required) An array of 1 or more audio processing filters.
+ * @param int $blockOffset (Optional) The block number to start precessing from.
+ * @param int $numBlocks (Optional) The maximum number of blocks to process.
+ * @throws WavFileException
+ */
+ public function filter($filters, $blockOffset = 0, $numBlocks = null)
+ {
+ // check preconditions
+ $totalBlocks = $this->getNumBlocks();
+ $numChannels = $this->getNumChannels();
+ if (is_null($numBlocks)) $numBlocks = $totalBlocks - $blockOffset;
+
+ if (!is_array($filters) || empty($filters) || $blockOffset < 0 || $blockOffset > $totalBlocks || $numBlocks <= 0) {
+ // nothing to do
+ return $this;
+ }
+
+ // check filtes
+ $filter_mix = false;
+ if (array_key_exists(self::FILTER_MIX, $filters)) {
+ if (!is_array($filters[self::FILTER_MIX])) {
+ // assume the 'wav' parameter
+ $filters[self::FILTER_MIX] = array('wav' => $filters[self::FILTER_MIX]);
+ }
+
+ $mix_wav = @$filters[self::FILTER_MIX]['wav'];
+ if (!($mix_wav instanceof WavFile)) {
+ throw new WavFileException("WavFile to mix is missing or invalid.");
+ } elseif ($mix_wav->getSampleRate() != $this->getSampleRate()) {
+ throw new WavFileException("Sample rate of WavFile to mix does not match.");
+ } else if ($mix_wav->getNumChannels() != $this->getNumChannels()) {
+ throw new WavFileException("Number of channels of WavFile to mix does not match.");
+ }
+
+ $mix_loop = @$filters[self::FILTER_MIX]['loop'];
+ if (is_null($mix_loop)) $mix_loop = false;
+
+ $mix_blockOffset = @$filters[self::FILTER_MIX]['blockOffset'];
+ if (is_null($mix_blockOffset)) $mix_blockOffset = 0;
+
+ $mix_totalBlocks = $mix_wav->getNumBlocks();
+ $mix_numBlocks = @$filters[self::FILTER_MIX]['numBlocks'];
+ if (is_null($mix_numBlocks)) $mix_numBlocks = $mix_loop ? $mix_totalBlocks : $mix_totalBlocks - $mix_blockOffset;
+ $mix_maxBlock = min($mix_blockOffset + $mix_numBlocks, $mix_totalBlocks);
+
+ $filter_mix = true;
+ }
+
+ $filter_normalize = false;
+ if (array_key_exists(self::FILTER_NORMALIZE, $filters)) {
+ $normalize_threshold = @$filters[self::FILTER_NORMALIZE];
+
+ if (!is_null($normalize_threshold) && abs($normalize_threshold) != 1) $filter_normalize = true;
+ }
+
+ $filter_degrade = false;
+ if (array_key_exists(self::FILTER_DEGRADE, $filters)) {
+ $degrade_quality = @$filters[self::FILTER_DEGRADE];
+ if (is_null($degrade_quality)) $degrade_quality = 1;
+
+ if ($degrade_quality >= 0 && $degrade_quality < 1) $filter_degrade = true;
+ }
+
+ $filter_vol = false;
+ if (array_key_exists(self::FILTER_VOLUME, $filters)) {
+ $volume_amount = @$filters[self::FILTER_VOLUME];
+ if (is_null($volume_amount)) $volume_amount = 1;
+
+ if ($volume_amount >= 0 && $volume_amount <= 2 && $volume_amount != 1.0) {
+ $filter_vol = true;
+ }
+ }
+
+
+ // loop through all sample blocks
+ for ($block = 0; $block < $numBlocks; ++$block) {
+ // loop through all channels
+ for ($channel = 1; $channel <= $numChannels; ++$channel) {
+ // read current sample
+ $currentBlock = $blockOffset + $block;
+ $sampleFloat = $this->getSampleValue($currentBlock, $channel);
+
+
+ /************* MIX FILTER ***********************/
+ if ($filter_mix) {
+ if ($mix_loop) {
+ $mixBlock = ($mix_blockOffset + ($block % $mix_numBlocks)) % $mix_totalBlocks;
+ } else {
+ $mixBlock = $mix_blockOffset + $block;
+ }
+
+ if ($mixBlock < $mix_maxBlock) {
+ $sampleFloat += $mix_wav->getSampleValue($mixBlock, $channel);
+ }
+ }
+
+ /************* NORMALIZE FILTER *******************/
+ if ($filter_normalize) {
+ $sampleFloat = $this->normalizeSample($sampleFloat, $normalize_threshold);
+ }
+
+ /************* DEGRADE FILTER *******************/
+ if ($filter_degrade) {
+ $sampleFloat += rand(1000000 * ($degrade_quality - 1), 1000000 * (1 - $degrade_quality)) / 1000000;
+ }
+
+ /************* VOLUME FILTER *******************/
+ if ($filter_vol) {
+ $sampleFloat *= $volume_amount;
+ }
+
+ // write current sample
+ $this->setSampleValue($sampleFloat, $currentBlock, $channel);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Append a wav file to the current wav. <br />
+ * The wav files must have the same sample rate, number of bits per sample, and number of channels.
+ *
+ * @param WavFile $wav (Required) The wav file to append.
+ * @throws WavFileException
+ */
+ public function appendWav(WavFile $wav) {
+ // basic checks
+ if ($wav->getSampleRate() != $this->getSampleRate()) {
+ throw new WavFileException("Sample rate for wav files do not match.");
+ } else if ($wav->getBitsPerSample() != $this->getBitsPerSample()) {
+ throw new WavFileException("Bits per sample for wav files do not match.");
+ } else if ($wav->getNumChannels() != $this->getNumChannels()) {
+ throw new WavFileException("Number of channels for wav files do not match.");
+ }
+
+ $this->_samples .= $wav->_samples;
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ return $this;
+ }
+
+ /**
+ * Mix 2 wav files together. <br />
+ * Both wavs must have the same sample rate and same number of channels.
+ *
+ * @param WavFile $wav (Required) The WavFile to mix.
+ * @param float $normalizeThreshold (Optional) See normalizeSample for an explanation.
+ * @throws WavFileException
+ */
+ public function mergeWav(WavFile $wav, $normalizeThreshold = null) {
+ return $this->filter(array(
+ WavFile::FILTER_MIX => $wav,
+ WavFile::FILTER_NORMALIZE => $normalizeThreshold
+ ));
+ }
+
+ /**
+ * Add silence to the wav file.
+ *
+ * @param float $duration (Optional) How many seconds of silence. If negative, add to the beginning of the file. Defaults to 1s.
+ */
+ public function insertSilence($duration = 1.0)
+ {
+ $numSamples = (int)($this->getSampleRate() * abs($duration));
+ $numChannels = $this->getNumChannels();
+
+ $data = str_repeat(self::packSample($this->getZeroAmplitude(), $this->getBitsPerSample()), $numSamples * $numChannels);
+ if ($duration >= 0) {
+ $this->_samples .= $data;
+ } else {
+ $this->_samples = $data . $this->_samples;
+ }
+
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ return $this;
+ }
+
+ /**
+ * Degrade the quality of the wav file by introducing random noise.
+ *
+ * @param float quality (Optional) The quality relative to the amplitude. 1 = no noise, 0 = max. noise.
+ */
+ public function degrade($quality = 1.0)
+ {
+ return $this->filter(array(
+ self::FILTER_DEGRADE => $quality
+ ));
+ }
+
+ /**
+ * Generate noise at the end of the wav for the specified duration and volume.
+ *
+ * @param float $duration (Optional) Number of seconds of noise to generate.
+ * @param float $percent (Optional) The percentage of the maximum amplitude to use. 100 = full amplitude.
+ */
+ public function generateNoise($duration = 1.0, $percent = 100)
+ {
+ $numChannels = $this->getNumChannels();
+ $numSamples = $this->getSampleRate() * $duration;
+ $minAmp = $this->getMinAmplitude();
+ $maxAmp = $this->getMaxAmplitude();
+ $bitDepth = $this->getBitsPerSample();
+
+ for ($s = 0; $s < $numSamples; ++$s) {
+ if ($bitDepth == 32) {
+ $val = rand(-$percent * 10000, $percent * 10000) / 1000000;
+ } else {
+ $val = rand($minAmp, $maxAmp);
+ $val = (int)($val * $percent / 100);
+ }
+
+ $this->_samples .= str_repeat(self::packSample($val, $bitDepth), $numChannels);
+ }
+
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ return $this;
+ }
+
+ /**
+ * Convert sample data to different bits per sample.
+ *
+ * @param int $bitsPerSample (Required) The new number of bits per sample;
+ * @throws WavFileException
+ */
+ public function convertBitsPerSample($bitsPerSample) {
+ if ($this->getBitsPerSample() == $bitsPerSample) {
+ return $this;
+ }
+
+ $tempWav = new WavFile($this->getNumChannels(), $this->getSampleRate(), $bitsPerSample);
+ $tempWav->filter(
+ array(self::FILTER_MIX => $this),
+ 0,
+ $this->getNumBlocks()
+ );
+
+ $this->setSamples() // implicit setDataSize(), setSize(), setActualSize(), setNumBlocks()
+ ->setBitsPerSample($bitsPerSample); // implicit setValidBitsPerSample(), setAudioFormat(), setAudioSubFormat(), setFmtChunkSize(), setFactChunkSize(), setSize(), setActualSize(), setDataOffset(), setByteRate(), setBlockAlign(), setNumBlocks()
+ $this->_samples = $tempWav->_samples;
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
+
+ return $this;
+ }
+
+
+ /*%******************************************************************************************%*/
+ // Miscellaneous methods
+
+ /**
+ * Output information about the wav object.
+ */
+ public function displayInfo()
+ {
+ $s = "File Size: %u\n"
+ ."Chunk Size: %u\n"
+ ."fmt Subchunk Size: %u\n"
+ ."Extended fmt Size: %u\n"
+ ."fact Subchunk Size: %u\n"
+ ."Data Offset: %u\n"
+ ."Data Size: %u\n"
+ ."Audio Format: %s\n"
+ ."Audio SubFormat: %s\n"
+ ."Channels: %u\n"
+ ."Channel Mask: 0x%s\n"
+ ."Sample Rate: %u\n"
+ ."Bits Per Sample: %u\n"
+ ."Valid Bits Per Sample: %u\n"
+ ."Sample Block Size: %u\n"
+ ."Number of Sample Blocks: %u\n"
+ ."Byte Rate: %uBps\n";
+
+ $s = sprintf($s, $this->getActualSize(),
+ $this->getChunkSize(),
+ $this->getFmtChunkSize(),
+ $this->getFmtExtendedSize(),
+ $this->getFactChunkSize(),
+ $this->getDataOffset(),
+ $this->getDataSize(),
+ $this->getAudioFormat() == self::WAVE_FORMAT_PCM ? 'PCM' : ($this->getAudioFormat() == self::WAVE_FORMAT_IEEE_FLOAT ? 'IEEE FLOAT' : 'EXTENSIBLE'),
+ $this->getAudioSubFormat() == self::WAVE_SUBFORMAT_PCM ? 'PCM' : 'IEEE FLOAT',
+ $this->getNumChannels(),
+ dechex($this->getChannelMask()),
+ $this->getSampleRate(),
+ $this->getBitsPerSample(),
+ $this->getValidBitsPerSample(),
+ $this->getBlockAlign(),
+ $this->getNumBlocks(),
+ $this->getByteRate());
+
+ if (php_sapi_name() == 'cli') {
+ return $s;
+ } else {
+ return nl2br($s);
+ }
+ }
+}
+
+
+/*%******************************************************************************************%*/
+// Exceptions
+
+/**
+ * WavFileException indicates an illegal state or argument in this class.
+ */
+class WavFileException extends Exception {}
+
+/**
+ * WavFormatException indicates a malformed or unsupported wav file header.
+ */
+class WavFormatException extends WavFileException {}
diff --git a/vendor/dapphp/securimage/audio/.htaccess b/vendor/dapphp/securimage/audio/.htaccess
new file mode 100644
index 0000000..4fdb24a
--- /dev/null
+++ b/vendor/dapphp/securimage/audio/.htaccess
@@ -0,0 +1,11 @@
+# Deny access to this folder
+
+# Apache 2.4
+<IfModule mod_authz_core.c>
+ Require all denied
+</IfModule>
+
+# Apache 2.2
+<IfModule !mod_authz_core.c>
+ Deny from all
+</IfModule>
diff --git a/vendor/dapphp/securimage/composer.json b/vendor/dapphp/securimage/composer.json
new file mode 100644
index 0000000..ada7882
--- /dev/null
+++ b/vendor/dapphp/securimage/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "dapphp/securimage",
+ "type": "library",
+ "vesion": "3.6.3",
+ "description": "PHP CAPTCHA Library",
+ "keywords": ["captcha","security"],
+ "homepage": "https://www.phpcaptcha.org",
+ "license": "BSD",
+ "authors": [
+ {
+ "name": "Drew Phillips",
+ "email": "drew@drew-phillips.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.2.0",
+ "ext-gd": "*"
+ },
+ "suggest": {
+ "ext-pdo": "For database storage support",
+ "ext-pdo_mysql": "For MySQL database support",
+ "ext-pdo_sqlite": "For SQLite3 database support"
+ },
+ "autoload": {
+ "classmap": ["securimage.php"]
+ }
+}
diff --git a/vendor/dapphp/securimage/config.inc.php b/vendor/dapphp/securimage/config.inc.php
new file mode 100644
index 0000000..2c83f03
--- /dev/null
+++ b/vendor/dapphp/securimage/config.inc.php
@@ -0,0 +1 @@
+<?php return array("session_name"=>"flyspray"); ?>
diff --git a/vendor/dapphp/securimage/database/.htaccess b/vendor/dapphp/securimage/database/.htaccess
new file mode 100644
index 0000000..4fdb24a
--- /dev/null
+++ b/vendor/dapphp/securimage/database/.htaccess
@@ -0,0 +1,11 @@
+# Deny access to this folder
+
+# Apache 2.4
+<IfModule mod_authz_core.c>
+ Require all denied
+</IfModule>
+
+# Apache 2.2
+<IfModule !mod_authz_core.c>
+ Deny from all
+</IfModule>
diff --git a/vendor/dapphp/securimage/database/index.html b/vendor/dapphp/securimage/database/index.html
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/vendor/dapphp/securimage/database/index.html
@@ -0,0 +1 @@
+
diff --git a/vendor/dapphp/securimage/database/securimage.sq3 b/vendor/dapphp/securimage/database/securimage.sq3
new file mode 100644
index 0000000..a3fcbd7
--- /dev/null
+++ b/vendor/dapphp/securimage/database/securimage.sq3
Binary files differ
diff --git a/vendor/dapphp/securimage/images/audio_icon.png b/vendor/dapphp/securimage/images/audio_icon.png
new file mode 100644
index 0000000..9922ef1
--- /dev/null
+++ b/vendor/dapphp/securimage/images/audio_icon.png
Binary files differ
diff --git a/vendor/dapphp/securimage/images/loading.png b/vendor/dapphp/securimage/images/loading.png
new file mode 100644
index 0000000..1711568
--- /dev/null
+++ b/vendor/dapphp/securimage/images/loading.png
Binary files differ
diff --git a/vendor/dapphp/securimage/images/refresh.png b/vendor/dapphp/securimage/images/refresh.png
new file mode 100644
index 0000000..f5e7d82
--- /dev/null
+++ b/vendor/dapphp/securimage/images/refresh.png
Binary files differ
diff --git a/vendor/dapphp/securimage/securimage.css b/vendor/dapphp/securimage/securimage.css
new file mode 100644
index 0000000..0cffdb9
--- /dev/null
+++ b/vendor/dapphp/securimage/securimage.css
@@ -0,0 +1,41 @@
+@CHARSET "UTF-8";
+
+@-webkit-keyframes rotating /* Safari and Chrome */ {
+ from {
+ -ms-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ to {
+ -ms-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -webkit-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes rotating {
+ from {
+ -ms-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ to {
+ -ms-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -webkit-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+.rotating {
+ -webkit-animation: rotating 1.5s linear infinite;
+ -moz-animation: rotating 1.5s linear infinite;
+ -ms-animation: rotating 1.5s linear infinite;
+ -o-animation: rotating 1.5s linear infinite;
+ animation: rotating 1.5s linear infinite;
+} \ No newline at end of file
diff --git a/vendor/dapphp/securimage/securimage.js b/vendor/dapphp/securimage/securimage.js
new file mode 100644
index 0000000..481e9e6
--- /dev/null
+++ b/vendor/dapphp/securimage/securimage.js
@@ -0,0 +1,252 @@
+/*!
+ * Securimage CAPTCHA Audio Library
+ * https://www.phpcaptcha.org/
+ *
+ * Copyright 2015 phpcaptcha.org
+ * Released under the BSD-3 license
+ * See https://github.com/dapphp/securimage/blob/master/README.md
+ */
+
+var SecurimageAudio = function(options) {
+ this.html5Support = true;
+ this.flashFallback = false;
+ this.captchaId = null;
+ this.playing = false;
+ this.reload = false;
+ this.audioElement = null;
+ this.controlsElement = null;
+ this.playButton = null;
+ this.playButtonImage = null;
+ this.loadingImage = null;
+
+ if (options.audioElement) {
+ this.audioElement = document.getElementById(options.audioElement);
+ }
+ if (options.controlsElement) {
+ this.controlsElement = document.getElementById(options.controlsElement);
+ }
+
+ this.init();
+}
+
+SecurimageAudio.prototype.init = function() {
+ var ua = navigator.userAgent.toLowerCase();
+ var ieVer = (ua.indexOf('msie') != -1) ? parseInt(ua.split('msie')[1]) : false;
+ // ie 11+ detection
+ if (!ieVer && null != (ieVer = ua.match(/trident\/.*rv:(\d+\.\d+)/)))
+ ieVer = parseInt(ieVer[1]);
+
+ var objAu = this.audioElement.getElementsByTagName('object');
+ if (objAu.length > 0) {
+ objAu = objAu[0];
+ } else {
+ objAu = null;
+ }
+
+ if (ieVer) {
+ if (ieVer < 9) {
+ // no html5 audio support, hide player controls
+ this.controlsElement.style.display = 'none';
+ this.html5Support = false;
+ return ;
+ } else if ('' == this.audioElement.canPlayType('audio/wav')) {
+ // check for mpeg <source> tag - if not found then fallback to flash
+ var sources = this.audioElement.getElementsByTagName('source');
+ var mp3support = false;
+ var type;
+
+ if (objAu) {
+ this.flashFallback = true;
+ }
+
+ for (var i = 0; i < sources.length; ++i) {
+ type = sources[i].attributes["type"].value;
+ if (type.toLowerCase().indexOf('mpeg') >= 0 || type.toLowerCase().indexOf('mp3') >= 0) {
+ mp3support = true;
+ break;
+ }
+ }
+
+ if (false == mp3support) {
+ // browser supports <audio> but does not support WAV audio and no flash audio available
+ this.html5Support = false;
+
+ if (this.flashFallback) {
+ // ie9+? bug - flash object does not display when moved from within audio tag to other dom node
+ var newObjAu = document.createElement('object');
+ var newParams = document.createElement('param');
+ var oldParams = objAu.getElementsByTagName('param');
+ this.copyElementAttributes(newObjAu, objAu);
+ if (oldParams.length > 0) {
+ this.copyElementAttributes(newParams, oldParams[0]);
+ newObjAu.appendChild(newParams);
+ }
+ objAu.parentNode.removeChild(objAu);
+ this.audioElement.parentNode.appendChild(newObjAu);
+ }
+
+ this.audioElement.parentNode.removeChild(this.audioElement);
+ this.controlsElement.parentNode.removeChild(this.controlsElement);
+
+ return ;
+ }
+ }
+ }
+
+ this.audioElement.addEventListener('playing', this.updateControls.bind(this), false);
+ this.audioElement.addEventListener('ended', this.audioStopped.bind(this), false);
+
+ // find the element used as the play button and register click event to play/stop audio
+ var children = this.controlsElement.getElementsByTagName('*');
+ for (var i = 0; i < children.length; ++i) {
+ var el = children[i];
+ if (undefined != el.className) {
+ if (el.className.indexOf('play_button') >= 0) {
+ this.playButton = el;
+ el.addEventListener('click', this.play.bind(this), false);
+ } else if (el.className.indexOf('play_image') >= 0) {
+ this.playButtonImage = el;
+ } else if (el.className.indexOf('loading_image') >= 0) {
+ this.loadingImage = el;
+ }
+ }
+ }
+
+ if (objAu) {
+ // remove flash object from DOM
+ objAu.parentNode.removeChild(objAu);
+ }
+}
+
+SecurimageAudio.prototype.play = function(evt) {
+ if (null != this.playButton) {
+ this.playButton.blur();
+ }
+
+ if (this.reload) {
+ this.replaceElements();
+ this.reload = false;
+ }
+
+ try {
+ if (!this.playing) {
+ if (this.playButtonImage != null) {
+ this.playButtonImage.style.display = 'none';
+ }
+ if (this.loadingImage != null) {
+ this.loadingImage.style.display = '';
+ }
+ //TODO: FIX, most likely browser doesn't support audio type
+ this.audioElement.onerror = this.audioError;
+ try {
+ this.audioElement.play();
+ } catch(ex) {
+ alert('Audio error: ' + ex);
+ }
+ } else {
+ this.audioElement.pause();
+ if (this.loadingImage != null) {
+ this.loadingImage.style.display = 'none';
+ }
+ if (this.playButtonImage != null) {
+ this.playButtonImage.style.display = '';
+ }
+ this.playing = false;
+ }
+ } catch (ex) {
+ alert('Audio error: ' + ex);
+ }
+
+ if (undefined !== evt) {
+ evt.preventDefault();
+ }
+ return false;
+}
+
+SecurimageAudio.prototype.refresh = function(captchaId) {
+ if (!this.html5Support) {
+ return;
+ }
+
+ if (undefined !== captchaId) {
+ this.captchaId = captchaId;
+ }
+
+ this.playing = true;
+ this.reload = false;
+ this.play(); // stops audio if playing
+ this.reload = true;
+
+ return false;
+}
+
+SecurimageAudio.prototype.copyElementAttributes = function(newEl, el) {
+ for (var i = 0, atts = el.attributes, n = atts.length; i < n; ++i) {
+ newEl.setAttribute(atts[i].nodeName, atts[i].value);
+ }
+
+ return newEl;
+}
+
+SecurimageAudio.prototype.replaceElements = function() {
+ var parent = this.audioElement.parentNode;
+ parent.removeChild(this.audioElement);
+
+ var newAudioEl = document.createElement('audio');
+ newAudioEl.setAttribute('style', 'display: none;');
+ newAudioEl.setAttribute('preload', 'false');
+ newAudioEl.setAttribute('id', this.audioElement.id);
+
+ for (var c = 0; c < this.audioElement.children.length; ++c) {
+ if (this.audioElement.children[c].tagName.toLowerCase() != 'source') continue;
+ var sourceEl = document.createElement('source');
+ this.copyElementAttributes(sourceEl, this.audioElement.children[c]);
+ var cid = (null !== this.captchaId) ? this.captchaId : (Math.random() + '').replace('0.', '');
+ sourceEl.src = sourceEl.src.replace(/([?|&])id=[a-zA-Z0-9]+/, '$1id=' + cid);
+ newAudioEl.appendChild(sourceEl);
+ }
+
+ this.audioElement = null;
+ this.audioElement = newAudioEl;
+ parent.appendChild(this.audioElement);
+
+ this.audioElement.addEventListener('playing', this.updateControls.bind(this), false);
+ this.audioElement.addEventListener('ended', this.audioStopped.bind(this), false);
+}
+
+SecurimageAudio.prototype.updateControls = function() {
+ this.playing = true;
+ if (this.loadingImage != null) {
+ this.loadingImage.style.display = 'none';
+ }
+ if (this.playButtonImage != null) {
+ this.playButtonImage.style.display = '';
+ }
+}
+
+SecurimageAudio.prototype.audioStopped = function() {
+ this.playing = false;
+}
+
+SecurimageAudio.prototype.audioError = function(err) {
+ var msg = null;
+ switch(err.target.error.code) {
+ case err.target.error.MEDIA_ERR_ABORTED:
+ break;
+ case err.target.error.MEDIA_ERR_NETWORK:
+ msg = 'A network error caused the audio download to fail.';
+ break;
+ case err.target.error.MEDIA_ERR_DECODE:
+ alert('An error occurred while decoding the audio');
+ break;
+ case err.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
+ alert('The audio format is not supported by your browser.');
+ break;
+ default:
+ alert('An unknown error occurred trying to play the audio.');
+ break;
+ }
+ if (msg) {
+ alert('Audio playback error: ' + msg);
+ }
+}
diff --git a/vendor/dapphp/securimage/securimage.php b/vendor/dapphp/securimage/securimage.php
new file mode 100644
index 0000000..1582b81
--- /dev/null
+++ b/vendor/dapphp/securimage/securimage.php
@@ -0,0 +1,3468 @@
+<?php
+
+// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
+
+/**
+ * Project: Securimage: A PHP class dealing with CAPTCHA images, audio, and validation
+ * File: securimage.php
+ *
+ * Copyright (c) 2017, Drew Phillips
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Any modifications to the library should be indicated clearly in the source code
+ * to inform users that the changes are not a part of the original software.
+ *
+ * If you found this script useful, please take a quick moment to rate it.
+ * http://www.hotscripts.com/rate/49400.html Thanks.
+ *
+ * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
+ * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
+ * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
+ * @copyright 2017 Drew Phillips
+ * @author Drew Phillips <drew@drew-phillips.com>
+ * @version 3.6.6 (Nov 20 2017)
+ * @package Securimage
+ *
+ */
+
+/**
+
+ ChangeLog
+ 3.6.6
+ - Not critical: Fix potential HTML injection in example form via HTTP_USER_AGENT (CVE-2017-14077)
+
+ 3.6.5
+ - Fix regex in replaceElements in securimage.js
+ - Update examples
+ - Exclude certain examples from Git autogenerated archives
+
+ 3.6.4
+ - Fix XSS vulnerability in example_form.ajax.php (Discovered by RedTeam. advisory rt-sa-2016-002)
+ - Update example_form.ajax.php to use Securimage::getCaptchaHtml()
+
+ 3.6.3
+ - Add support for multibyte wordlist files
+ - Fix code generation issues with UTF-8 charsets
+ - Add parameter to getCaptchaHtml() method to control display components of captcha HTML
+ - Fix database audio storage issue with multiple namespaces
+
+ 3.6.2
+ - Support HTTP range requests with audio playback (iOS requirement)
+ - Add optional config.inc.php for storing global configuration settings
+
+ 3.6.1
+ - Fix copyElement bug in securimage.js for IE Flash fallback
+
+ 3.6
+ - Implement CAPTCHA audio using HTML5 <audio> with optional Flash fallback
+ - Support MP3 audio using LAME MP3 Encoder (Internet Explorer 9+ does not support WAV format in <audio> tags)
+ - Add getCaptchaHtml() options to support full framework integration (ruifil)
+
+ 3.5.4
+ - Fix email validation code in example form files
+ - Fix backslashes in getCaptchaHtml for img attribute on Windows systems
+
+ 3.5.3
+ - Add options for audio button to getCaptchaHtml(), fix urlencoding of flash parameters that was breaking button
+
+ 3.5.2
+
+ - Add Securimage::getCaptchaHtml() for getting automatically generated captcha html code
+ - Option for using SoX to add effects to captcha audio to make identification by neural networks more difficult
+ - Add setNamespace() method
+ - Add getTimeToSolve() method
+ - Add session_status() check so session still starts if one had previously been opened and closed
+ - Add .htaccess file to audio directory to deny access; update audio files
+ - Option to skip checking of database tables during connection
+ - Add composer.json to package, submit to packagist
+ - Add font_ratio variable to determine size of font (github.com/wilkor)
+ - Add hint if sqlite3 database is not writeable. Improve database error handling, add example database options to securimage_play.php
+ - Fixed issue regarding database storage and math captcha breaking audio output (github.com/SoftwareAndOutsourcing)
+
+ 3.5.1
+ - Fix XSS vulnerability in example_form.php (discovered by Gjoko Krstic - <gjoko@zeroscience.mk>)
+
+ 3.5
+ - Release new version
+ - MB string support for charlist
+ - Modify audio file path to use language directories
+ - Changed default captcha appearance
+
+ 3.2RC4
+ - Add MySQL, PostgreSQL, and SQLite3 support for database storage
+ - Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions
+ - Add new captcha type that displays 2 dictionary words on one image
+ - Update examples
+
+ 3.2RC3
+ - Fix canSendHeaders() check which was breaking if a PHP startup error was issued
+
+ 3.2RC2
+ - Add error handler (https://github.com/dapphp/securimage/issues/15)
+ - Fix flash examples to use the correct value name for audio parameter
+
+ 3.2RC1
+ - New audio captcha code. Faster, fully dynamic audio, full WAV support
+ (Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio>
+ - New Flash audio streaming button. User defined image and size supported
+ - Additional options for customizing captcha (noise_level, send_headers,
+ no_exit, no_session, display_value
+ - Add captcha ID support. Uses sqlite and unique captcha IDs to track captchas,
+ no session used
+ - Add static methods for creating and validating captcha by ID
+ - Automatic clearing of old codes from SQLite database
+
+ 3.0.3Beta
+ - Add improved mixing function to WavFile class (Paul Voegler)
+ - Improve performance and security of captcha audio (Paul Voegler, Drew Phillips)
+ - Add option to use random file as background noise in captcha audio
+ - Add new securimage options for audio files
+
+ 3.0.2Beta
+ - Fix issue with session variables when upgrading from 2.0 - 3.0
+ - Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work
+
+ 3.0.1
+ - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
+
+ 3.0
+ - Rewrite class using PHP5 OOP
+ - Remove support for GD fonts, require FreeType
+ - Remove support for multi-color codes
+ - Add option to make codes case-sensitive
+ - Add namespaces to support multiple captchas on a single page or page specific captchas
+ - Add option to show simple math problems instead of codes
+ - Remove support for mp3 files due to vulnerability in decoding mp3 audio files
+ - Create new flash file to stream wav files instead of mp3
+ - Changed to BSD license
+
+ 2.0.2
+ - Fix pathing to make integration into libraries easier (Nathan Phillip Brink ohnobinki@ohnopublishing.net)
+
+ 2.0.1
+ - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
+ - Add fallback to gd fonts if ttf support is not enabled or font file not found (Mike Challis http://www.642weather.com/weather/scripts.php)
+ - Check for previous definition of image type constants (Mike Challis)
+ - Fix mime type settings for audio output
+ - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
+ - Ability to let codes expire after a given length of time
+ - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
+
+ 2.0.0
+ - Add mathematical distortion to characters (using code from HKCaptcha)
+ - Improved session support
+ - Added Securimage_Color class for easier color definitions
+ - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
+ - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
+ - Audio output is mp3 format by default
+ - Change font to AlteHaasGrotesk by yann le coroller
+ - Some code cleanup
+
+ 1.0.4 (unreleased)
+ - Ability to output audible codes in mp3 format to stream from flash
+
+ 1.0.3.1
+ - Error reading from wordlist in some cases caused words to be cut off 1 letter short
+
+ 1.0.3
+ - Removed shadow_text from code which could cause an undefined property error due to removal from previous version
+
+ 1.0.2
+ - Audible CAPTCHA Code wav files
+ - Create codes from a word list instead of random strings
+
+ 1.0
+ - Added the ability to use a selected character set, rather than a-z0-9 only.
+ - Added the multi-color text option to use different colors for each letter.
+ - Switched to automatic session handling instead of using files for code storage
+ - Added GD Font support if ttf support is not available. Can use internal GD fonts or load new ones.
+ - Added the ability to set line thickness
+ - Added option for drawing arced lines over letters
+ - Added ability to choose image type for output
+
+ */
+
+
+/**
+ * Securimage CAPTCHA Class.
+ *
+ * A class for creating and validating secure CAPTCHA images and audio.
+ *
+ * The class contains many options regarding appearance, security, storage of
+ * captcha data and image/audio generation options.
+ *
+* @package Securimage
+ * @subpackage classes
+ * @author Drew Phillips <drew@drew-phillips.com>
+ *
+ */
+class Securimage
+{
+ // All of the public variables below are securimage options
+ // They can be passed as an array to the Securimage constructor, set below,
+ // or set from securimage_show.php and securimage_play.php
+
+ /**
+ * Constant for rendering captcha as a JPEG image
+ * @var int
+ */
+ const SI_IMAGE_JPEG = 1;
+
+ /**
+ * Constant for rendering captcha as a PNG image (default)
+ * @var int
+ */
+
+ const SI_IMAGE_PNG = 2;
+ /**
+ * Constant for rendering captcha as a GIF image
+ * @var int
+ */
+ const SI_IMAGE_GIF = 3;
+
+ /**
+ * Constant for generating a normal alphanumeric captcha based on the
+ * character set
+ *
+ * @see Securimage::$charset charset property
+ * @var int
+ */
+ const SI_CAPTCHA_STRING = 0;
+
+ /**
+ * Constant for generating a captcha consisting of a simple math problem
+ *
+ * @var int
+ */
+ const SI_CAPTCHA_MATHEMATIC = 1;
+
+ /**
+ * Constant for generating a word based captcha using 2 words from a list
+ *
+ * @var int
+ */
+ const SI_CAPTCHA_WORDS = 2;
+
+ /**
+ * MySQL option identifier for database storage option
+ *
+ * @var string
+ */
+ const SI_DRIVER_MYSQL = 'mysql';
+
+ /**
+ * PostgreSQL option identifier for database storage option
+ *
+ * @var string
+ */
+ const SI_DRIVER_PGSQL = 'pgsql';
+
+ /**
+ * SQLite option identifier for database storage option
+ *
+ * @var string
+ */
+ const SI_DRIVER_SQLITE3 = 'sqlite';
+
+ /**
+ * getCaptchaHtml() display constant for HTML Captcha Image
+ *
+ * @var integer
+ */
+ const HTML_IMG = 1;
+
+ /**
+ * getCaptchaHtml() display constant for HTML5 Audio code
+ *
+ * @var integer
+ */
+ const HTML_AUDIO = 2;
+
+ /**
+ * getCaptchaHtml() display constant for Captcha Input text box
+ *
+ * @var integer
+ */
+ const HTML_INPUT = 4;
+
+ /**
+ * getCaptchaHtml() display constant for Captcha Text HTML label
+ *
+ * @var integer
+ */
+ const HTML_INPUT_LABEL = 8;
+
+ /**
+ * getCaptchaHtml() display constant for HTML Refresh button
+ *
+ * @var integer
+ */
+ const HTML_ICON_REFRESH = 16;
+
+ /**
+ * getCaptchaHtml() display constant for all HTML elements (default)
+ *
+ * @var integer
+ */
+ const HTML_ALL = 0xffffffff;
+
+ /*%*********************************************************************%*/
+ // Properties
+
+ /**
+ * The width of the captcha image
+ * @var int
+ */
+ public $image_width = 215;
+
+ /**
+ * The height of the captcha image
+ * @var int
+ */
+ public $image_height = 80;
+
+ /**
+ * Font size is calculated by image height and this ratio. Leave blank for
+ * default ratio of 0.4.
+ *
+ * Valid range: 0.1 - 0.99.
+ *
+ * Depending on image_width, values > 0.6 are probably too large and
+ * values < 0.3 are too small.
+ *
+ * @var float
+ */
+ public $font_ratio;
+
+ /**
+ * The type of the image, default = png
+ *
+ * @see Securimage::SI_IMAGE_PNG SI_IMAGE_PNG
+ * @see Securimage::SI_IMAGE_JPEG SI_IMAGE_JPEG
+ * @see Securimage::SI_IMAGE_GIF SI_IMAGE_GIF
+ * @var int
+ */
+ public $image_type = self::SI_IMAGE_PNG;
+
+ /**
+ * The background color of the captcha
+ * @var Securimage_Color|string
+ */
+ public $image_bg_color = '#ffffff';
+
+ /**
+ * The color of the captcha text
+ * @var Securimage_Color|string
+ */
+ public $text_color = '#707070';
+
+ /**
+ * The color of the lines over the captcha
+ * @var Securimage_Color|string
+ */
+ public $line_color = '#707070';
+
+ /**
+ * The color of the noise that is drawn
+ * @var Securimage_Color|string
+ */
+ public $noise_color = '#707070';
+
+ /**
+ * How transparent to make the text.
+ *
+ * 0 = completely opaque, 100 = invisible
+ *
+ * @var int
+ */
+ public $text_transparency_percentage = 20;
+
+ /**
+ * Whether or not to draw the text transparently.
+ *
+ * true = use transparency, false = no transparency
+ *
+ * @var bool
+ */
+ public $use_transparent_text = true;
+
+ /**
+ * The length of the captcha code
+ * @var int
+ */
+ public $code_length = 6;
+
+ /**
+ * Whether the captcha should be case sensitive or not.
+ *
+ * Not recommended, use only for maximum protection.
+ *
+ * @var bool
+ */
+ public $case_sensitive = false;
+
+ /**
+ * The character set to use for generating the captcha code
+ * @var string
+ */
+ public $charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
+
+ /**
+ * How long in seconds a captcha remains valid, after this time it will be
+ * considered incorrect.
+ *
+ * @var int
+ */
+ public $expiry_time = 900;
+
+ /**
+ * The session name securimage should use.
+ *
+ * Only use if your application uses a custom session name (e.g. Joomla).
+ * It is recommended to set this value here so it is used by all securimage
+ * scripts (i.e. securimage_show.php)
+ *
+ * @var string
+ */
+ public $session_name = null;
+
+ /**
+ * true to use the wordlist file, false to generate random captcha codes
+ * @var bool
+ */
+ public $use_wordlist = false;
+
+ /**
+ * The level of distortion.
+ *
+ * 0.75 = normal, 1.0 = very high distortion
+ *
+ * @var double
+ */
+ public $perturbation = 0.85;
+
+ /**
+ * How many lines to draw over the captcha code to increase security
+ * @var int
+ */
+ public $num_lines = 5;
+
+ /**
+ * The level of noise (random dots) to place on the image, 0-10
+ * @var int
+ */
+ public $noise_level = 2;
+
+ /**
+ * The signature text to draw on the bottom corner of the image
+ * @var string
+ */
+ public $image_signature = '';
+
+ /**
+ * The color of the signature text
+ * @var Securimage_Color|string
+ */
+ public $signature_color = '#707070';
+
+ /**
+ * The path to the ttf font file to use for the signature text.
+ * Defaults to $ttf_file (AHGBold.ttf)
+ *
+ * @see Securimage::$ttf_file
+ * @var string
+ */
+ public $signature_font;
+
+ /**
+ * No longer used.
+ *
+ * Use an SQLite database to store data (for users that do not support cookies)
+ *
+ * @var bool
+ * @see Securimage::$database_driver database_driver property
+ * @deprecated 3.2RC4
+ */
+ public $use_sqlite_db = false;
+
+ /**
+ * Use a database backend for code storage.
+ * Provides a fallback to users with cookies disabled.
+ * Required when using captcha IDs.
+ *
+ * @see Securimage::$database_driver
+ * @var bool
+ */
+ public $use_database = false;
+
+ /**
+ * Whether or not to skip checking if Securimage tables exist when using a
+ * database.
+ *
+ * Turn this to true once database functionality is working to improve
+ * performance.
+ *
+ * @var bool true to not check if captcha_codes tables are set up, false
+ * to check (and create if necessary)
+ */
+ public $skip_table_check = false;
+
+ /**
+ * Database driver to use for database support.
+ * Allowable values: *mysql*, *pgsql*, *sqlite*.
+ * Default: sqlite
+ *
+ * @var string
+ */
+ public $database_driver = self::SI_DRIVER_SQLITE3;
+
+ /**
+ * Database host to connect to when using mysql or postgres
+ *
+ * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP
+ *
+ * Does not apply to SQLite
+ *
+ * @var string
+ */
+ public $database_host = 'localhost';
+
+ /**
+ * Database username for connection (mysql, postgres only)
+ * Default is an empty string
+ *
+ * @var string
+ */
+ public $database_user = '';
+
+ /**
+ * Database password for connection (mysql, postgres only)
+ * Default is empty string
+ *
+ * @var string
+ */
+ public $database_pass = '';
+
+ /**
+ * Name of the database to select (mysql, postgres only)
+ *
+ * @see Securimage::$database_file for SQLite
+ * @var string
+ */
+ public $database_name = '';
+
+ /**
+ * Database table where captcha codes are stored
+ *
+ * Note: Securimage will attempt to create this table for you if it does
+ * not exist. If the table cannot be created, an E_USER_WARNING is emitted
+ *
+ * @var string
+ */
+ public $database_table = 'captcha_codes';
+
+ /**
+ * Fully qualified path to the database file when using SQLite3.
+ *
+ * This value is only used when $database_driver == sqlite and does
+ * not apply when no database is used, or when using MySQL or PostgreSQL.
+ *
+ * On *nix, file must have permissions of 0666.
+ *
+ * **Make sure the directory containing this file is NOT web accessible**
+ *
+ * @var string
+ */
+ public $database_file;
+
+ /**
+ * The type of captcha to create.
+ *
+ * Either alphanumeric based on *charset*, a simple math problem, or an
+ * image consisting of 2 words from the word list.
+ *
+ * @see Securimage::SI_CAPTCHA_STRING SI_CAPTCHA_STRING
+ * @see Securimage::SI_CAPTCHA_MATHEMATIC SI_CAPTCHA_MATHEMATIC
+ * @see Securimage::SI_CAPTCHA_WORDS SI_CAPTCHA_WORDS
+ * @see Securimage::$charset charset property
+ * @see Securimage::$wordlist_file wordlist_file property
+ * @var int
+ */
+ public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC, or self::SI_CAPTCHA_WORDS;
+
+ /**
+ * The captcha namespace used for having multiple captchas on a page or
+ * to separate captchas from differen forms on your site.
+ * Example:
+ *
+ * <?php
+ * // use <img src="securimage_show.php?namespace=contact_form">
+ * // or manually in securimage_show.php
+ * $img->setNamespace('contact_form');
+ *
+ * // in form validator
+ * $img->setNamespace('contact_form');
+ * if ($img->check($code) == true) {
+ * echo "Valid!";
+ * }
+ *
+ * @var string
+ */
+ public $namespace;
+
+ /**
+ * The TTF font file to use to draw the captcha code.
+ *
+ * Leave blank for default font AHGBold.ttf
+ *
+ * @var string
+ */
+ public $ttf_file;
+
+ /**
+ * The path to the wordlist file to use.
+ *
+ * Leave blank for default words/words.txt
+ *
+ * @var string
+ */
+ public $wordlist_file;
+
+ /**
+ * Character encoding of the wordlist file.
+ * Requires PHP Multibyte String (mbstring) support.
+ * Allows word list to contain characters other than US-ASCII (requires compatible TTF font).
+ *
+ * @var string The character encoding (e.g. UTF-8, UTF-7, EUC-JP, GB2312)
+ * @see http://php.net/manual/en/mbstring.supported-encodings.php
+ * @since 3.6.3
+ */
+ public $wordlist_file_encoding = null;
+
+ /**
+ * The directory to scan for background images, if set a random background
+ * will be chosen from this folder
+ *
+ * @var string
+ */
+ public $background_directory;
+
+ /**
+ * No longer used
+ *
+ * The path to the SQLite database file to use
+ *
+ * @deprecated 3.2RC4
+ * @see Securimage::$database_file database_file property
+ * @var string
+ */
+ public $sqlite_database;
+
+ /**
+ * The path to the audio files to be used for audio captchas.
+ *
+ * Can also be set in securimage_play.php
+ *
+ * Example:
+ *
+ * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
+ *
+ * @var string
+ */
+ public $audio_path;
+
+ /**
+ * Use SoX (The Swiss Army knife of audio manipulation) for audio effects
+ * and processing.
+ *
+ * Using SoX should make it more difficult for bots to solve audio captchas
+ *
+ * @see Securimage::$sox_binary_path sox_binary_path property
+ * @var bool true to use SoX, false to use PHP
+ */
+ public $audio_use_sox = false;
+
+ /**
+ * The path to the SoX binary on your system
+ *
+ * @var string
+ */
+ public $sox_binary_path = '/usr/bin/sox';
+
+ /**
+ * The path to the lame (mp3 encoder) binary on your system
+ * Static so that Securimage::getCaptchaHtml() has access to this value.
+ *
+ * @since 3.6
+ * @var string
+ */
+ public static $lame_binary_path = '/usr/bin/lame';
+
+ /**
+ * The path to the directory containing audio files that will be selected
+ * randomly and mixed with the captcha audio.
+ *
+ * @var string
+ */
+ public $audio_noise_path;
+
+ /**
+ * Whether or not to mix background noise files into captcha audio
+ *
+ * Mixing random background audio with noise can help improve security of
+ * audio captcha.
+ *
+ * Default: securimage/audio/noise
+ *
+ * @since 3.0.3
+ * @see Securimage::$audio_noise_path audio_noise_path property
+ * @var bool true = mix, false = no
+ */
+ public $audio_use_noise;
+
+ /**
+ * The method and threshold (or gain factor) used to normalize the mixing
+ * with background noise.
+ *
+ * See http://www.voegler.eu/pub/audio/ for more information.
+ *
+ * Default: 0.6
+ *
+ * Valid:
+ * >= 1
+ * Normalize by multiplying by the threshold (boost - positive gain).
+ * A value of 1 in effect means no normalization (and results in clipping).
+ *
+ * <= -1
+ * Normalize by dividing by the the absolute value of threshold (attenuate - negative gain).
+ * A factor of 2 (-2) is about 6dB reduction in volume.
+ *
+ * [0, 1) (open inverval - not including 1)
+ * The threshold above which amplitudes are comressed logarithmically.
+ * e.g. 0.6 to leave amplitudes up to 60% "as is" and compressabove.
+ *
+ * (-1, 0) (open inverval - not including -1 and 0)
+ * The threshold above which amplitudes are comressed linearly.
+ * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above.
+ *
+ * @since 3.0.4
+ * @var float
+ */
+ public $audio_mix_normalization = 0.8;
+
+ /**
+ * Whether or not to degrade audio by introducing random noise.
+ *
+ * Current research shows this may not increase the security of audible
+ * captchas.
+ *
+ * Default: true
+ *
+ * @since 3.0.3
+ * @var bool
+ */
+ public $degrade_audio;
+
+ /**
+ * Minimum delay to insert between captcha audio letters in milliseconds
+ *
+ * @since 3.0.3
+ * @var float
+ */
+ public $audio_gap_min = 0;
+
+ /**
+ * Maximum delay to insert between captcha audio letters in milliseconds
+ *
+ * @since 3.0.3
+ * @var float
+ */
+ public $audio_gap_max = 3000;
+
+ /**
+ * Captcha ID if using static captcha
+ * @var string Unique captcha id
+ */
+ protected static $_captchaId = null;
+
+ /**
+ * The GD image resource of the captcha image
+ *
+ * @var resource
+ */
+ protected $im;
+
+ /**
+ * A temporary GD image resource of the captcha image for distortion
+ *
+ * @var resource
+ */
+ protected $tmpimg;
+
+ /**
+ * The background image GD resource
+ * @var string
+ */
+ protected $bgimg;
+
+ /**
+ * Scale factor for magnification of distorted captcha image
+ *
+ * @var int
+ */
+ protected $iscale = 5;
+
+ /**
+ * Absolute path to securimage directory.
+ *
+ * This is calculated at runtime
+ *
+ * @var string
+ */
+ public $securimage_path = null;
+
+ /**
+ * The captcha challenge value.
+ *
+ * Either the case-sensitive/insensitive word captcha, or the solution to
+ * the math captcha.
+ *
+ * @var string|bool Captcha challenge value
+ */
+ protected $code;
+
+ /**
+ * The display value of the captcha to draw on the image
+ *
+ * Either the word captcha or the math equation to present to the user
+ *
+ * @var string Captcha display value to draw on the image
+ */
+ protected $code_display;
+
+ /**
+ * Alternate text to draw as the captcha image text
+ *
+ * A value that can be passed to the constructor that can be used to
+ * generate a captcha image with a given value.
+ *
+ * This value does not get stored in the session or database and is only
+ * used when calling Securimage::show().
+ *
+ * If a display_value was passed to the constructor and the captcha image
+ * is generated, the display_value will be used as the string to draw on
+ * the captcha image.
+ *
+ * Used only if captcha codes are generated and managed by a 3rd party
+ * app/library
+ *
+ * @var string Captcha code value to display on the image
+ */
+ public $display_value;
+
+ /**
+ * Captcha code supplied by user [set from Securimage::check()]
+ *
+ * @var string
+ */
+ protected $captcha_code;
+
+ /**
+ * Time (in seconds) that the captcha was solved in (correctly or incorrectly).
+ *
+ * This is from the time of code creation, to when validation was attempted.
+ *
+ * @var int
+ */
+ protected $_timeToSolve = 0;
+
+ /**
+ * Flag that can be specified telling securimage not to call exit after
+ * generating a captcha image or audio file
+ *
+ * @var bool If true, script will not terminate; if false script will terminate (default)
+ */
+ protected $no_exit;
+
+ /**
+ * Flag indicating whether or not a PHP session should be started and used
+ *
+ * @var bool If true, no session will be started; if false, session will be started and used to store data (default)
+ */
+ protected $no_session;
+
+ /**
+ * Flag indicating whether or not HTTP headers will be sent when outputting
+ * captcha image/audio
+ *
+ * @var bool If true (default) headers will be sent, if false, no headers are sent
+ */
+ protected $send_headers;
+
+ /**
+ * PDO connection when a database is used
+ *
+ * @var PDO|bool
+ */
+ protected $pdo_conn;
+
+ /**
+ * The GD color for the background color
+ *
+ * @var int
+ */
+ protected $gdbgcolor;
+
+ /**
+ * The GD color for the text color
+ *
+ * @var int
+ */
+ protected $gdtextcolor;
+
+ /**
+ * The GD color for the line color
+ *
+ * @var int
+ */
+ protected $gdlinecolor;
+
+ /**
+ * The GD color for the signature text color
+ *
+ * @var int
+ */
+ protected $gdsignaturecolor;
+
+ /**
+ * Create a new securimage object, pass options to set in the constructor.
+ *
+ * The object can then be used to display a captcha, play an audible captcha, or validate a submission.
+ *
+ * @param array $options Options to initialize the class. May be any class property.
+ *
+ * $options = array(
+ * 'text_color' => new Securimage_Color('#013020'),
+ * 'code_length' => 5,
+ * 'num_lines' => 5,
+ * 'noise_level' => 3,
+ * 'font_file' => Securimage::getPath() . '/custom.ttf'
+ * );
+ *
+ * $img = new Securimage($options);
+ *
+ */
+ public function __construct($options = array())
+ {
+ $this->securimage_path = dirname(__FILE__);
+
+ if (!is_array($options)) {
+ trigger_error(
+ '$options passed to Securimage::__construct() must be an array. ' .
+ gettype($options) . ' given',
+ E_USER_WARNING
+ );
+ $options = array();
+ }
+
+ // check for and load settings from custom config file
+ if (file_exists(dirname(__FILE__) . '/config.inc.php')) {
+ $settings = include dirname(__FILE__) . '/config.inc.php';
+
+ if (is_array($settings)) {
+ $options = array_merge($settings, $options);
+ }
+ }
+
+ if (is_array($options) && sizeof($options) > 0) {
+ foreach($options as $prop => $val) {
+ if ($prop == 'captchaId') {
+ Securimage::$_captchaId = $val;
+ $this->use_database = true;
+ } else if ($prop == 'use_sqlite_db') {
+ trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
+ } else {
+ $this->$prop = $val;
+ }
+ }
+ }
+
+ $this->image_bg_color = $this->initColor($this->image_bg_color, '#ffffff');
+ $this->text_color = $this->initColor($this->text_color, '#616161');
+ $this->line_color = $this->initColor($this->line_color, '#616161');
+ $this->noise_color = $this->initColor($this->noise_color, '#616161');
+ $this->signature_color = $this->initColor($this->signature_color, '#616161');
+
+ if (is_null($this->ttf_file)) {
+ $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
+ }
+
+ $this->signature_font = $this->ttf_file;
+
+ if (is_null($this->wordlist_file)) {
+ $this->wordlist_file = $this->securimage_path . '/words/words.txt';
+ }
+
+ if (is_null($this->database_file)) {
+ $this->database_file = $this->securimage_path . '/database/securimage.sq3';
+ }
+
+ if (is_null($this->audio_path)) {
+ $this->audio_path = $this->securimage_path . '/audio/en/';
+ }
+
+ if (is_null($this->audio_noise_path)) {
+ $this->audio_noise_path = $this->securimage_path . '/audio/noise/';
+ }
+
+ if (is_null($this->audio_use_noise)) {
+ $this->audio_use_noise = true;
+ }
+
+ if (is_null($this->degrade_audio)) {
+ $this->degrade_audio = true;
+ }
+
+ if (is_null($this->code_length) || (int)$this->code_length < 1) {
+ $this->code_length = 6;
+ }
+
+ if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
+ $this->perturbation = 0.75;
+ }
+
+ if (is_null($this->namespace) || !is_string($this->namespace)) {
+ $this->namespace = 'default';
+ }
+
+ if (is_null($this->no_exit)) {
+ $this->no_exit = false;
+ }
+
+ if (is_null($this->no_session)) {
+ $this->no_session = false;
+ }
+
+ if (is_null($this->send_headers)) {
+ $this->send_headers = true;
+ }
+
+ if ($this->no_session != true) {
+ // Initialize session or attach to existing
+ if ( session_id() == '' || (function_exists('session_status') && PHP_SESSION_NONE == session_status()) ) { // no session has been started yet (or it was previousy closed), which is needed for validation
+ if (!is_null($this->session_name) && trim($this->session_name) != '') {
+ session_name(trim($this->session_name)); // set session name if provided
+ }
+ session_start();
+ }
+ }
+ }
+
+ /**
+ * Return the absolute path to the Securimage directory.
+ *
+ * @return string The path to the securimage base directory
+ */
+ public static function getPath()
+ {
+ return dirname(__FILE__);
+ }
+
+ /**
+ * Generate a new captcha ID or retrieve the current ID (if exists).
+ *
+ * @param bool $new If true, generates a new challenge and returns and ID. If false, the existing captcha ID is returned, or null if none exists.
+ * @param array $options Additional options to be passed to Securimage.
+ * $options must include database settings if they are not set directly in securimage.php
+ *
+ * @return null|string Returns null if no captcha id set and new was false, or the captcha ID
+ */
+ public static function getCaptchaId($new = true, array $options = array())
+ {
+ if (is_null($new) || (bool)$new == true) {
+ $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
+ $opts = array('no_session' => true,
+ 'use_database' => true);
+ if (sizeof($options) > 0) $opts = array_merge($options, $opts);
+ $si = new self($opts);
+ Securimage::$_captchaId = $id;
+ $si->createCode();
+
+ return $id;
+ } else {
+ return Securimage::$_captchaId;
+ }
+ }
+
+ /**
+ * Validate a captcha code input against a captcha ID
+ *
+ * @param string $id The captcha ID to check
+ * @param string $value The captcha value supplied by the user
+ * @param array $options Array of options to construct Securimage with.
+ * Options must include database options if they are not set in securimage.php
+ *
+ * @see Securimage::$database_driver
+ * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open
+ */
+ public static function checkByCaptchaId($id, $value, array $options = array())
+ {
+ $opts = array('captchaId' => $id,
+ 'no_session' => true,
+ 'use_database' => true);
+
+ if (sizeof($options) > 0) $opts = array_merge($options, $opts);
+
+ $si = new self($opts);
+
+ if ($si->openDatabase()) {
+ $code = $si->getCodeFromDatabase();
+
+ if (is_array($code)) {
+ $si->code = $code['code'];
+ $si->code_display = $code['code_disp'];
+ }
+
+ if ($si->check($value)) {
+ $si->clearCodeFromDatabase();
+
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Generates a new challenge and serves a captcha image.
+ *
+ * Appropriate headers will be sent to the browser unless the *send_headers* option is false.
+ *
+ * @param string $background_image The absolute or relative path to the background image to use as the background of the captcha image.
+ *
+ * $img = new Securimage();
+ * $img->code_length = 6;
+ * $img->num_lines = 5;
+ * $img->noise_level = 5;
+ *
+ * $img->show(); // sends the image and appropriate headers to browser
+ * exit;
+ */
+ public function show($background_image = '')
+ {
+ set_error_handler(array(&$this, 'errorHandler'));
+
+ if($background_image != '' && is_readable($background_image)) {
+ $this->bgimg = $background_image;
+ }
+
+ $this->doImage();
+ }
+
+ /**
+ * Checks a given code against the correct value from the session and/or database.
+ *
+ * @param string $code The captcha code to check
+ *
+ * $code = $_POST['code'];
+ * $img = new Securimage();
+ * if ($img->check($code) == true) {
+ * $captcha_valid = true;
+ * } else {
+ * $captcha_valid = false;
+ * }
+ *
+ * @return bool true if the given code was correct, false if not.
+ */
+ public function check($code)
+ {
+ $this->code_entered = $code;
+ $this->validate();
+ return $this->correct_code;
+ }
+
+ /**
+ * Returns HTML code for displaying the captcha image, audio button, and form text input.
+ *
+ * Options can be specified to modify the output of the HTML. Accepted options:
+ *
+ * 'securimage_path':
+ * Optional: The URI to where securimage is installed (e.g. /securimage)
+ * 'show_image_url':
+ * Path to the securimage_show.php script (useful when integrating with a framework or moving outside the securimage directory)
+ * This will be passed as a urlencoded string to the <img> tag for outputting the captcha image
+ * 'audio_play_url':
+ * Same as show_image_url, except this indicates the URL of the audio playback script
+ * 'image_id':
+ * A string that sets the "id" attribute of the captcha image (default: captcha_image)
+ * 'image_alt_text':
+ * The alt text of the captcha image (default: CAPTCHA Image)
+ * 'show_audio_button':
+ * true/false Whether or not to show the audio button (default: true)
+ * 'disable_flash_fallback':)
+ * Allow only HTML5 audio and disable Flash fallback
+ * 'show_refresh_button':
+ * true/false Whether or not to show a button to refresh the image (default: true)
+ * 'audio_icon_url':
+ * URL to the image used for showing the HTML5 audio icon
+ * 'icon_size':
+ * Size (for both height & width) in pixels of the audio and refresh buttons
+ * 'show_text_input':
+ * true/false Whether or not to show the text input for the captcha (default: true)
+ * 'refresh_alt_text':
+ * Alt text for the refresh image (default: Refresh Image)
+ * 'refresh_title_text':
+ * Title text for the refresh image link (default: Refresh Image)
+ * 'input_id':
+ * A string that sets the "id" attribute of the captcha text input (default: captcha_code)
+ * 'input_name':
+ * A string that sets the "name" attribute of the captcha text input (default: same as input_id)
+ * 'input_text':
+ * A string that sets the text of the label for the captcha text input (default: Type the text:)
+ * 'input_attributes':
+ * An array of additional HTML tag attributes to pass to the text input tag (default: empty)
+ * 'image_attributes':
+ * An array of additional HTML tag attributes to pass to the captcha image tag (default: empty)
+ * 'error_html':
+ * Optional HTML markup to be shown above the text input field
+ * 'namespace':
+ * The optional captcha namespace to use for showing the image and playing back the audio. Namespaces are for using multiple captchas on the same page.
+ *
+ * @param array $options Array of options for modifying the HTML code.
+ * @param int $parts Securiage::HTML_* constant controlling what component of the captcha HTML to display
+ *
+ * @return string The generated HTML code for displaying the captcha
+ */
+ public static function getCaptchaHtml($options = array(), $parts = Securimage::HTML_ALL)
+ {
+ static $javascript_init = false;
+
+ if (!isset($options['securimage_path'])) {
+ $docroot = (isset($_SERVER['DOCUMENT_ROOT'])) ? $_SERVER['DOCUMENT_ROOT'] : substr($_SERVER['SCRIPT_FILENAME'], 0, -strlen($_SERVER['SCRIPT_NAME']));
+ $docroot = realpath($docroot);
+ $sipath = dirname(__FILE__);
+ $securimage_path = str_replace($docroot, '', $sipath);
+ } else {
+ $securimage_path = $options['securimage_path'];
+ }
+
+ $show_image_url = (isset($options['show_image_url'])) ? $options['show_image_url'] : null;
+ $image_id = (isset($options['image_id'])) ? $options['image_id'] : 'captcha_image';
+ $image_alt = (isset($options['image_alt_text'])) ? $options['image_alt_text'] : 'CAPTCHA Image';
+ $show_audio_btn = (isset($options['show_audio_button'])) ? (bool)$options['show_audio_button'] : true;
+ $disable_flash_fbk = (isset($options['disable_flash_fallback'])) ? (bool)$options['disable_flash_fallback'] : false;
+ $show_refresh_btn = (isset($options['show_refresh_button'])) ? (bool)$options['show_refresh_button'] : true;
+ $refresh_icon_url = (isset($options['refresh_icon_url'])) ? $options['refresh_icon_url'] : null;
+ $audio_but_bg_col = (isset($options['audio_button_bgcol'])) ? $options['audio_button_bgcol'] : '#ffffff';
+ $audio_icon_url = (isset($options['audio_icon_url'])) ? $options['audio_icon_url'] : null;
+ $loading_icon_url = (isset($options['loading_icon_url'])) ? $options['loading_icon_url'] : null;
+ $icon_size = (isset($options['icon_size'])) ? $options['icon_size'] : 32;
+ $audio_play_url = (isset($options['audio_play_url'])) ? $options['audio_play_url'] : null;
+ $audio_swf_url = (isset($options['audio_swf_url'])) ? $options['audio_swf_url'] : null;
+ $show_input = (isset($options['show_text_input'])) ? (bool)$options['show_text_input'] : true;
+ $refresh_alt = (isset($options['refresh_alt_text'])) ? $options['refresh_alt_text'] : 'Refresh Image';
+ $refresh_title = (isset($options['refresh_title_text'])) ? $options['refresh_title_text'] : 'Refresh Image';
+ $input_text = (isset($options['input_text'])) ? $options['input_text'] : 'Type the text:';
+ $input_id = (isset($options['input_id'])) ? $options['input_id'] : 'captcha_code';
+ $input_name = (isset($options['input_name'])) ? $options['input_name'] : $input_id;
+ $input_attrs = (isset($options['input_attributes'])) ? $options['input_attributes'] : array();
+ $image_attrs = (isset($options['image_attributes'])) ? $options['image_attributes'] : array();
+ $error_html = (isset($options['error_html'])) ? $options['error_html'] : null;
+ $namespace = (isset($options['namespace'])) ? $options['namespace'] : '';
+
+ $rand = md5(uniqid($_SERVER['REMOTE_PORT'], true));
+ $securimage_path = rtrim($securimage_path, '/\\');
+ $securimage_path = str_replace('\\', '/', $securimage_path);
+
+ $image_attr = '';
+ if (!is_array($image_attrs)) $image_attrs = array();
+ if (!isset($image_attrs['style'])) $image_attrs['style'] = 'float: left; padding-right: 5px';
+ $image_attrs['id'] = $image_id;
+
+ $show_path = $securimage_path . '/securimage_show.php?';
+ if ($show_image_url) {
+ if (parse_url($show_image_url, PHP_URL_QUERY)) {
+ $show_path = "{$show_image_url}&";
+ } else {
+ $show_path = "{$show_image_url}?";
+ }
+ }
+ if (!empty($namespace)) {
+ $show_path .= sprintf('namespace=%s&amp;', $namespace);
+ }
+ $image_attrs['src'] = $show_path . $rand;
+
+ $image_attrs['alt'] = $image_alt;
+
+ foreach($image_attrs as $name => $val) {
+ $image_attr .= sprintf('%s="%s" ', $name, htmlspecialchars($val));
+ }
+
+ $swf_path = $securimage_path . '/securimage_play.swf';
+ $play_path = $securimage_path . '/securimage_play.php?';
+ $icon_path = $securimage_path . '/images/audio_icon.png';
+ $load_path = $securimage_path . '/images/loading.png';
+ $js_path = $securimage_path . '/securimage.js';
+
+ if (!empty($audio_icon_url)) {
+ $icon_path = $audio_icon_url;
+ }
+
+ if (!empty($loading_icon_url)) {
+ $load_path = $loading_icon_url;
+ }
+
+ if (!empty($audio_play_url)) {
+ if (parse_url($audio_play_url, PHP_URL_QUERY)) {
+ $play_path = "{$audio_play_url}&";
+ } else {
+ $play_path = "{$audio_play_url}?";
+ }
+ }
+
+ if (!empty($namespace)) {
+ $play_path .= sprintf('namespace=%s&amp;', $namespace);
+ }
+
+ if (!empty($audio_swf_url)) {
+ $swf_path = $audio_swf_url;
+ }
+
+ $audio_obj = $image_id . '_audioObj';
+ $html = '';
+
+ if ( ($parts & Securimage::HTML_IMG) > 0) {
+ $html .= sprintf('<img %s/>', $image_attr);
+ }
+
+ if ( ($parts & Securimage::HTML_AUDIO) > 0 && $show_audio_btn) {
+ // html5 audio
+ $html .= sprintf('<div id="%s_audio_div">', $image_id) . "\n" .
+ sprintf('<audio id="%s_audio" preload="none" style="display: none">', $image_id) . "\n";
+
+ // check for existence and executability of LAME binary
+ // prefer mp3 over wav by sourcing it first, if available
+ if (is_executable(Securimage::$lame_binary_path)) {
+ $html .= sprintf('<source id="%s_source_mp3" src="%sid=%s&amp;format=mp3" type="audio/mpeg">', $image_id, $play_path, uniqid()) . "\n";
+ }
+
+ // output wav source
+ $html .= sprintf('<source id="%s_source_wav" src="%sid=%s" type="audio/wav">', $image_id, $play_path, uniqid()) . "\n";
+
+ // flash audio button
+ if (!$disable_flash_fbk) {
+ $html .= sprintf('<object type="application/x-shockwave-flash" data="%s?bgcol=%s&amp;icon_file=%s&amp;audio_file=%s" height="%d" width="%d">',
+ htmlspecialchars($swf_path),
+ urlencode($audio_but_bg_col),
+ urlencode($icon_path),
+ urlencode(html_entity_decode($play_path)),
+ $icon_size, $icon_size
+ );
+
+ $html .= sprintf('<param name="movie" value="%s?bgcol=%s&amp;icon_file=%s&amp;audio_file=%s" />',
+ htmlspecialchars($swf_path),
+ urlencode($audio_but_bg_col),
+ urlencode($icon_path),
+ urlencode(html_entity_decode($play_path))
+ );
+
+ $html .= '</object><br />';
+ }
+
+ // html5 audio close
+ $html .= "</audio>\n</div>\n";
+
+ // html5 audio controls
+ $html .= sprintf('<div id="%s_audio_controls">', $image_id) . "\n" .
+ sprintf('<a tabindex="-1" class="captcha_play_button" href="%sid=%s" onclick="return false">',
+ $play_path, uniqid()
+ ) . "\n" .
+ sprintf('<img class="captcha_play_image" height="%d" width="%d" src="%s" alt="Play CAPTCHA Audio" style="border: 0px">', $icon_size, $icon_size, htmlspecialchars($icon_path)) . "\n" .
+ sprintf('<img class="captcha_loading_image rotating" height="%d" width="%d" src="%s" alt="Loading audio" style="display: none">', $icon_size, $icon_size, htmlspecialchars($load_path)) . "\n" .
+ "</a>\n<noscript>Enable Javascript for audio controls</noscript>\n" .
+ "</div>\n";
+
+ // html5 javascript
+ if (!$javascript_init) {
+ $html .= sprintf('<script type="text/javascript" src="%s"></script>', $js_path) . "\n";
+ $javascript_init = true;
+ }
+ $html .= '<script type="text/javascript">' .
+ "$audio_obj = new SecurimageAudio({ audioElement: '{$image_id}_audio', controlsElement: '{$image_id}_audio_controls' });" .
+ "</script>\n";
+ }
+
+ if ( ($parts & Securimage::HTML_ICON_REFRESH) > 0 && $show_refresh_btn) {
+ $icon_path = $securimage_path . '/images/refresh.png';
+ if ($refresh_icon_url) {
+ $icon_path = $refresh_icon_url;
+ }
+ $img_tag = sprintf('<img height="%d" width="%d" src="%s" alt="%s" onclick="this.blur()" style="border: 0px; vertical-align: bottom" />',
+ $icon_size, $icon_size, htmlspecialchars($icon_path), htmlspecialchars($refresh_alt));
+
+ $html .= sprintf('<a tabindex="-1" style="border: 0" href="#" title="%s" onclick="%sdocument.getElementById(\'%s\').src = \'%s\' + Math.random(); this.blur(); return false">%s</a><br />',
+ htmlspecialchars($refresh_title),
+ ($audio_obj) ? "if (typeof window.{$audio_obj} !== 'undefined') {$audio_obj}.refresh(); " : '',
+ $image_id,
+ $show_path,
+ $img_tag
+ );
+ }
+
+ if ($parts == Securimage::HTML_ALL) {
+ $html .= '<div style="clear: both"></div>';
+ }
+
+ if ( ($parts & Securimage::HTML_INPUT_LABEL) > 0 && $show_input) {
+ $html .= sprintf('<label for="%s">%s</label> ',
+ htmlspecialchars($input_id),
+ htmlspecialchars($input_text));
+
+ if (!empty($error_html)) {
+ $html .= $error_html;
+ }
+ }
+
+ if ( ($parts & Securimage::HTML_INPUT) > 0 && $show_input) {
+ $input_attr = '';
+ if (!is_array($input_attrs)) $input_attrs = array();
+ $input_attrs['type'] = 'text';
+ $input_attrs['name'] = $input_name;
+ $input_attrs['id'] = $input_id;
+
+ foreach($input_attrs as $name => $val) {
+ $input_attr .= sprintf('%s="%s" ', $name, htmlspecialchars($val));
+ }
+
+ $html .= sprintf('<input %s/>', $input_attr);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get the time in seconds that it took to solve the captcha.
+ *
+ * @return int The time in seconds from when the code was created, to when it was solved
+ */
+ public function getTimeToSolve()
+ {
+ return $this->_timeToSolve;
+ }
+
+ /**
+ * Set the namespace for the captcha being stored in the session or database.
+ *
+ * Namespaces are useful when multiple captchas need to be displayed on a single page.
+ *
+ * @param string $namespace Namespace value, String consisting of characters "a-zA-Z0-9_-"
+ */
+ public function setNamespace($namespace)
+ {
+ $namespace = preg_replace('/[^a-z0-9-_]/i', '', $namespace);
+ $namespace = substr($namespace, 0, 64);
+
+ if (!empty($namespace)) {
+ $this->namespace = $namespace;
+ } else {
+ $this->namespace = 'default';
+ }
+ }
+
+ /**
+ * Generate an audible captcha in WAV format and send it to the browser with appropriate headers.
+ * Example:
+ *
+ * $img = new Securimage();
+ * $img->outputAudioFile(); // outputs a wav file to the browser
+ * exit;
+ *
+ * @param string $format
+ */
+ public function outputAudioFile($format = null)
+ {
+ set_error_handler(array(&$this, 'errorHandler'));
+
+ if (isset($_SERVER['HTTP_RANGE'])) {
+ $range = true;
+ $rangeId = (isset($_SERVER['HTTP_X_PLAYBACK_SESSION_ID'])) ?
+ 'ID' . $_SERVER['HTTP_X_PLAYBACK_SESSION_ID'] :
+ 'ID' . md5($_SERVER['REQUEST_URI']);
+ $uniq = $rangeId;
+ } else {
+ $uniq = md5(uniqid(microtime()));
+ }
+
+ try {
+ if (!($audio = $this->getAudioData())) {
+ // if previously generated audio not found for current captcha
+ require_once dirname(__FILE__) . '/WavFile.php';
+ $audio = $this->getAudibleCode();
+
+ if (strtolower($format) == 'mp3') {
+ $audio = $this->wavToMp3($audio);
+ }
+
+ $this->saveAudioData($audio);
+ }
+ } catch (Exception $ex) {
+ if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) {
+ fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
+ fclose($fp);
+ }
+
+ $audio = $this->audioError();
+ }
+
+ if ($this->no_session != true) {
+ // close session to make it available to other requests in the event
+ // streaming the audio takes sevaral seconds or more
+ session_write_close();
+ }
+
+ if ($this->canSendHeaders() || $this->send_headers == false) {
+ if ($this->send_headers) {
+ if ($format == 'mp3') {
+ $ext = 'mp3';
+ $type = 'audio/mpeg';
+ } else {
+ $ext = 'wav';
+ $type = 'audio/wav';
+ }
+
+ header('Accept-Ranges: bytes');
+ header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.{$ext}\"");
+ header('Cache-Control: no-store, no-cache, must-revalidate');
+ header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
+ header('Content-type: ' . $type);
+ }
+
+ $this->rangeDownload($audio);
+ } else {
+ echo '<hr /><strong>'
+ .'Failed to generate audio file, content has already been '
+ .'output.<br />This is most likely due to misconfiguration or '
+ .'a PHP error was sent to the browser.</strong>';
+ }
+
+ restore_error_handler();
+
+ if (!$this->no_exit) exit;
+ }
+
+ /**
+ * Output audio data with http range support. Typically this shouldn't be
+ * called directly unless being used with a custom implentation. Use
+ * Securimage::outputAudioFile instead.
+ *
+ * @param string $audio Raw wav or mp3 audio file content
+ */
+ public function rangeDownload($audio)
+ {
+ /* Congratulations Firefox Android/Linux/Windows for being the most
+ * sensible browser of all when streaming HTML5 audio!
+ *
+ * Chrome on Android and iOS on iPad/iPhone both make extra HTTP requests
+ * for the audio whether on WiFi or the mobile network resulting in
+ * multiple downloads of the audio file and wasted bandwidth.
+ *
+ * If I'm doing something wrong in this code or anyone knows why, I'd
+ * love to hear from you.
+ */
+ $audioLength = $size = strlen($audio);
+
+ if (isset($_SERVER['HTTP_RANGE'])) {
+ list( , $range) = explode('=', $_SERVER['HTTP_RANGE']); // bytes=byte-range-set
+ $range = trim($range);
+
+ if (strpos($range, ',') !== false) {
+ // eventually, we should handle requests with multiple ranges
+ // most likely these types of requests will never be sent
+ header('HTTP/1.1 416 Range Not Satisfiable');
+ echo "<h1>Range Not Satisfiable</h1>";
+ exit;
+ } else if (preg_match('/(\d+)-(\d+)/', $range, $match)) {
+ // bytes n - m
+ $range = array(intval($match[1]), intval($match[2]));
+ } else if (preg_match('/(\d+)-$/', $range, $match)) {
+ // bytes n - last byte of file
+ $range = array(intval($match[1]), null);
+ } else if (preg_match('/-(\d+)/', $range, $match)) {
+ // final n bytes of file
+ $range = array($size - intval($match[1]), $size - 1);
+ }
+
+ if ($range[1] === null) $range[1] = $size - 1;
+ $length = $range[1] - $range[0] + 1;
+ $audio = substr($audio, $range[0], $length);
+ $audioLength = strlen($audio);
+
+ header('HTTP/1.1 206 Partial Content');
+ header("Content-Range: bytes {$range[0]}-{$range[1]}/{$size}");
+
+ if ($range[0] < 0 ||$range[1] >= $size || $range[0] >= $size || $range[0] > $range[1]) {
+ header('HTTP/1.1 416 Range Not Satisfiable');
+ echo "<h1>Range Not Satisfiable</h1>";
+ exit;
+ }
+ }
+
+ header('Content-Length: ' . $audioLength);
+
+ echo $audio;
+ }
+
+ /**
+ * Return the code from the session or database (if configured). If none exists or was found, an empty string is returned.
+ *
+ * @param bool $array true to receive an array containing the code and properties, false to receive just the code.
+ * @param bool $returnExisting If true, and the class property *code* is set, it will be returned instead of getting the code from the session or database.
+ * @return array|string Return is an array if $array = true, otherwise a string containing the code
+ */
+ public function getCode($array = false, $returnExisting = false)
+ {
+ $code = array();
+ $time = 0;
+ $disp = 'error';
+
+ if ($returnExisting && strlen($this->code) > 0) {
+ if ($array) {
+ return array(
+ 'code' => $this->code,
+ 'display' => $this->code_display,
+ 'code_display' => $this->code_display,
+ 'time' => 0);
+ } else {
+ return $this->code;
+ }
+ }
+
+ if ($this->no_session != true) {
+ if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
+ trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
+ if ($this->isCodeExpired(
+ $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
+ $code['code'] = $_SESSION['securimage_code_value'][$this->namespace];
+ $code['time'] = $_SESSION['securimage_code_ctime'][$this->namespace];
+ $code['display'] = $_SESSION['securimage_code_disp'] [$this->namespace];
+ }
+ }
+ }
+
+ if (empty($code) && $this->use_database) {
+ // no code in session - may mean user has cookies turned off
+ $this->openDatabase();
+ $code = $this->getCodeFromDatabase();
+
+ if (!empty($code)) {
+ $code['display'] = $code['code_disp'];
+ unset($code['code_disp']);
+ }
+ } else { /* no code stored in session or sqlite database, validation will fail */ }
+
+ if ($array == true) {
+ return $code;
+ } else {
+ return $code['code'];
+ }
+ }
+
+ /**
+ * The main image drawing routing, responsible for constructing the entire image and serving it
+ */
+ protected function doImage()
+ {
+ if( ($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor')) {
+ $imagecreate = 'imagecreatetruecolor';
+ } else {
+ $imagecreate = 'imagecreate';
+ }
+
+ $this->im = $imagecreate($this->image_width, $this->image_height);
+ $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
+
+ $this->allocateColors();
+ imagepalettecopy($this->tmpimg, $this->im);
+
+ $this->setBackground();
+
+ $code = '';
+
+ if ($this->getCaptchaId(false) !== null) {
+ // a captcha Id was supplied
+
+ // check to see if a display_value for the captcha image was set
+ if (is_string($this->display_value) && strlen($this->display_value) > 0) {
+ $this->code_display = $this->display_value;
+ $this->code = ($this->case_sensitive) ?
+ $this->display_value :
+ strtolower($this->display_value);
+ $code = $this->code;
+ } else if ($this->openDatabase()) {
+ // no display_value, check the database for existing captchaId
+ $code = $this->getCodeFromDatabase();
+
+ // got back a result from the database with a valid code for captchaId
+ if (is_array($code)) {
+ $this->code = $code['code'];
+ $this->code_display = $code['code_disp'];
+ $code = $code['code'];
+ }
+ }
+ }
+
+ if ($code == '') {
+ // if the code was not set using display_value or was not found in
+ // the database, create a new code
+ $this->createCode();
+ }
+
+ if ($this->noise_level > 0) {
+ $this->drawNoise();
+ }
+
+ $this->drawWord();
+
+ if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
+ $this->distortedCopy();
+ }
+
+ if ($this->num_lines > 0) {
+ $this->drawLines();
+ }
+
+ if (trim($this->image_signature) != '') {
+ $this->addSignature();
+ }
+
+ $this->output();
+ }
+
+ /**
+ * Allocate the colors to be used for the image
+ */
+ protected function allocateColors()
+ {
+ // allocate bg color first for imagecreate
+ $this->gdbgcolor = imagecolorallocate($this->im,
+ $this->image_bg_color->r,
+ $this->image_bg_color->g,
+ $this->image_bg_color->b);
+
+ $alpha = intval($this->text_transparency_percentage / 100 * 127);
+
+ if ($this->use_transparent_text == true) {
+ $this->gdtextcolor = imagecolorallocatealpha($this->im,
+ $this->text_color->r,
+ $this->text_color->g,
+ $this->text_color->b,
+ $alpha);
+ $this->gdlinecolor = imagecolorallocatealpha($this->im,
+ $this->line_color->r,
+ $this->line_color->g,
+ $this->line_color->b,
+ $alpha);
+ $this->gdnoisecolor = imagecolorallocatealpha($this->im,
+ $this->noise_color->r,
+ $this->noise_color->g,
+ $this->noise_color->b,
+ $alpha);
+ } else {
+ $this->gdtextcolor = imagecolorallocate($this->im,
+ $this->text_color->r,
+ $this->text_color->g,
+ $this->text_color->b);
+ $this->gdlinecolor = imagecolorallocate($this->im,
+ $this->line_color->r,
+ $this->line_color->g,
+ $this->line_color->b);
+ $this->gdnoisecolor = imagecolorallocate($this->im,
+ $this->noise_color->r,
+ $this->noise_color->g,
+ $this->noise_color->b);
+ }
+
+ $this->gdsignaturecolor = imagecolorallocate($this->im,
+ $this->signature_color->r,
+ $this->signature_color->g,
+ $this->signature_color->b);
+
+ }
+
+ /**
+ * The the background color, or background image to be used
+ */
+ protected function setBackground()
+ {
+ // set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
+ imagefilledrectangle($this->im, 0, 0,
+ $this->image_width, $this->image_height,
+ $this->gdbgcolor);
+ imagefilledrectangle($this->tmpimg, 0, 0,
+ $this->image_width * $this->iscale, $this->image_height * $this->iscale,
+ $this->gdbgcolor);
+
+ if ($this->bgimg == '') {
+ if ($this->background_directory != null &&
+ is_dir($this->background_directory) &&
+ is_readable($this->background_directory))
+ {
+ $img = $this->getBackgroundFromDirectory();
+ if ($img != false) {
+ $this->bgimg = $img;
+ }
+ }
+ }
+
+ if ($this->bgimg == '') {
+ return;
+ }
+
+ $dat = @getimagesize($this->bgimg);
+ if($dat == false) {
+ return;
+ }
+
+ switch($dat[2]) {
+ case 1: $newim = @imagecreatefromgif($this->bgimg); break;
+ case 2: $newim = @imagecreatefromjpeg($this->bgimg); break;
+ case 3: $newim = @imagecreatefrompng($this->bgimg); break;
+ default: return;
+ }
+
+ if(!$newim) return;
+
+ imagecopyresized($this->im, $newim, 0, 0, 0, 0,
+ $this->image_width, $this->image_height,
+ imagesx($newim), imagesy($newim));
+ }
+
+ /**
+ * Scan the directory for a background image to use
+ * @return string|bool
+ */
+ protected function getBackgroundFromDirectory()
+ {
+ $images = array();
+
+ if ( ($dh = opendir($this->background_directory)) !== false) {
+ while (($file = readdir($dh)) !== false) {
+ if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
+ }
+
+ closedir($dh);
+
+ if (sizeof($images) > 0) {
+ return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This method generates a new captcha code.
+ *
+ * Generates a random captcha code based on *charset*, math problem, or captcha from the wordlist and saves the value to the session and/or database.
+ */
+ public function createCode()
+ {
+ $this->code = false;
+
+ switch($this->captcha_type) {
+ case self::SI_CAPTCHA_MATHEMATIC:
+ {
+ do {
+ $signs = array('+', '-', 'x');
+ $left = mt_rand(1, 10);
+ $right = mt_rand(1, 5);
+ $sign = $signs[mt_rand(0, 2)];
+
+ switch($sign) {
+ case 'x': $c = $left * $right; break;
+ case '-': $c = $left - $right; break;
+ default: $c = $left + $right; break;
+ }
+ } while ($c <= 0); // no negative #'s or 0
+
+ $this->code = "$c";
+ $this->code_display = "$left $sign $right";
+ break;
+ }
+
+ case self::SI_CAPTCHA_WORDS:
+ $words = $this->readCodeFromFile(2);
+ $this->code = implode(' ', $words);
+ $this->code_display = $this->code;
+ break;
+
+ default:
+ {
+ if ($this->use_wordlist && is_readable($this->wordlist_file)) {
+ $this->code = $this->readCodeFromFile();
+ }
+
+ if ($this->code == false) {
+ $this->code = $this->generateCode($this->code_length);
+ }
+
+ $this->code_display = $this->code;
+ $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code);
+ } // default
+ }
+
+ $this->saveData();
+ }
+
+ /**
+ * Draws the captcha code on the image
+ */
+ protected function drawWord()
+ {
+ $width2 = $this->image_width * $this->iscale;
+ $height2 = $this->image_height * $this->iscale;
+ $ratio = ($this->font_ratio) ? $this->font_ratio : 0.4;
+
+ if ((float)$ratio < 0.1 || (float)$ratio >= 1) {
+ $ratio = 0.4;
+ }
+
+ if (!is_readable($this->ttf_file)) {
+ imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
+ } else {
+ if ($this->perturbation > 0) {
+ $font_size = $height2 * $ratio;
+ $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
+ $tx = $bb[4] - $bb[0];
+ $ty = $bb[5] - $bb[1];
+ $x = floor($width2 / 2 - $tx / 2 - $bb[0]);
+ $y = round($height2 / 2 - $ty / 2 - $bb[1]);
+
+ imagettftext($this->tmpimg, $font_size, 0, (int)$x, (int)$y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
+ } else {
+ $font_size = $this->image_height * $ratio;
+ $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
+ $tx = $bb[4] - $bb[0];
+ $ty = $bb[5] - $bb[1];
+ $x = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
+ $y = round($this->image_height / 2 - $ty / 2 - $bb[1]);
+
+ imagettftext($this->im, $font_size, 0, (int)$x, (int)$y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
+ }
+ }
+
+ // DEBUG
+ //$this->im = $this->tmpimg;
+ //$this->output();
+
+ }
+
+ /**
+ * Copies the captcha image to the final image with distortion applied
+ */
+ protected function distortedCopy()
+ {
+ $numpoles = 3; // distortion factor
+ // make array of poles AKA attractor points
+ for ($i = 0; $i < $numpoles; ++ $i) {
+ $px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8);
+ $py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
+ $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
+ $tmp = ((- $this->frand()) * 0.15) - .15;
+ $amp[$i] = $this->perturbation * $tmp;
+ }
+
+ $bgCol = imagecolorat($this->tmpimg, 0, 0);
+ $width2 = $this->iscale * $this->image_width;
+ $height2 = $this->iscale * $this->image_height;
+ imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
+ // loop over $img pixels, take pixels from $tmpimg with distortion field
+ for ($ix = 0; $ix < $this->image_width; ++ $ix) {
+ for ($iy = 0; $iy < $this->image_height; ++ $iy) {
+ $x = $ix;
+ $y = $iy;
+ for ($i = 0; $i < $numpoles; ++ $i) {
+ $dx = $ix - $px[$i];
+ $dy = $iy - $py[$i];
+ if ($dx == 0 && $dy == 0) {
+ continue;
+ }
+ $r = sqrt($dx * $dx + $dy * $dy);
+ if ($r > $rad[$i]) {
+ continue;
+ }
+ $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
+ $x += $dx * $rscale;
+ $y += $dy * $rscale;
+ }
+ $c = $bgCol;
+ $x *= $this->iscale;
+ $y *= $this->iscale;
+ if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
+ $c = imagecolorat($this->tmpimg, $x, $y);
+ }
+ if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
+ imagesetpixel($this->im, $ix, $iy, $c);
+ }
+ }
+ }
+ }
+
+ /**
+ * Draws distorted lines on the image
+ */
+ protected function drawLines()
+ {
+ for ($line = 0; $line < $this->num_lines; ++ $line) {
+ $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
+ $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
+ $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
+
+ $theta = ($this->frand() - 0.5) * M_PI * 0.7;
+ $w = $this->image_width;
+ $len = mt_rand($w * 0.4, $w * 0.7);
+ $lwid = mt_rand(0, 2);
+
+ $k = $this->frand() * 0.6 + 0.2;
+ $k = $k * $k * 0.5;
+ $phi = $this->frand() * 6.28;
+ $step = 0.5;
+ $dx = $step * cos($theta);
+ $dy = $step * sin($theta);
+ $n = $len / $step;
+ $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
+ $x0 = $x - 0.5 * $len * cos($theta);
+ $y0 = $y - 0.5 * $len * sin($theta);
+
+ $ldx = round(- $dy * $lwid);
+ $ldy = round($dx * $lwid);
+
+ for ($i = 0; $i < $n; ++ $i) {
+ $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
+ $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
+ imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
+ }
+ }
+ }
+
+ /**
+ * Draws random noise on the image
+ */
+ protected function drawNoise()
+ {
+ if ($this->noise_level > 10) {
+ $noise_level = 10;
+ } else {
+ $noise_level = $this->noise_level;
+ }
+
+ $t0 = microtime(true);
+
+ $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
+
+ $points = $this->image_width * $this->image_height * $this->iscale;
+ $height = $this->image_height * $this->iscale;
+ $width = $this->image_width * $this->iscale;
+ for ($i = 0; $i < $noise_level; ++$i) {
+ $x = mt_rand(10, $width);
+ $y = mt_rand(10, $height);
+ $size = mt_rand(7, 10);
+ if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
+ imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
+ }
+
+ $t1 = microtime(true);
+
+ $t = $t1 - $t0;
+
+ /*
+ // DEBUG
+ imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
+ header('content-type: image/png');
+ imagepng($this->tmpimg);
+ exit;
+ */
+ }
+
+ /**
+ * Print signature text on image
+ */
+ protected function addSignature()
+ {
+ $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
+ $textlen = $bbox[2] - $bbox[0];
+ $x = $this->image_width - $textlen - 5;
+ $y = $this->image_height - 3;
+
+ imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
+ }
+
+ /**
+ * Sends the appropriate image and cache headers and outputs image to the browser
+ */
+ protected function output()
+ {
+ if ($this->canSendHeaders() || $this->send_headers == false) {
+ if ($this->send_headers) {
+ // only send the content-type headers if no headers have been output
+ // this will ease debugging on misconfigured servers where warnings
+ // may have been output which break the image and prevent easily viewing
+ // source to see the error.
+ header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
+ header("Cache-Control: no-store, no-cache, must-revalidate");
+ header("Cache-Control: post-check=0, pre-check=0", false);
+ header("Pragma: no-cache");
+ }
+
+ switch ($this->image_type) {
+ case self::SI_IMAGE_JPEG:
+ if ($this->send_headers) header("Content-Type: image/jpeg");
+ imagejpeg($this->im, null, 90);
+ break;
+ case self::SI_IMAGE_GIF:
+ if ($this->send_headers) header("Content-Type: image/gif");
+ imagegif($this->im);
+ break;
+ default:
+ if ($this->send_headers) header("Content-Type: image/png");
+ imagepng($this->im);
+ break;
+ }
+ } else {
+ echo '<hr /><strong>'
+ .'Failed to generate captcha image, content has already been '
+ .'output.<br />This is most likely due to misconfiguration or '
+ .'a PHP error was sent to the browser.</strong>';
+ }
+
+ imagedestroy($this->im);
+ restore_error_handler();
+
+ if (!$this->no_exit) exit;
+ }
+
+ /**
+ * Generates an audio captcha in WAV format
+ *
+ * @return string The audio representation of the captcha in Wav format
+ */
+ protected function getAudibleCode()
+ {
+ $letters = array();
+ $code = $this->getCode(true, true);
+
+ if (empty($code) || $code['code'] == '') {
+ if (strlen($this->display_value) > 0) {
+ $code = array('code' => $this->display_value, 'display' => $this->display_value);
+ } else {
+ $this->createCode();
+ $code = $this->getCode(true);
+ }
+ }
+
+ if (empty($code)) {
+ $error = 'Failed to get audible code (are database settings correct?). Check the error log for details';
+ trigger_error($error, E_USER_WARNING);
+ throw new Exception($error);
+ }
+
+ if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) {
+ $math = true;
+
+ $left = $eq[1];
+ $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
+ $right = $eq[3];
+
+ $letters = array($left, $sign, $right);
+ } else {
+ $math = false;
+
+ $length = strlen($code['display']);
+
+ for($i = 0; $i < $length; ++$i) {
+ $letter = $code['display']{$i};
+ $letters[] = $letter;
+ }
+ }
+
+ try {
+ return $this->generateWAV($letters);
+ } catch(Exception $ex) {
+ throw $ex;
+ }
+ }
+
+ /**
+ * Gets a captcha code from a file containing a list of words.
+ *
+ * Seek to a random offset in the file and reads a block of data and returns a line from the file.
+ *
+ * @param int $numWords Number of words (lines) to read from the file
+ * @return string|array|bool Returns a string if only one word is to be read, or an array of words
+ */
+ protected function readCodeFromFile($numWords = 1)
+ {
+ $strpos_func = 'strpos';
+ $strlen_func = 'strlen';
+ $substr_func = 'substr';
+ $strtolower_func = 'strtolower';
+ $mb_support = false;
+
+ if (!empty($this->wordlist_file_encoding)) {
+ if (!extension_loaded('mbstring')) {
+ trigger_error("wordlist_file_encoding option set, but PHP does not have mbstring support", E_USER_WARNING);
+ return false;
+ }
+
+ // emits PHP warning if not supported
+ $mb_support = mb_internal_encoding($this->wordlist_file_encoding);
+
+ if (!$mb_support) {
+ return false;
+ }
+
+ $strpos_func = 'mb_strpos';
+ $strlen_func = 'mb_strlen';
+ $substr_func = 'mb_substr';
+ $strtolower_func = 'mb_strtolower';
+ }
+
+ $fp = fopen($this->wordlist_file, 'rb');
+ if (!$fp) return false;
+
+ $fsize = filesize($this->wordlist_file);
+ if ($fsize < 128) return false; // too small of a list to be effective
+
+ if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1;
+
+ $words = array();
+ $i = 0;
+ do {
+ fseek($fp, mt_rand(0, $fsize - 128), SEEK_SET); // seek to a random position of file from 0 to filesize-128
+ $data = fread($fp, 128); // read a chunk from our random position
+
+ if ($mb_support !== false) {
+ $data = mb_ereg_replace("\r?\n", "\n", $data);
+ } else {
+ $data = preg_replace("/\r?\n/", "\n", $data);
+ }
+
+ $start = @$strpos_func($data, "\n", mt_rand(0, 56)) + 1; // random start position
+ $end = @$strpos_func($data, "\n", $start); // find end of word
+
+ if ($start === false) {
+ // picked start position at end of file
+ continue;
+ } else if ($end === false) {
+ $end = $strlen_func($data);
+ }
+
+ $word = $strtolower_func($substr_func($data, $start, $end - $start)); // return a line of the file
+
+ if ($mb_support) {
+ // convert to UTF-8 for imagettftext
+ $word = mb_convert_encoding($word, 'UTF-8', $this->wordlist_file_encoding);
+ }
+
+ $words[] = $word;
+ } while (++$i < $numWords);
+
+ fclose($fp);
+
+ if ($numWords < 2) {
+ return $words[0];
+ } else {
+ return $words;
+ }
+ }
+
+ /**
+ * Generates a random captcha code from the set character set
+ *
+ * @see Securimage::$charset Charset option
+ * @return string A randomly generated CAPTCHA code
+ */
+ protected function generateCode()
+ {
+ $code = '';
+
+ if (function_exists('mb_strlen')) {
+ for($i = 1, $cslen = mb_strlen($this->charset, 'UTF-8'); $i <= $this->code_length; ++$i) {
+ $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
+ }
+ } else {
+ for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
+ $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
+ }
+ }
+
+ return $code;
+ }
+
+ /**
+ * Validate a code supplied by the user
+ *
+ * Checks the entered code against the value stored in the session and/or database (if configured). Handles case sensitivity.
+ * Also removes the code from session/database if the code was entered correctly to prevent re-use attack.
+ *
+ * This function does not return a value.
+ *
+ * @see Securimage::$correct_code 'correct_code' property
+ */
+ protected function validate()
+ {
+ if (!is_string($this->code) || strlen($this->code) == 0) {
+ $code = $this->getCode(true);
+ // returns stored code, or an empty string if no stored code was found
+ // checks the session and database if enabled
+ } else {
+ $code = $this->code;
+ }
+
+ if (is_array($code)) {
+ if (!empty($code)) {
+ $ctime = $code['time'];
+ $code = $code['code'];
+
+ $this->_timeToSolve = time() - $ctime;
+ } else {
+ $code = '';
+ }
+ }
+
+ if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
+ // case sensitive was set from securimage_show.php but not in class
+ // the code saved in the session has capitals so set case sensitive to true
+ $this->case_sensitive = true;
+ }
+
+ $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
+ : strtolower($this->code_entered))
+ );
+ $this->correct_code = false;
+
+ if ($code != '') {
+ if (strpos($code, ' ') !== false) {
+ // for multi word captchas, remove more than once space from input
+ $code_entered = preg_replace('/\s+/', ' ', $code_entered);
+ $code_entered = strtolower($code_entered);
+ }
+
+ if ((string)$code === (string)$code_entered) {
+ $this->correct_code = true;
+ if ($this->no_session != true) {
+ $_SESSION['securimage_code_disp'] [$this->namespace] = '';
+ $_SESSION['securimage_code_value'][$this->namespace] = '';
+ $_SESSION['securimage_code_ctime'][$this->namespace] = '';
+ $_SESSION['securimage_code_audio'][$this->namespace] = '';
+ }
+ $this->clearCodeFromDatabase();
+ }
+ }
+ }
+
+ /**
+ * Save CAPTCHA data to session and database (if configured)
+ */
+ protected function saveData()
+ {
+ if ($this->no_session != true) {
+ if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) {
+ // fix for migration from v2 - v3
+ unset($_SESSION['securimage_code_value']);
+ unset($_SESSION['securimage_code_ctime']);
+ }
+
+ $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
+ $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
+ $_SESSION['securimage_code_ctime'][$this->namespace] = time();
+ $_SESSION['securimage_code_audio'][$this->namespace] = null; // clear previous audio, if set
+ }
+
+ if ($this->use_database) {
+ $this->saveCodeToDatabase();
+ }
+ }
+
+ /**
+ * Save audio data to session and/or the configured database
+ *
+ * @param string $data The CAPTCHA audio data
+ */
+ protected function saveAudioData($data)
+ {
+ if ($this->no_session != true) {
+ $_SESSION['securimage_code_audio'][$this->namespace] = $data;
+ }
+
+ if ($this->use_database) {
+ $this->saveAudioToDatabase($data);
+ }
+ }
+
+ /**
+ * Gets audio file contents from the session or database
+ *
+ * @return string|boolean Audio contents on success, or false if no audio found in session or DB
+ */
+ protected function getAudioData()
+ {
+ if ($this->no_session != true) {
+ if (isset($_SESSION['securimage_code_audio'][$this->namespace])) {
+ return $_SESSION['securimage_code_audio'][$this->namespace];
+ }
+ }
+
+ if ($this->use_database) {
+ $this->openDatabase();
+ $code = $this->getCodeFromDatabase();
+
+ if (!empty($code['audio_data'])) {
+ return $code['audio_data'];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Saves the CAPTCHA data to the configured database.
+ */
+ protected function saveCodeToDatabase()
+ {
+ $success = false;
+ $this->openDatabase();
+
+ if ($this->use_database && $this->pdo_conn) {
+ $id = $this->getCaptchaId(false);
+ $ip = $_SERVER['REMOTE_ADDR'];
+
+ if (empty($id)) {
+ $id = $ip;
+ }
+
+ $time = time();
+ $code = $this->code;
+ $code_disp = $this->code_display;
+
+ // This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
+ // Clears previous captcha for this client from database so we can do a straight insert
+ // without having to do INSERT ... ON DUPLICATE KEY or a find/update
+ $this->clearCodeFromDatabase();
+
+ $query = "INSERT INTO {$this->database_table} ("
+ ."id, code, code_display, namespace, created) "
+ ."VALUES(?, ?, ?, ?, ?)";
+
+ $stmt = $this->pdo_conn->prepare($query);
+ $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
+
+ if (!$success) {
+ $err = $stmt->errorInfo();
+ $error = "Failed to insert code into database. {$err[1]}: {$err[2]}.";
+
+ if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
+ $err14 = ($err[1] == 14);
+ if ($err14) $error .= sprintf(" Ensure database directory and file are writeable by user '%s' (%d).",
+ get_current_user(), getmyuid());
+ }
+
+ trigger_error($error, E_USER_WARNING);
+ }
+ }
+
+ return $success !== false;
+ }
+
+ /**
+ * Saves CAPTCHA audio to the configured database
+ *
+ * @param string $data Audio data
+ * @return boolean true on success, false on failure
+ */
+ protected function saveAudioToDatabase($data)
+ {
+ $success = false;
+ $this->openDatabase();
+
+ if ($this->use_database && $this->pdo_conn) {
+ $id = $this->getCaptchaId(false);
+ $ip = $_SERVER['REMOTE_ADDR'];
+ $ns = $this->namespace;
+
+ if (empty($id)) {
+ $id = $ip;
+ }
+
+ $query = "UPDATE {$this->database_table} SET audio_data = :audioData WHERE id = :id AND namespace = :namespace";
+ $stmt = $this->pdo_conn->prepare($query);
+ $stmt->bindParam(':audioData', $data, PDO::PARAM_LOB);
+ $stmt->bindParam(':id', $id);
+ $stmt->bindParam(':namespace', $ns);
+ $success = $stmt->execute();
+ }
+
+ return $success !== false;
+ }
+
+ /**
+ * Opens a connection to the configured database.
+ *
+ * @see Securimage::$use_database Use database
+ * @see Securimage::$database_driver Database driver
+ * @see Securimage::$pdo_conn pdo_conn
+ * @return bool true if the database connection was successful, false if not
+ */
+ protected function openDatabase()
+ {
+ $this->pdo_conn = false;
+
+ if ($this->use_database) {
+ $pdo_extension = 'PDO_' . strtoupper($this->database_driver);
+
+ if (!extension_loaded($pdo_extension)) {
+ trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
+ return false;
+ }
+ }
+
+ if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
+ if (!file_exists($this->database_file)) {
+ $fp = fopen($this->database_file, 'w+');
+ if (!$fp) {
+ $err = error_get_last();
+ trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
+ return false;
+ }
+ fclose($fp);
+ chmod($this->database_file, 0666);
+ } else if (!is_writeable($this->database_file)) {
+ trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING);
+ return false;
+ }
+ }
+
+ try {
+ $dsn = $this->getDsn();
+
+ $options = array();
+ $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
+ } catch (PDOException $pdoex) {
+ trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
+ return false;
+ } catch (Exception $ex) {
+ trigger_error($ex->getMessage(), E_USER_WARNING);
+ return false;
+ }
+
+ try {
+ if (!$this->skip_table_check && !$this->checkTablesExist()) {
+ // create tables...
+ $this->createDatabaseTables();
+ }
+ } catch (Exception $ex) {
+ trigger_error($ex->getMessage(), E_USER_WARNING);
+ $this->pdo_conn = false;
+ return false;
+ }
+
+ if (mt_rand(0, 100) / 100.0 == 1.0) {
+ $this->purgeOldCodesFromDatabase();
+ }
+
+ return $this->pdo_conn;
+ }
+
+ /**
+ * Get the PDO DSN string for connecting to the database
+ *
+ * @see Securimage::$database_driver Database driver
+ * @throws Exception If database specific options are not configured
+ * @return string The DSN for connecting to the database
+ */
+ protected function getDsn()
+ {
+ $dsn = sprintf('%s:', $this->database_driver);
+
+ switch($this->database_driver) {
+ case self::SI_DRIVER_SQLITE3:
+ $dsn .= $this->database_file;
+ break;
+
+ case self::SI_DRIVER_MYSQL:
+ case self::SI_DRIVER_PGSQL:
+ if (empty($this->database_host)) {
+ throw new Exception('Securimage::database_host is not set');
+ } else if (empty($this->database_name)) {
+ throw new Exception('Securimage::database_name is not set');
+ }
+
+ $dsn .= sprintf('host=%s;dbname=%s',
+ $this->database_host,
+ $this->database_name);
+ break;
+
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * Checks if the necessary database tables for storing captcha codes exist
+ *
+ * @throws Exception If the table check failed for some reason
+ * @return boolean true if the database do exist, false if not
+ */
+ protected function checkTablesExist()
+ {
+ $table = $this->pdo_conn->quote($this->database_table);
+
+ switch($this->database_driver) {
+ case self::SI_DRIVER_SQLITE3:
+ // query row count for sqlite, PRAGMA queries seem to return no
+ // rowCount using PDO even if there are rows returned
+ $query = "SELECT COUNT(id) FROM $table";
+ break;
+
+ case self::SI_DRIVER_MYSQL:
+ $query = "SHOW TABLES LIKE $table";
+ break;
+
+ case self::SI_DRIVER_PGSQL:
+ $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;";
+ break;
+ }
+
+ $result = $this->pdo_conn->query($query);
+
+ if (!$result) {
+ $err = $this->pdo_conn->errorInfo();
+
+ if ($this->database_driver == self::SI_DRIVER_SQLITE3 &&
+ $err[1] === 1 && strpos($err[2], 'no such table') !== false)
+ {
+ return false;
+ }
+
+ throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}");
+ } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
+ // successful here regardless of row count for sqlite
+ return true;
+ } else if ($result->rowCount() == 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Create the necessary databaes table for storing captcha codes.
+ *
+ * Based on the database adapter used, the tables will created in the existing connection.
+ *
+ * @see Securimage::$database_driver Database driver
+ * @return boolean true if the tables were created, false if not
+ */
+ protected function createDatabaseTables()
+ {
+ $queries = array();
+
+ switch($this->database_driver) {
+ case self::SI_DRIVER_SQLITE3:
+ $queries[] = "CREATE TABLE \"{$this->database_table}\" (
+ id VARCHAR(40),
+ namespace VARCHAR(32) NOT NULL,
+ code VARCHAR(32) NOT NULL,
+ code_display VARCHAR(32) NOT NULL,
+ created INTEGER NOT NULL,
+ audio_data BLOB NULL,
+ PRIMARY KEY(id, namespace)
+ )";
+
+ $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)";
+ break;
+
+ case self::SI_DRIVER_MYSQL:
+ $queries[] = "CREATE TABLE `{$this->database_table}` (
+ `id` VARCHAR(40) NOT NULL,
+ `namespace` VARCHAR(32) NOT NULL,
+ `code` VARCHAR(32) NOT NULL,
+ `code_display` VARCHAR(32) NOT NULL,
+ `created` INT NOT NULL,
+ `audio_data` MEDIUMBLOB NULL,
+ PRIMARY KEY(id, namespace),
+ INDEX(created)
+ )";
+ break;
+
+ case self::SI_DRIVER_PGSQL:
+ $queries[] = "CREATE TABLE {$this->database_table} (
+ id character varying(40) NOT NULL,
+ namespace character varying(32) NOT NULL,
+ code character varying(32) NOT NULL,
+ code_display character varying(32) NOT NULL,
+ created integer NOT NULL,
+ audio_data bytea NULL,
+ CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace)
+ )";
+
+ $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);";
+ break;
+ }
+
+ $this->pdo_conn->beginTransaction();
+
+ foreach($queries as $query) {
+ $result = $this->pdo_conn->query($query);
+
+ if (!$result) {
+ $err = $this->pdo_conn->errorInfo();
+ trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING);
+ $this->pdo_conn->rollBack();
+ $this->pdo_conn = false;
+ return false;
+ }
+ }
+
+ $this->pdo_conn->commit();
+
+ return true;
+ }
+
+ /**
+ * Retrieves a stored code from the database for based on the captchaId or
+ * IP address if captcha ID not used.
+ *
+ * @return string|array Empty string if no code was found or has expired,
+ * otherwise returns array of code information.
+ */
+ protected function getCodeFromDatabase()
+ {
+ $code = '';
+
+ if ($this->use_database == true && $this->pdo_conn) {
+ if (Securimage::$_captchaId !== null) {
+ $query = "SELECT * FROM {$this->database_table} WHERE id = ?";
+ $stmt = $this->pdo_conn->prepare($query);
+ $result = $stmt->execute(array(Securimage::$_captchaId));
+ } else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ $ns = $this->namespace;
+
+ // ip is stored in id column when no captchaId
+ $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?";
+ $stmt = $this->pdo_conn->prepare($query);
+ $result = $stmt->execute(array($ip, $ns));
+ }
+
+ if (!$result) {
+ $err = $this->pdo_conn->errorInfo();
+ trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING);
+ } else {
+ if ( ($row = $stmt->fetch()) !== false ) {
+ if (false == $this->isCodeExpired($row['created'])) {
+ if ($this->database_driver == self::SI_DRIVER_PGSQL && is_resource($row['audio_data'])) {
+ // pg bytea data returned as stream resource
+ $data = '';
+ while (!feof($row['audio_data'])) {
+ $data .= fgets($row['audio_data']);
+ }
+ $row['audio_data'] = $data;
+ }
+ $code = array(
+ 'code' => $row['code'],
+ 'code_disp' => $row['code_display'],
+ 'time' => $row['created'],
+ 'audio_data' => $row['audio_data'],
+ );
+ }
+ }
+ }
+ }
+
+ return $code;
+ }
+
+ /**
+ * Remove a stored code from the database based on captchaId or IP address.
+ */
+ protected function clearCodeFromDatabase()
+ {
+ if ($this->pdo_conn) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ $ns = $this->pdo_conn->quote($this->namespace);
+ $id = Securimage::$_captchaId;
+
+ if (empty($id)) {
+ $id = $ip; // if no captchaId set, IP address is captchaId.
+ }
+
+ $id = $this->pdo_conn->quote($id);
+
+ $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s",
+ $this->database_table, $id, $ns);
+
+ $result = $this->pdo_conn->query($query);
+ if (!$result) {
+ trigger_error("Failed to delete code from database.", E_USER_WARNING);
+ }
+ }
+ }
+
+ /**
+ * Deletes old (expired) codes from the database
+ */
+ protected function purgeOldCodesFromDatabase()
+ {
+ if ($this->use_database && $this->pdo_conn) {
+ $now = time();
+ $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
+
+ $query = sprintf("DELETE FROM %s WHERE %s - created > %s",
+ $this->database_table,
+ $now,
+ $this->pdo_conn->quote("$limit", PDO::PARAM_INT));
+
+ $result = $this->pdo_conn->query($query);
+ }
+ }
+
+ /**
+ * Checks to see if the captcha code has expired and can no longer be used.
+ *
+ * @see Securimage::$expiry_time expiry_time
+ * @param int $creation_time The Unix timestamp of when the captcha code was created
+ * @return bool true if the code is expired, false if it is still valid
+ */
+ protected function isCodeExpired($creation_time)
+ {
+ $expired = true;
+
+ if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
+ $expired = false;
+ } else if (time() - $creation_time < $this->expiry_time) {
+ $expired = false;
+ }
+
+ return $expired;
+ }
+
+ /**
+ * Generate a wav file given the $letters in the code
+ *
+ * @param array $letters The letters making up the captcha
+ * @return string The audio content in WAV format
+ */
+ protected function generateWAV($letters)
+ {
+ $wavCaptcha = new WavFile();
+ $first = true; // reading first wav file
+
+ if ($this->audio_use_sox && !is_executable($this->sox_binary_path)) {
+ throw new Exception("Path to SoX binary is incorrect or not executable");
+ }
+
+ foreach ($letters as $letter) {
+ $letter = strtoupper($letter);
+
+ try {
+ $letter_file = realpath($this->audio_path) . DIRECTORY_SEPARATOR . $letter . '.wav';
+
+ if ($this->audio_use_sox) {
+ $sox_cmd = sprintf("%s %s -t wav - %s",
+ $this->sox_binary_path,
+ $letter_file,
+ $this->getSoxEffectChain());
+
+ $data = `$sox_cmd`;
+
+ $l = new WavFile();
+ $l->setIgnoreChunkSizes(true);
+ $l->setWavData($data);
+ } else {
+ $l = new WavFile($letter_file);
+ }
+
+ if ($first) {
+ // set sample rate, bits/sample, and # of channels for file based on first letter
+ $wavCaptcha->setSampleRate($l->getSampleRate())
+ ->setBitsPerSample($l->getBitsPerSample())
+ ->setNumChannels($l->getNumChannels());
+ $first = false;
+ }
+
+ // append letter to the captcha audio
+ $wavCaptcha->appendWav($l);
+
+ // random length of silence between $audio_gap_min and $audio_gap_max
+ if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) {
+ $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 );
+ }
+ } catch (Exception $ex) {
+ // failed to open file, or the wav file is broken or not supported
+ // 2 wav files were not compatible, different # channels, bits/sample, or sample rate
+ throw new Exception("Error generating audio captcha on letter '$letter': " . $ex->getMessage());
+ }
+ }
+
+ /********* Set up audio filters *****************************/
+ $filters = array();
+
+ if ($this->audio_use_noise == true) {
+ // use background audio - find random file
+ $wavNoise = false;
+ $randOffset = 0;
+
+ /*
+ // uncomment to try experimental SoX noise generation.
+ // warning: sounds may be considered annoying
+ if ($this->audio_use_sox) {
+ $duration = $wavCaptcha->getDataSize() / ($wavCaptcha->getBitsPerSample() / 8) /
+ $wavCaptcha->getNumChannels() / $wavCaptcha->getSampleRate();
+ $duration = round($duration, 2);
+ $wavNoise = new WavFile();
+ $wavNoise->setIgnoreChunkSizes(true);
+ $noiseData = $this->getSoxNoiseData($duration,
+ $wavCaptcha->getNumChannels(),
+ $wavCaptcha->getSampleRate(),
+ $wavCaptcha->getBitsPerSample());
+ $wavNoise->setWavData($noiseData, true);
+
+ } else
+ */
+ if ( ($noiseFile = $this->getRandomNoiseFile()) !== false) {
+ try {
+ $wavNoise = new WavFile($noiseFile, false);
+ } catch(Exception $ex) {
+ throw $ex;
+ }
+
+ // start at a random offset from the beginning of the wavfile
+ // in order to add more randomness
+
+ $randOffset = 0;
+
+ if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) {
+ $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks());
+ $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign());
+ } else {
+ $wavNoise->readWavData();
+ $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1);
+ }
+ }
+
+ if ($wavNoise !== false) {
+ $mixOpts = array('wav' => $wavNoise,
+ 'loop' => true,
+ 'blockOffset' => $randOffset);
+
+ $filters[WavFile::FILTER_MIX] = $mixOpts;
+ $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization;
+ }
+ }
+
+ if ($this->degrade_audio == true) {
+ // add random noise.
+ // any noise level below 95% is intensely distorted and not pleasant to the ear
+ $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0;
+ }
+
+ if (!empty($filters)) {
+ $wavCaptcha->filter($filters); // apply filters to captcha audio
+ }
+
+ return $wavCaptcha->__toString();
+ }
+
+ /**
+ * Gets and returns the path to a random noise file from the audio noise directory.
+ *
+ * @return bool|string false if a file could not be found, or a string containing the path to the file.
+ */
+ public function getRandomNoiseFile()
+ {
+ $return = false;
+
+ if ( ($dh = opendir($this->audio_noise_path)) !== false ) {
+ $list = array();
+
+ while ( ($file = readdir($dh)) !== false ) {
+ if ($file == '.' || $file == '..') continue;
+ if (strtolower(substr($file, -4)) != '.wav') continue;
+
+ $list[] = $file;
+ }
+
+ closedir($dh);
+
+ if (sizeof($list) > 0) {
+ $file = $list[array_rand($list, 1)];
+ $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file;
+
+ if (!is_readable($return)) $return = false;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get a random effect or chain of effects to apply to a segment of the
+ * audio file.
+ *
+ * These effects should increase the randomness of the audio for
+ * a particular letter/number by modulating the signal. The SoX effects
+ * used are *bend*, *chorus*, *overdrive*, *pitch*, *reverb*, *tempo*, and
+ * *tremolo*.
+ *
+ * For each effect selected, random parameters are supplied to the effect.
+ *
+ * @param int $numEffects How many effects to chain together
+ * @return string A string of valid SoX effects and their respective options.
+ */
+ protected function getSoxEffectChain($numEffects = 2)
+ {
+ $effectsList = array('bend', 'chorus', 'overdrive', 'pitch', 'reverb', 'tempo', 'tremolo');
+ $effects = array_rand($effectsList, $numEffects);
+ $outEffects = array();
+
+ if (!is_array($effects)) $effects = array($effects);
+
+ foreach($effects as $effect) {
+ $effect = $effectsList[$effect];
+
+ switch($effect)
+ {
+ case 'bend':
+ $delay = mt_rand(0, 15) / 100.0;
+ $cents = mt_rand(-120, 120);
+ $dur = mt_rand(75, 400) / 100.0;
+ $outEffects[] = "$effect $delay,$cents,$dur";
+ break;
+
+ case 'chorus':
+ $gainIn = mt_rand(75, 90) / 100.0;
+ $gainOut = mt_rand(70, 95) / 100.0;
+ $chorStr = "$effect $gainIn $gainOut";
+
+ for ($i = 0; $i < mt_rand(2, 3); ++$i) {
+ $delay = mt_rand(20, 100);
+ $decay = mt_rand(10, 100) / 100.0;
+ $speed = mt_rand(20, 50) / 100.0;
+ $depth = mt_rand(150, 250) / 100.0;
+
+ $chorStr .= " $delay $decay $speed $depth -s";
+ }
+
+ $outEffects[] = $chorStr;
+ break;
+
+ case 'overdrive':
+ $gain = mt_rand(5, 25);
+ $color = mt_rand(20, 70);
+ $outEffects[] = "$effect $gain $color";
+ break;
+
+ case 'pitch':
+ $cents = mt_rand(-300, 300);
+ $outEffects[] = "$effect $cents";
+ break;
+
+ case 'reverb':
+ $reverberance = mt_rand(20, 80);
+ $damping = mt_rand(10, 80);
+ $scale = mt_rand(85, 100);
+ $depth = mt_rand(90, 100);
+ $predelay = mt_rand(0, 5);
+ $outEffects[] = "$effect $reverberance $damping $scale $depth $predelay";
+ break;
+
+ case 'tempo':
+ $factor = mt_rand(65, 135) / 100.0;
+ $outEffects[] = "$effect -s $factor";
+ break;
+
+ case 'tremolo':
+ $hz = mt_rand(10, 30);
+ $depth = mt_rand(40, 85);
+ $outEffects[] = "$effect $hz $depth";
+ break;
+ }
+ }
+
+ return implode(' ', $outEffects);
+ }
+
+ /**
+ * This function is not yet used.
+ *
+ * Generate random background noise from sweeping oscillators
+ *
+ * @param float $duration How long in seconds the generated sound will be
+ * @param int $numChannels Number of channels in output wav
+ * @param int $sampleRate Sample rate of output wav
+ * @param int $bitRate Bits per sample (8, 16, 24)
+ * @return string Audio data in wav format
+ */
+ protected function getSoxNoiseData($duration, $numChannels, $sampleRate, $bitRate)
+ {
+ $shapes = array('sine', 'square', 'triangle', 'sawtooth', 'trapezium');
+ $steps = array(':', '+', '/', '-');
+ $selShapes = array_rand($shapes, 2);
+ $selSteps = array_rand($steps, 2);
+ $sweep0 = array();
+ $sweep0[0] = mt_rand(100, 700);
+ $sweep0[1] = mt_rand(1500, 2500);
+ $sweep1 = array();
+ $sweep1[0] = mt_rand(500, 1000);
+ $sweep1[1] = mt_rand(1200, 2000);
+
+ if (mt_rand(0, 10) % 2 == 0)
+ $sweep0 = array_reverse($sweep0);
+
+ if (mt_rand(0, 10) % 2 == 0)
+ $sweep1 = array_reverse($sweep1);
+
+ $cmd = sprintf("%s -c %d -r %d -b %d -n -t wav - synth noise create vol 0.3 synth %.2f %s mix %d%s%d vol 0.3 synth %.2f %s fmod %d%s%d vol 0.3",
+ $this->sox_binary_path,
+ $numChannels,
+ $sampleRate,
+ $bitRate,
+ $duration,
+ $shapes[$selShapes[0]],
+ $sweep0[0],
+ $steps[$selSteps[0]],
+ $sweep0[1],
+ $duration,
+ $shapes[$selShapes[1]],
+ $sweep1[0],
+ $steps[$selSteps[1]],
+ $sweep1[1]
+ );
+ $data = `$cmd`;
+
+ return $data;
+ }
+
+ /**
+ * Convert WAV data to MP3 using the Lame MP3 encoder binary
+ *
+ * @param string $data Contents of the WAV file to convert
+ * @return string MP3 file data
+ */
+ protected function wavToMp3($data)
+ {
+ if (!file_exists(self::$lame_binary_path) || !is_executable(self::$lame_binary_path)) {
+ throw new Exception('Lame binary "' . $this->lame_binary_path . '" does not exist or is not executable');
+ }
+
+ // size of wav data input
+ $size = strlen($data);
+
+ // file descriptors for reading and writing to the Lame process
+ $descriptors = array(
+ 0 => array('pipe', 'r'), // stdin
+ 1 => array('pipe', 'w'), // stdout
+ 2 => array('pipe', 'a'), // stderr
+ );
+
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ // workaround for Windows conversion
+ // writing to STDIN seems to hang indefinitely after writing approximately 0xC400 bytes
+ $wavinput = tempnam(sys_get_temp_dir(), 'wav');
+ if (!$wavinput) {
+ throw new Exception('Failed to create temporary file for WAV to MP3 conversion');
+ }
+ file_put_contents($wavinput, $data);
+ $size = 0;
+ } else {
+ $wavinput = '-'; // stdin
+ }
+
+ // Mono, variable bit rate, 32 kHz sampling rate, read WAV from stdin, write MP3 to stdout
+ $cmd = sprintf("%s -m m -v -b 32 %s -", self::$lame_binary_path, $wavinput);
+ $proc = proc_open($cmd, $descriptors, $pipes);
+
+ if (!is_resource($proc)) {
+ throw new Exception('Failed to open process for MP3 encoding');
+ }
+
+ stream_set_blocking($pipes[0], 0); // set stdin to be non-blocking
+
+ for ($written = 0; $written < $size; $written += $len) {
+ // write to stdin until all WAV data is written
+ $len = fwrite($pipes[0], substr($data, $written, 0x20000));
+
+ if ($len === 0) {
+ // fwrite wrote no data, make sure process is still alive, otherwise wait for it to process
+ $status = proc_get_status($proc);
+ if ($status['running'] === false) break;
+ usleep(25000);
+ } else if ($written < $size) {
+ // couldn't write all data, small pause and try again
+ usleep(10000);
+ } else if ($len === false) {
+ // fwrite failed, should not happen
+ break;
+ }
+ }
+
+ fclose($pipes[0]);
+
+ $data = stream_get_contents($pipes[1]);
+ $err = trim(stream_get_contents($pipes[2]));
+
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ $return = proc_close($proc);
+
+ if ($wavinput != '-') unlink($wavinput); // delete temp file on Windows
+
+ if ($return !== 0) {
+ throw new Exception("Failed to convert WAV to MP3. Shell returned ({$return}): {$err}");
+ } else if ($written < $size) {
+ throw new Exception('Failed to convert WAV to MP3. Failed to write all data to encoder');
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return a wav file saying there was an error generating file
+ *
+ * @return string The binary audio contents
+ */
+ protected function audioError()
+ {
+ return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
+ }
+
+ /**
+ * Checks to see if headers can be sent and if any error has been output
+ * to the browser
+ *
+ * @return bool true if it is safe to send headers, false if not
+ */
+ protected function canSendHeaders()
+ {
+ if (headers_sent()) {
+ // output has been flushed and headers have already been sent
+ return false;
+ } else if (strlen((string)ob_get_contents()) > 0) {
+ // headers haven't been sent, but there is data in the buffer that will break image and audio data
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return a random float between 0 and 0.9999
+ *
+ * @return float Random float between 0 and 0.9999
+ */
+ function frand()
+ {
+ return 0.0001 * mt_rand(0,9999);
+ }
+
+ /**
+ * Convert an html color code to a Securimage_Color
+ * @param string $color
+ * @param Securimage_Color|string $default The defalt color to use if $color is invalid
+ */
+ protected function initColor($color, $default)
+ {
+ if ($color == null) {
+ return new Securimage_Color($default);
+ } else if (is_string($color)) {
+ try {
+ return new Securimage_Color($color);
+ } catch(Exception $e) {
+ return new Securimage_Color($default);
+ }
+ } else if (is_array($color) && sizeof($color) == 3) {
+ return new Securimage_Color($color[0], $color[1], $color[2]);
+ } else {
+ return new Securimage_Color($default);
+ }
+ }
+
+ /**
+ * The error handling function used when outputting captcha image or audio.
+ *
+ * This error handler helps determine if any errors raised would
+ * prevent captcha image or audio from displaying. If they have
+ * no effect on the output buffer or headers, true is returned so
+ * the script can continue processing.
+ *
+ * See https://github.com/dapphp/securimage/issues/15
+ *
+ * @param int $errno PHP error number
+ * @param string $errstr String description of the error
+ * @param string $errfile File error occurred in
+ * @param int $errline Line the error occurred on in file
+ * @param array $errcontext Additional context information
+ * @return boolean true if the error was handled, false if PHP should handle the error
+ */
+ public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array())
+ {
+ // get the current error reporting level
+ $level = error_reporting();
+
+ // if error was supressed or $errno not set in current error level
+ if ($level == 0 || ($level & $errno) == 0) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+
+/**
+ * Color object for Securimage CAPTCHA
+ *
+* @since 2.0
+ * @package Securimage
+ * @subpackage classes
+ *
+ */
+class Securimage_Color
+{
+ /**
+ * Red value (0-255)
+ * @var int
+ */
+ public $r;
+
+ /**
+ * Gree value (0-255)
+ * @var int
+ */
+ public $g;
+
+ /**
+ * Blue value (0-255)
+ * @var int
+ */
+ public $b;
+
+ /**
+ * Create a new Securimage_Color object.
+ *
+ * Constructor expects 1 or 3 arguments.
+ *
+ * When passing a single argument, specify the color using HTML hex format.
+ *
+ * When passing 3 arguments, specify each RGB component (from 0-255)
+ * individually.
+ *
+ * Examples:
+ *
+ * $color = new Securimage_Color('#0080FF');
+ * $color = new Securimage_Color(0, 128, 255);
+ *
+ * @param string $color The html color code to use
+ * @throws Exception If any color value is not valid
+ */
+ public function __construct($color = '#ffffff')
+ {
+ $args = func_get_args();
+
+ if (sizeof($args) == 0) {
+ $this->r = 255;
+ $this->g = 255;
+ $this->b = 255;
+ } else if (sizeof($args) == 1) {
+ // set based on html code
+ if (substr($color, 0, 1) == '#') {
+ $color = substr($color, 1);
+ }
+
+ if (strlen($color) != 3 && strlen($color) != 6) {
+ throw new InvalidArgumentException(
+ 'Invalid HTML color code passed to Securimage_Color'
+ );
+ }
+
+ $this->constructHTML($color);
+ } else if (sizeof($args) == 3) {
+ $this->constructRGB($args[0], $args[1], $args[2]);
+ } else {
+ throw new InvalidArgumentException(
+ 'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given'
+ );
+ }
+ }
+
+ /**
+ * Construct from an rgb triplet
+ *
+ * @param int $red The red component, 0-255
+ * @param int $green The green component, 0-255
+ * @param int $blue The blue component, 0-255
+ */
+ protected function constructRGB($red, $green, $blue)
+ {
+ if ($red < 0) $red = 0;
+ if ($red > 255) $red = 255;
+ if ($green < 0) $green = 0;
+ if ($green > 255) $green = 255;
+ if ($blue < 0) $blue = 0;
+ if ($blue > 255) $blue = 255;
+
+ $this->r = $red;
+ $this->g = $green;
+ $this->b = $blue;
+ }
+
+ /**
+ * Construct from an html hex color code
+ *
+ * @param string $color
+ */
+ protected function constructHTML($color)
+ {
+ if (strlen($color) == 3) {
+ $red = str_repeat(substr($color, 0, 1), 2);
+ $green = str_repeat(substr($color, 1, 1), 2);
+ $blue = str_repeat(substr($color, 2, 1), 2);
+ } else {
+ $red = substr($color, 0, 2);
+ $green = substr($color, 2, 2);
+ $blue = substr($color, 4, 2);
+ }
+
+ $this->r = hexdec($red);
+ $this->g = hexdec($green);
+ $this->b = hexdec($blue);
+ }
+}
diff --git a/vendor/dapphp/securimage/securimage_play.php b/vendor/dapphp/securimage/securimage_play.php
new file mode 100644
index 0000000..b028c2b
--- /dev/null
+++ b/vendor/dapphp/securimage/securimage_play.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
+ * File: securimage_play.php<br />
+ *
+ * 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 any later version.<br /><br />
+ *
+ * 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.<br /><br />
+ *
+ * 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<br /><br />
+ *
+ * Any modifications to the library should be indicated clearly in the source code
+ * to inform users that the changes are not a part of the original software.<br /><br />
+ *
+ * If you found this script useful, please take a quick moment to rate it.<br />
+ * http://www.hotscripts.com/rate/49400.html Thanks.
+ *
+ * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
+ * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
+ * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
+ * @copyright 2012 Drew Phillips
+ * @author Drew Phillips <drew@drew-phillips.com>
+ * @version 3.6.6 (Nov 20 2017)
+ * @package Securimage
+ *
+ */
+
+require_once dirname(__FILE__) . '/securimage.php';
+
+// if using database, adjust these options as necessary and change $img = new Securimage(); to $img = new Securimage($options);
+// see test.mysql.php or test.sqlite.php for examples
+$options = array(
+ 'use_database' => true,
+ 'database_name' => '',
+ 'database_user' => '',
+ 'database_driver' => Securimage::SI_DRIVER_MYSQL
+);
+
+$img = new Securimage();
+
+// Other audio settings
+//$img->audio_use_sox = true;
+//$img->audio_use_noise = true;
+//$img->degrade_audio = false;
+//$img->sox_binary_path = 'sox';
+//Securimage::$lame_binary_path = '/usr/bin/lame'; // for mp3 audio support
+
+// To use an alternate language, uncomment the following and download the files from phpcaptcha.org
+// $img->audio_path = $img->securimage_path . '/audio/es/';
+
+// If you have more than one captcha on a page, one must use a custom namespace
+// $img->namespace = 'form2';
+
+// set namespace if supplied to script via HTTP GET
+if (!empty($_GET['namespace'])) $img->setNamespace($_GET['namespace']);
+
+
+// mp3 or wav format
+$format = (isset($_GET['format']) && strtolower($_GET['format']) == 'mp3') ? 'mp3' : null;
+
+$img->outputAudioFile($format);
diff --git a/vendor/dapphp/securimage/securimage_show.php b/vendor/dapphp/securimage/securimage_show.php
new file mode 100644
index 0000000..f352f76
--- /dev/null
+++ b/vendor/dapphp/securimage/securimage_show.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
+ * File: securimage_show.php<br />
+ *
+ * Copyright (c) 2013, Drew Phillips
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Any modifications to the library should be indicated clearly in the source code
+ * to inform users that the changes are not a part of the original software.<br /><br />
+ *
+ * If you found this script useful, please take a quick moment to rate it.<br />
+ * http://www.hotscripts.com/rate/49400.html Thanks.
+ *
+ * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
+ * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
+ * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
+ * @copyright 2013 Drew Phillips
+ * @author Drew Phillips <drew@drew-phillips.com>
+ * @version 3.6.6 (Nov 20 2017)
+ * @package Securimage
+ *
+ */
+
+// Remove the "//" from the following line for debugging problems
+// error_reporting(E_ALL); ini_set('display_errors', 1);
+
+require_once dirname(__FILE__) . '/securimage.php';
+
+$img = new Securimage();
+
+// You can customize the image by making changes below, some examples are included - remove the "//" to uncomment
+
+//$img->ttf_file = './Quiff.ttf';
+//$img->captcha_type = Securimage::SI_CAPTCHA_MATHEMATIC; // show a simple math problem instead of text
+//$img->case_sensitive = true; // true to use case sensitve codes - not recommended
+//$img->image_height = 90; // height in pixels of the image
+//$img->image_width = $img->image_height * M_E; // a good formula for image size based on the height
+//$img->perturbation = .75; // 1.0 = high distortion, higher numbers = more distortion
+//$img->image_bg_color = new Securimage_Color("#0099CC"); // image background color
+//$img->text_color = new Securimage_Color("#EAEAEA"); // captcha text color
+//$img->num_lines = 8; // how many lines to draw over the image
+//$img->line_color = new Securimage_Color("#0000CC"); // color of lines over the image
+//$img->image_type = SI_IMAGE_JPEG; // render as a jpeg image
+//$img->signature_color = new Securimage_Color(rand(0, 64),
+// rand(64, 128),
+// rand(128, 255)); // random signature color
+
+// see securimage.php for more options that can be set
+
+// set namespace if supplied to script via HTTP GET
+if (!empty($_GET['namespace'])) $img->setNamespace($_GET['namespace']);
+
+
+$img->show(); // outputs the image and content headers to the browser
+// alternate use:
+// $img->show('/path/to/background_image.jpg');
diff --git a/vendor/dapphp/securimage/words/words.txt b/vendor/dapphp/securimage/words/words.txt
new file mode 100644
index 0000000..9a444ce
--- /dev/null
+++ b/vendor/dapphp/securimage/words/words.txt
@@ -0,0 +1,15457 @@
+aahing
+aaliis
+aarrgh
+abacas
+abacus
+abakas
+abamps
+abased
+abaser
+abases
+abasia
+abated
+abater
+abates
+abatis
+abator
+abayas
+abbacy
+abbess
+abbeys
+abbots
+abduce
+abduct
+abeles
+abelia
+abhors
+abided
+abider
+abides
+abject
+abjure
+ablate
+ablaut
+ablaze
+ablest
+ablins
+abloom
+ablush
+abmhos
+aboard
+aboded
+abodes
+abohms
+abolla
+abomas
+aboral
+aborts
+abound
+aboves
+abrade
+abroad
+abrupt
+abseil
+absent
+absorb
+absurd
+abulia
+abulic
+abvolt
+abwatt
+abying
+abysms
+acacia
+acajou
+acarid
+acarus
+accede
+accent
+accept
+access
+accord
+accost
+accrue
+accuse
+acedia
+acetal
+acetic
+acetin
+acetum
+acetyl
+achene
+achier
+aching
+acidic
+acidly
+acinar
+acinic
+acinus
+ackees
+acnode
+acorns
+acquit
+across
+acting
+actins
+action
+active
+actors
+actual
+acuate
+acuity
+aculei
+acumen
+acuter
+acutes
+adages
+adagio
+adapts
+addend
+adders
+addict
+adding
+addled
+addles
+adduce
+adduct
+adeems
+adenyl
+adepts
+adhere
+adieus
+adieux
+adipic
+adjoin
+adjure
+adjust
+admass
+admire
+admits
+admixt
+adnate
+adnexa
+adnoun
+adobes
+adobos
+adonis
+adopts
+adored
+adorer
+adores
+adorns
+adrift
+adroit
+adsorb
+adults
+advect
+advent
+adverb
+advert
+advice
+advise
+adytum
+adzing
+adzuki
+aecial
+aecium
+aedile
+aedine
+aeneus
+aeonic
+aerate
+aerial
+aeried
+aerier
+aeries
+aerify
+aerily
+aerobe
+aerugo
+aether
+afeard
+affair
+affect
+affine
+affirm
+afflux
+afford
+affray
+afghan
+afield
+aflame
+afloat
+afraid
+afreet
+afresh
+afrits
+afters
+aftosa
+agamas
+agamic
+agamid
+agapae
+agapai
+agapes
+agaric
+agates
+agaves
+agedly
+ageing
+ageism
+ageist
+agency
+agenda
+agenes
+agents
+aggada
+aggers
+aggies
+aggros
+aghast
+agings
+agisms
+agists
+agitas
+aglare
+agleam
+aglets
+agnail
+agnate
+agnize
+agonal
+agones
+agonic
+agorae
+agoras
+agorot
+agouti
+agouty
+agrafe
+agreed
+agrees
+agrias
+aguish
+ahchoo
+ahimsa
+aholds
+ahorse
+aiders
+aidful
+aiding
+aidman
+aidmen
+aiglet
+aigret
+aikido
+ailing
+aimers
+aimful
+aiming
+aiolis
+airbag
+airbus
+airers
+airest
+airier
+airily
+airing
+airman
+airmen
+airted
+airths
+airway
+aisled
+aisles
+aivers
+ajivas
+ajowan
+ajugas
+akelas
+akenes
+akimbo
+alamos
+alands
+alanin
+alants
+alanyl
+alarms
+alarum
+alaska
+alated
+alates
+albata
+albedo
+albeit
+albino
+albite
+albums
+alcade
+alcaic
+alcids
+alcove
+alders
+aldols
+aldose
+aldrin
+alegar
+alephs
+alerts
+alevin
+alexia
+alexin
+alfaki
+algins
+algoid
+algors
+algums
+alibis
+alible
+alidad
+aliens
+alight
+aligns
+alined
+aliner
+alines
+aliped
+aliyah
+aliyas
+aliyos
+aliyot
+alkali
+alkane
+alkene
+alkies
+alkine
+alkoxy
+alkyds
+alkyls
+alkyne
+allays
+allees
+allege
+allele
+alleys
+allied
+allies
+allium
+allods
+allots
+allows
+alloys
+allude
+allure
+allyls
+almahs
+almehs
+almner
+almond
+almost
+almuce
+almude
+almuds
+almugs
+alnico
+alodia
+alohas
+aloins
+alpaca
+alphas
+alphyl
+alpine
+alsike
+altars
+alters
+althea
+aludel
+alulae
+alular
+alumin
+alumna
+alumni
+alvine
+always
+amadou
+amarna
+amatol
+amazed
+amazes
+amazon
+ambage
+ambari
+ambary
+ambeer
+ambers
+ambery
+ambits
+ambled
+ambler
+ambles
+ambush
+amebae
+ameban
+amebas
+amebic
+ameers
+amends
+aments
+amerce
+amices
+amicus
+amides
+amidic
+amidin
+amidol
+amidst
+amigas
+amigos
+amines
+aminic
+ammine
+ammino
+ammono
+amnion
+amnios
+amoeba
+amoles
+amoral
+amount
+amours
+ampere
+amping
+ampler
+ampule
+ampuls
+amrita
+amtrac
+amucks
+amulet
+amused
+amuser
+amuses
+amusia
+amylic
+amylum
+anabas
+anadem
+analog
+ananke
+anarch
+anatto
+anchor
+anchos
+ancone
+andros
+anears
+aneled
+aneles
+anemia
+anemic
+anenst
+anergy
+angary
+angels
+angers
+angina
+angled
+angler
+angles
+anglos
+angora
+angsts
+anilin
+animal
+animas
+animes
+animis
+animus
+anions
+anises
+anisic
+ankled
+ankles
+anklet
+ankush
+anlace
+anlage
+annals
+anneal
+annexe
+annona
+annoys
+annual
+annuli
+annuls
+anodal
+anodes
+anodic
+anoint
+anoles
+anomic
+anomie
+anonym
+anopia
+anorak
+anoxia
+anoxic
+ansate
+answer
+anteed
+anthem
+anther
+antiar
+antick
+antics
+anting
+antler
+antral
+antres
+antrum
+anural
+anuran
+anuria
+anuric
+anvils
+anyhow
+anyone
+anyons
+anyway
+aorist
+aortae
+aortal
+aortas
+aortic
+aoudad
+apache
+apathy
+apercu
+apexes
+aphids
+aphtha
+apiary
+apical
+apices
+apiece
+aplite
+aplomb
+apneal
+apneas
+apneic
+apnoea
+apodal
+apogee
+apollo
+apolog
+aporia
+appall
+appals
+appeal
+appear
+appels
+append
+apples
+applet
+appose
+aprons
+aptest
+arabic
+arable
+arames
+aramid
+arbors
+arbour
+arbute
+arcade
+arcana
+arcane
+arched
+archer
+arches
+archil
+archly
+archon
+arcing
+arcked
+arctic
+ardebs
+ardent
+ardors
+ardour
+arecas
+arenas
+arenes
+areola
+areole
+arepas
+aretes
+argala
+argali
+argals
+argent
+argils
+argled
+argles
+argols
+argons
+argosy
+argots
+argued
+arguer
+argues
+argufy
+argyle
+argyll
+arhats
+ariary
+arider
+aridly
+ariels
+aright
+ariled
+ariose
+ariosi
+arioso
+arisen
+arises
+arista
+aristo
+arkose
+armada
+armers
+armets
+armful
+armies
+arming
+armlet
+armors
+armory
+armour
+armpit
+armure
+arnica
+aroids
+aroint
+aromas
+around
+arouse
+aroynt
+arpens
+arpent
+arrack
+arrant
+arrays
+arrear
+arrest
+arriba
+arrive
+arroba
+arrows
+arrowy
+arroyo
+arseno
+arshin
+arsine
+arsino
+arsons
+artels
+artery
+artful
+artier
+artily
+artist
+asanas
+asarum
+ascend
+ascent
+ascots
+asdics
+ashcan
+ashier
+ashing
+ashlar
+ashler
+ashman
+ashmen
+ashore
+ashram
+asides
+askant
+askers
+asking
+aslant
+asleep
+aslope
+aslosh
+aspect
+aspens
+aspers
+aspics
+aspire
+aspish
+asrama
+astern
+asters
+asthma
+astony
+astral
+astray
+astute
+aswarm
+aswirl
+aswoon
+asylum
+atabal
+ataman
+atavic
+ataxia
+ataxic
+atelic
+atlatl
+atmans
+atolls
+atomic
+atonal
+atoned
+atoner
+atones
+atonia
+atonic
+atopic
+atrial
+atrium
+attach
+attack
+attain
+attars
+attend
+attent
+attest
+attics
+attire
+attorn
+attrit
+attune
+atwain
+atween
+atypic
+aubade
+auburn
+aucuba
+audads
+audial
+audile
+auding
+audios
+audits
+augend
+augers
+aughts
+augite
+augurs
+augury
+august
+auklet
+aulder
+auntie
+auntly
+aurate
+aureus
+aurist
+aurora
+aurous
+aurums
+auspex
+ausubo
+auteur
+author
+autism
+autist
+autoed
+autumn
+auxins
+avails
+avatar
+avaunt
+avenge
+avenue
+averse
+averts
+avians
+aviary
+aviate
+avidin
+avidly
+avions
+avisos
+avocet
+avoids
+avoset
+avouch
+avowal
+avowed
+avower
+avulse
+awaits
+awaked
+awaken
+awakes
+awards
+aweary
+aweigh
+aweing
+awhile
+awhirl
+awless
+awmous
+awning
+awoken
+axeman
+axemen
+axenic
+axilla
+axioms
+axions
+axised
+axises
+axites
+axlike
+axonal
+axones
+axonic
+axseed
+azalea
+azides
+azines
+azlons
+azoles
+azonal
+azonic
+azoted
+azotes
+azoths
+azotic
+azukis
+azures
+azygos
+baaing
+baalim
+baases
+babble
+babels
+babied
+babier
+babies
+babkas
+babool
+baboon
+baboos
+babuls
+baccae
+bached
+baches
+backed
+backer
+backup
+bacons
+bacula
+badass
+badder
+baddie
+badged
+badger
+badges
+badman
+badmen
+baffed
+baffle
+bagels
+bagful
+bagged
+bagger
+baggie
+bagman
+bagmen
+bagnio
+baguet
+bagwig
+bailed
+bailee
+bailer
+bailey
+bailie
+bailor
+bairns
+baited
+baiter
+baizas
+baizes
+bakers
+bakery
+baking
+balata
+balboa
+balded
+balder
+baldly
+baleen
+balers
+baling
+balked
+balker
+ballad
+ballet
+ballon
+ballot
+balsam
+balsas
+bamboo
+bammed
+banana
+bancos
+bandas
+banded
+bander
+bandit
+bandog
+banged
+banger
+bangle
+banian
+baning
+banish
+banjax
+banjos
+banked
+banker
+bankit
+banned
+banner
+bannet
+bantam
+banter
+banyan
+banzai
+baobab
+barbal
+barbed
+barbel
+barber
+barbes
+barbet
+barbie
+barbut
+barcas
+barded
+bardes
+bardic
+barege
+barely
+barest
+barfed
+barfly
+barged
+bargee
+barges
+barhop
+baring
+barite
+barium
+barked
+barker
+barley
+barlow
+barman
+barmen
+barmie
+barned
+barney
+barong
+barons
+barony
+barque
+barred
+barrel
+barren
+barres
+barret
+barrio
+barrow
+barter
+baryes
+baryon
+baryta
+baryte
+basalt
+basely
+basest
+bashaw
+bashed
+basher
+bashes
+basics
+basify
+basils
+basing
+basins
+basion
+basked
+basket
+basque
+basted
+baster
+bastes
+batboy
+bateau
+bathed
+bather
+bathes
+bathos
+batiks
+bating
+batman
+batmen
+batons
+batted
+batten
+batter
+battik
+battle
+battue
+baubee
+bauble
+baulks
+baulky
+bawbee
+bawdry
+bawled
+bawler
+bawtie
+bayamo
+bayard
+baying
+bayman
+baymen
+bayous
+bazaar
+bazars
+bazoos
+beachy
+beacon
+beaded
+beader
+beadle
+beagle
+beaked
+beaker
+beamed
+beaned
+beanie
+beanos
+beards
+bearer
+beaten
+beater
+beauts
+beauty
+bebops
+becalm
+became
+becaps
+becked
+becket
+beckon
+beclog
+become
+bedamn
+bedaub
+bedbug
+bedded
+bedder
+bedeck
+bedell
+bedels
+bedews
+bedims
+bedlam
+bedpan
+bedrid
+bedrug
+bedsit
+beduin
+bedumb
+beebee
+beechy
+beefed
+beeped
+beeper
+beetle
+beeves
+beezer
+befall
+befell
+befits
+beflag
+beflea
+befogs
+befool
+before
+befoul
+befret
+begall
+begaze
+begets
+beggar
+begged
+begins
+begird
+begirt
+beglad
+begone
+begrim
+begulf
+begums
+behalf
+behave
+behead
+beheld
+behest
+behind
+behold
+behoof
+behove
+behowl
+beiges
+beigne
+beings
+bekiss
+beknot
+belady
+belaud
+belays
+beldam
+beleap
+belfry
+belgas
+belied
+belief
+belier
+belies
+belike
+belive
+belled
+belles
+bellow
+belong
+belons
+belows
+belted
+belter
+beluga
+bemata
+bemean
+bemire
+bemist
+bemixt
+bemoan
+bemock
+bemuse
+bename
+benday
+bended
+bendee
+bender
+bendys
+benign
+bennes
+bennet
+bennis
+bentos
+benumb
+benzal
+benzin
+benzol
+benzyl
+berake
+berate
+bereft
+berets
+berime
+berlin
+bermed
+bermes
+bertha
+berths
+beryls
+beseem
+besets
+beside
+besmut
+besnow
+besoms
+besots
+bested
+bestir
+bestow
+bestud
+betake
+betels
+bethel
+betide
+betime
+betise
+betons
+betony
+betook
+betray
+bettas
+betted
+better
+bettor
+bevels
+bevies
+bevors
+bewail
+beware
+beweep
+bewept
+bewigs
+beworm
+bewrap
+bewray
+beylic
+beylik
+beyond
+bezant
+bezazz
+bezels
+bezils
+bezoar
+bhakta
+bhakti
+bhangs
+bharal
+bhoots
+bialis
+bialys
+biased
+biases
+biaxal
+bibbed
+bibber
+bibles
+bicarb
+biceps
+bicker
+bicorn
+bicron
+bidden
+bidder
+biders
+bidets
+biding
+bields
+biface
+biffed
+biffin
+biflex
+bifold
+biform
+bigamy
+bigeye
+bigger
+biggie
+biggin
+bights
+bigots
+bigwig
+bijous
+bijoux
+bikers
+bikies
+biking
+bikini
+bilboa
+bilbos
+bilged
+bilges
+bilked
+bilker
+billed
+biller
+billet
+billie
+billon
+billow
+bimahs
+bimbos
+binary
+binate
+binder
+bindis
+bindle
+biners
+binged
+binger
+binges
+bingos
+binits
+binned
+binocs
+biogas
+biogen
+biomes
+bionic
+bionts
+biopic
+biopsy
+biotas
+biotic
+biotin
+bipack
+bipeds
+bipods
+birded
+birder
+birdie
+bireme
+birkie
+birled
+birler
+birles
+birred
+birses
+births
+bisect
+bishop
+bisons
+bisque
+bister
+bistre
+bistro
+biters
+biting
+bitmap
+bitted
+bitten
+bitter
+bizone
+bizzes
+blabby
+blacks
+bladed
+blader
+blades
+blaffs
+blains
+blamed
+blamer
+blames
+blanch
+blanks
+blared
+blares
+blasts
+blasty
+blawed
+blazed
+blazer
+blazes
+blazon
+bleach
+bleaks
+blears
+bleary
+bleats
+blebby
+bleeds
+bleeps
+blench
+blende
+blends
+blenny
+blight
+blimey
+blimps
+blinds
+blinis
+blinks
+blintz
+blites
+blithe
+bloats
+blocks
+blocky
+blokes
+blonde
+blonds
+bloods
+bloody
+blooey
+blooie
+blooms
+bloomy
+bloops
+blotch
+blotto
+blotty
+blouse
+blousy
+blowby
+blowed
+blower
+blowsy
+blowup
+blowzy
+bludge
+bluely
+bluest
+bluesy
+bluets
+blueys
+bluffs
+bluing
+bluish
+blumed
+blumes
+blunge
+blunts
+blurbs
+blurry
+blurts
+blypes
+boards
+boarts
+boasts
+boated
+boatel
+boater
+bobbed
+bobber
+bobbin
+bobble
+bobcat
+bocces
+boccia
+boccie
+boccis
+boches
+bodega
+bodice
+bodied
+bodies
+bodily
+boding
+bodkin
+boffed
+boffin
+boffos
+bogans
+bogart
+bogeys
+bogged
+boggle
+bogies
+bogles
+boheas
+bohunk
+boiled
+boiler
+boings
+boinks
+boites
+bolder
+boldly
+bolero
+bolete
+boleti
+bolide
+bolled
+bolshy
+bolson
+bolted
+bolter
+bombax
+bombed
+bomber
+bombes
+bombyx
+bonaci
+bonbon
+bonded
+bonder
+bonduc
+bongos
+bonier
+boning
+bonita
+bonito
+bonnes
+bonnet
+bonnie
+bonobo
+bonsai
+bonzer
+bonzes
+boobed
+boobie
+booboo
+boocoo
+boodle
+booger
+boogey
+boogie
+boohoo
+booing
+boojum
+booked
+booker
+bookie
+bookoo
+boomed
+boomer
+boosts
+booted
+bootee
+booths
+bootie
+boozed
+boozer
+boozes
+bopeep
+bopped
+bopper
+borage
+borals
+borane
+borate
+bordel
+border
+boreal
+boreas
+boreen
+borers
+boride
+boring
+borked
+borons
+borrow
+borsch
+borsht
+borzoi
+boshes
+bosker
+bosket
+bosons
+bosque
+bossed
+bosses
+boston
+bosuns
+botany
+botchy
+botels
+botfly
+bother
+bottle
+bottom
+boubou
+boucle
+boudin
+bouffe
+boughs
+bought
+bougie
+boules
+boulle
+bounce
+bouncy
+bounds
+bounty
+bourgs
+bourne
+bourns
+bourse
+boused
+bouses
+bouton
+bovids
+bovine
+bowers
+bowery
+bowfin
+bowing
+bowled
+bowleg
+bowler
+bowman
+bowmen
+bowpot
+bowsed
+bowses
+bowwow
+bowyer
+boxcar
+boxers
+boxful
+boxier
+boxily
+boxing
+boyard
+boyars
+boyish
+boylas
+braced
+bracer
+braces
+brachs
+bracts
+braggy
+brahma
+braids
+brails
+brains
+brainy
+braise
+braize
+braked
+brakes
+branch
+brands
+brandy
+branks
+branny
+brants
+brashy
+brasil
+brassy
+bratty
+bravas
+braved
+braver
+braves
+bravos
+brawer
+brawls
+brawly
+brawns
+brawny
+brayed
+brayer
+brazas
+brazed
+brazen
+brazer
+brazes
+brazil
+breach
+breads
+bready
+breaks
+breams
+breath
+bredes
+breech
+breeds
+breeks
+breeze
+breezy
+bregma
+brents
+breves
+brevet
+brewed
+brewer
+brewis
+briard
+briars
+briary
+bribed
+bribee
+briber
+bribes
+bricks
+bricky
+bridal
+brides
+bridge
+bridle
+briefs
+briers
+briery
+bright
+brillo
+brills
+brined
+briner
+brines
+brings
+brinks
+briony
+brises
+brisks
+briths
+britts
+broach
+broads
+broche
+brocks
+brogan
+brogue
+broils
+broken
+broker
+brolly
+bromal
+bromes
+bromic
+bromid
+bromin
+bromos
+bronco
+broncs
+bronze
+bronzy
+brooch
+broods
+broody
+brooks
+brooms
+broomy
+broses
+broths
+brothy
+browed
+browns
+browny
+browse
+brucin
+brughs
+bruins
+bruise
+bruits
+brulot
+brumal
+brumby
+brumes
+brunch
+brunet
+brunts
+brushy
+brutal
+bruted
+brutes
+bruxed
+bruxes
+bryony
+bubale
+bubals
+bubbas
+bubble
+bubbly
+bubkes
+buboed
+buboes
+buccal
+bucked
+bucker
+bucket
+buckle
+buckos
+buckra
+budded
+budder
+buddha
+buddle
+budged
+budger
+budges
+budget
+budgie
+buffed
+buffer
+buffet
+buffos
+bugeye
+bugged
+bugger
+bugled
+bugler
+bugles
+bugout
+bugsha
+builds
+bulbar
+bulbed
+bulbel
+bulbil
+bulbul
+bulged
+bulger
+bulges
+bulgur
+bulked
+bullae
+bulled
+bullet
+bumble
+bumkin
+bumped
+bumper
+bumphs
+bunchy
+buncos
+bundle
+bundts
+bunged
+bungee
+bungle
+bunion
+bunked
+bunker
+bunkos
+bunkum
+bunted
+bunter
+bunyas
+buoyed
+bupkes
+bupkus
+buppie
+buqsha
+burans
+burble
+burbly
+burbot
+burden
+burdie
+bureau
+burets
+burgee
+burger
+burghs
+burgle
+burgoo
+burial
+buried
+burier
+buries
+burins
+burkas
+burked
+burker
+burkes
+burlap
+burled
+burler
+burley
+burned
+burner
+burnet
+burnie
+burped
+burqas
+burred
+burrer
+burros
+burrow
+bursae
+bursal
+bursar
+bursas
+burses
+bursts
+burton
+busbar
+busboy
+bushed
+bushel
+busher
+bushes
+bushwa
+busied
+busier
+busies
+busily
+busing
+busked
+busker
+buskin
+busman
+busmen
+bussed
+busses
+busted
+buster
+bustic
+bustle
+butane
+butene
+buteos
+butled
+butler
+butles
+butted
+butter
+buttes
+button
+bututs
+butyls
+buyers
+buying
+buyoff
+buyout
+buzuki
+buzzed
+buzzer
+buzzes
+bwanas
+byelaw
+bygone
+bylaws
+byline
+byname
+bypass
+bypast
+bypath
+byplay
+byrled
+byrnie
+byroad
+byssal
+byssus
+bytalk
+byways
+byword
+bywork
+byzant
+cabala
+cabals
+cabana
+cabbed
+cabbie
+cabers
+cabins
+cabled
+cabler
+cables
+cablet
+cabman
+cabmen
+cabobs
+cacaos
+cached
+caches
+cachet
+cachou
+cackle
+cactus
+caddie
+caddis
+cadent
+cadets
+cadged
+cadger
+cadges
+cadmic
+cadres
+caecal
+caecum
+caeoma
+caesar
+caftan
+cagers
+cagier
+cagily
+caging
+cahier
+cahoot
+cahows
+caiman
+caique
+cairds
+cairns
+cairny
+cajole
+cakier
+caking
+calami
+calash
+calcar
+calces
+calcic
+calesa
+calico
+califs
+caliph
+calked
+calker
+calkin
+callan
+callas
+called
+callee
+caller
+callet
+callow
+callus
+calmed
+calmer
+calmly
+calory
+calpac
+calque
+calved
+calves
+calxes
+camail
+camber
+cambia
+camels
+cameos
+camera
+camion
+camisa
+camise
+camlet
+cammie
+camped
+camper
+campos
+campus
+canals
+canape
+canard
+canary
+cancan
+cancel
+cancer
+cancha
+candid
+candle
+candor
+caners
+canful
+cangue
+canids
+canine
+caning
+canker
+cannas
+canned
+cannel
+canner
+cannie
+cannon
+cannot
+canoed
+canoer
+canoes
+canola
+canons
+canopy
+cansos
+cantal
+canted
+canter
+canthi
+cantic
+cantle
+canton
+cantor
+cantos
+cantus
+canula
+canvas
+canyon
+capers
+capful
+capias
+capita
+caplet
+caplin
+capons
+capote
+capped
+capper
+capric
+capris
+capsid
+captan
+captor
+carack
+carafe
+carate
+carats
+carbon
+carbos
+carboy
+carcel
+carded
+carder
+cardia
+cardio
+cardon
+careen
+career
+carers
+caress
+carets
+carful
+cargos
+carhop
+caribe
+caried
+caries
+carina
+caring
+carked
+carles
+carlin
+carman
+carmen
+carnal
+carnet
+carney
+carnie
+carobs
+caroch
+caroli
+carols
+caroms
+carpal
+carped
+carpel
+carper
+carpet
+carpus
+carrel
+carrom
+carrot
+carses
+carted
+cartel
+carter
+cartes
+carton
+cartop
+carved
+carvel
+carven
+carver
+carves
+casaba
+casava
+casbah
+casefy
+caseic
+casein
+casern
+cashaw
+cashed
+cashes
+cashew
+cashoo
+casing
+casini
+casino
+casita
+casked
+casket
+casque
+caster
+castes
+castle
+castor
+casual
+catalo
+catchy
+catena
+caters
+catgut
+cation
+catkin
+catlin
+catnap
+catnip
+catsup
+catted
+cattie
+cattle
+caucus
+caudad
+caudal
+caudex
+caudle
+caught
+caulds
+caules
+caulis
+caulks
+causal
+caused
+causer
+causes
+causey
+caveat
+cavern
+cavers
+caviar
+cavies
+cavils
+caving
+cavity
+cavort
+cawing
+cayman
+cayuse
+ceased
+ceases
+cebids
+ceboid
+cecity
+cedarn
+cedars
+cedary
+ceders
+ceding
+cedula
+ceibas
+ceiled
+ceiler
+ceilis
+celebs
+celery
+celiac
+cellae
+cellar
+celled
+cellos
+celoms
+cement
+cenote
+censed
+censer
+censes
+censor
+census
+centai
+cental
+centas
+center
+centos
+centra
+centre
+centum
+ceorls
+cerate
+cercal
+cercis
+cercus
+cereal
+cereus
+cerias
+cering
+ceriph
+cerise
+cerite
+cerium
+cermet
+cerous
+certes
+ceruse
+cervid
+cervix
+cesium
+cessed
+cesses
+cestas
+cestoi
+cestos
+cestus
+cesura
+cetane
+chabuk
+chacma
+chadar
+chador
+chadri
+chaeta
+chafed
+chafer
+chafes
+chaffs
+chaffy
+chaine
+chains
+chairs
+chaise
+chakra
+chalah
+chaleh
+chalet
+chalks
+chalky
+challa
+chally
+chalot
+chammy
+champs
+champy
+chance
+chancy
+change
+changs
+chants
+chanty
+chapel
+chapes
+charas
+chards
+chared
+chares
+charge
+charka
+charks
+charms
+charro
+charrs
+charry
+charts
+chased
+chaser
+chases
+chasms
+chasmy
+chasse
+chaste
+chatty
+chaunt
+chawed
+chawer
+chazan
+cheapo
+cheaps
+cheats
+chebec
+checks
+cheder
+cheeks
+cheeky
+cheeps
+cheero
+cheers
+cheery
+cheese
+cheesy
+chefed
+chegoe
+chelae
+chelas
+chemic
+chemos
+cheque
+cherry
+cherts
+cherty
+cherub
+chests
+chesty
+chetah
+cheths
+chevre
+chewed
+chewer
+chiasm
+chiaus
+chicas
+chicer
+chichi
+chicks
+chicle
+chicly
+chicos
+chided
+chider
+chides
+chiefs
+chield
+chiels
+chigoe
+childe
+chiles
+chilis
+chilli
+chills
+chilly
+chimar
+chimbs
+chimed
+chimer
+chimes
+chimla
+chimps
+chinas
+chinch
+chined
+chines
+chinks
+chinky
+chinos
+chints
+chintz
+chippy
+chiral
+chirks
+chirms
+chiros
+chirps
+chirpy
+chirre
+chirrs
+chirus
+chisel
+chital
+chitin
+chiton
+chitty
+chives
+chivvy
+choana
+chocks
+choice
+choirs
+choked
+choker
+chokes
+chokey
+cholas
+choler
+cholla
+cholos
+chomps
+chooks
+choose
+choosy
+chopin
+choppy
+choral
+chords
+chorea
+chored
+chores
+choric
+chorus
+chosen
+choses
+chotts
+chough
+chouse
+choush
+chowed
+chowse
+chrism
+chroma
+chrome
+chromo
+chromy
+chubby
+chucks
+chucky
+chufas
+chuffs
+chuffy
+chukar
+chukka
+chummy
+chumps
+chunks
+chunky
+chuppa
+church
+churls
+churns
+churro
+churrs
+chuted
+chutes
+chyles
+chymes
+chymic
+cibols
+cicada
+cicala
+cicale
+cicely
+cicero
+ciders
+cigars
+cilice
+cilium
+cinder
+cinema
+cineol
+cinque
+cipher
+circle
+circus
+cirque
+cirrus
+ciscos
+cisted
+cistus
+citers
+cither
+citied
+cities
+citify
+citing
+citola
+citole
+citral
+citric
+citrin
+citron
+citrus
+civets
+civics
+civies
+civism
+clachs
+clacks
+clades
+claims
+clammy
+clamor
+clamps
+clangs
+clanks
+clanky
+claque
+claret
+claros
+clasps
+claspt
+classy
+clasts
+clause
+claver
+claves
+clavus
+clawed
+clawer
+claxon
+clayed
+clayey
+cleans
+clears
+cleats
+cleave
+cleeks
+clefts
+clench
+cleome
+cleped
+clepes
+clergy
+cleric
+clerid
+clerks
+clever
+clevis
+clewed
+cliche
+clicks
+client
+cliffs
+cliffy
+clifts
+climax
+climbs
+climes
+clinal
+clinch
+clines
+clings
+clingy
+clinic
+clinks
+clique
+cliquy
+clitic
+clivia
+cloaca
+cloaks
+cloche
+clocks
+cloddy
+cloggy
+clomps
+clonal
+cloned
+cloner
+clones
+clonic
+clonks
+clonus
+cloots
+cloque
+closed
+closer
+closes
+closet
+clothe
+cloths
+clotty
+clouds
+cloudy
+clough
+clours
+clouts
+cloven
+clover
+cloves
+clowns
+cloyed
+clozes
+clubby
+clucks
+cluing
+clumps
+clumpy
+clumsy
+clunks
+clunky
+clutch
+clypei
+cnidae
+coacts
+coalas
+coaled
+coaler
+coapts
+coarse
+coasts
+coated
+coatee
+coater
+coatis
+coaxal
+coaxed
+coaxer
+coaxes
+cobalt
+cobber
+cobble
+cobias
+cobles
+cobnut
+cobras
+cobweb
+cocain
+coccal
+coccic
+coccid
+coccus
+coccyx
+cochin
+cocoas
+cocoon
+codded
+codder
+coddle
+codecs
+codeia
+codens
+coders
+codify
+coding
+codlin
+codons
+coedit
+coelom
+coempt
+coerce
+coeval
+coffee
+coffer
+coffin
+coffle
+cogent
+cogged
+cogito
+cognac
+cogons
+cogway
+cohead
+coheir
+cohere
+cohogs
+cohort
+cohosh
+cohost
+cohune
+coifed
+coiffe
+coigne
+coigns
+coiled
+coiler
+coined
+coiner
+coital
+coitus
+cojoin
+coking
+colbys
+colder
+coldly
+colead
+coleus
+colics
+colies
+colins
+collar
+collet
+collie
+collop
+colobi
+cologs
+colone
+coloni
+colons
+colony
+colors
+colour
+colter
+colugo
+column
+colure
+colzas
+comade
+comake
+comate
+combat
+combed
+comber
+combes
+combos
+comedo
+comedy
+comely
+comers
+cometh
+comets
+comfit
+comics
+coming
+comity
+commas
+commie
+commit
+commix
+common
+comose
+comous
+compas
+comped
+compel
+comply
+compos
+compts
+comtes
+concha
+concho
+conchs
+conchy
+concur
+condor
+condos
+coneys
+confab
+confer
+confit
+congas
+congee
+conger
+conges
+congii
+congos
+congou
+conics
+conies
+conine
+coning
+conins
+conium
+conked
+conker
+conned
+conner
+conoid
+consol
+consul
+contes
+contos
+contra
+convex
+convey
+convoy
+coocoo
+cooeed
+cooees
+cooers
+cooeys
+cooing
+cooked
+cooker
+cookey
+cookie
+cooled
+cooler
+coolie
+coolly
+coolth
+coombe
+coombs
+cooped
+cooper
+coopts
+cooter
+cootie
+copalm
+copals
+copays
+copeck
+copens
+copers
+copied
+copier
+copies
+coping
+coplot
+copout
+copped
+copper
+coppra
+coprah
+copras
+copses
+copter
+copula
+coquet
+corals
+corban
+corbel
+corbie
+corded
+corder
+cordon
+corers
+corgis
+coring
+corium
+corked
+corker
+cormel
+cornea
+corned
+cornel
+corner
+cornet
+cornua
+cornus
+corody
+corona
+corpse
+corpus
+corral
+corrie
+corsac
+corses
+corset
+cortex
+cortin
+corvee
+corves
+corvet
+corvid
+corymb
+coryza
+cosecs
+cosets
+coseys
+coshed
+cosher
+coshes
+cosied
+cosier
+cosies
+cosign
+cosily
+cosine
+cosmic
+cosmid
+cosmos
+cosset
+costae
+costal
+costar
+costed
+coster
+costly
+cotans
+coteau
+coting
+cottae
+cottar
+cottas
+cotter
+cotton
+cotype
+cougar
+coughs
+coulee
+coulis
+counts
+county
+couped
+coupes
+couple
+coupon
+course
+courts
+cousin
+couter
+couths
+covary
+covens
+covers
+covert
+covets
+coveys
+coving
+covins
+cowage
+coward
+cowboy
+cowers
+cowier
+cowing
+cowled
+cowman
+cowmen
+cowpat
+cowpea
+cowpie
+cowpox
+cowrie
+coxing
+coydog
+coyest
+coying
+coyish
+coyote
+coypou
+coypus
+cozens
+cozeys
+cozied
+cozier
+cozies
+cozily
+cozzes
+craals
+crabby
+cracks
+cracky
+cradle
+crafts
+crafty
+craggy
+crakes
+crambe
+crambo
+cramps
+crampy
+cranch
+craned
+cranes
+crania
+cranks
+cranky
+cranny
+crapes
+crappy
+crases
+crasis
+cratch
+crated
+crater
+crates
+craton
+cravat
+craved
+craven
+craver
+craves
+crawls
+crawly
+crayon
+crazed
+crazes
+creaks
+creaky
+creams
+creamy
+crease
+creasy
+create
+creche
+credal
+credit
+credos
+creeds
+creeks
+creels
+creeps
+creepy
+creese
+creesh
+cremes
+crenel
+creole
+creped
+crepes
+crepey
+crepon
+cresol
+cressy
+crests
+cresyl
+cretic
+cretin
+crewed
+crewel
+cricks
+criers
+crikey
+crimes
+crimps
+crimpy
+cringe
+crinum
+cripes
+crises
+crisic
+crisis
+crisps
+crispy
+crissa
+crista
+critic
+croaks
+croaky
+crocks
+crocus
+crofts
+crojik
+crones
+crooks
+croons
+crores
+crosse
+crotch
+croton
+crouch
+croupe
+croups
+croupy
+crouse
+croute
+crowds
+crowdy
+crowed
+crower
+crowns
+crozer
+crozes
+cruces
+crucks
+cruddy
+cruder
+crudes
+cruets
+cruise
+crumbs
+crumby
+crummy
+crumps
+crunch
+cruors
+crural
+cruses
+cruset
+crusts
+crusty
+crutch
+cruxes
+crwths
+crying
+crypto
+crypts
+cuatro
+cubage
+cubebs
+cubers
+cubics
+cubing
+cubism
+cubist
+cubiti
+cubits
+cuboid
+cuckoo
+cuddie
+cuddle
+cuddly
+cudgel
+cueing
+cuesta
+cuffed
+cuisse
+culets
+cullay
+culled
+culler
+cullet
+cullis
+culmed
+culpae
+cultch
+cultic
+cultus
+culver
+cumber
+cumbia
+cumins
+cummer
+cummin
+cumuli
+cundum
+cuneal
+cunner
+cupels
+cupful
+cupids
+cupola
+cuppas
+cupped
+cupper
+cupric
+cuprum
+cupula
+cupule
+curacy
+curagh
+curara
+curare
+curari
+curate
+curbed
+curber
+curded
+curdle
+curers
+curets
+curfew
+curiae
+curial
+curies
+curing
+curios
+curite
+curium
+curled
+curler
+curlew
+curran
+curred
+currie
+cursed
+curser
+curses
+cursor
+curtal
+curter
+curtly
+curtsy
+curule
+curved
+curves
+curvet
+curvey
+cuscus
+cusecs
+cushat
+cushaw
+cuspal
+cusped
+cuspid
+cuspis
+cussed
+cusser
+cusses
+cussos
+custom
+custos
+cutely
+cutest
+cutesy
+cuteys
+cuties
+cutins
+cutlas
+cutler
+cutlet
+cutoff
+cutout
+cutter
+cuttle
+cutups
+cuvees
+cyanic
+cyanid
+cyanin
+cyborg
+cycads
+cycled
+cycler
+cycles
+cyclic
+cyclin
+cyclos
+cyders
+cyeses
+cyesis
+cygnet
+cymars
+cymbal
+cymene
+cymlin
+cymoid
+cymols
+cymose
+cymous
+cynics
+cypher
+cypres
+cyprus
+cystic
+cytons
+dabbed
+dabber
+dabble
+dachas
+dacite
+dacker
+dacoit
+dacron
+dactyl
+daddle
+dadgum
+dadoed
+dadoes
+daedal
+daemon
+daffed
+dafter
+daftly
+daggas
+dagger
+daggle
+dagoba
+dagoes
+dahlia
+dahoon
+daiker
+daikon
+daimen
+daimio
+daimon
+daimyo
+dainty
+daises
+dakoit
+dalasi
+daledh
+daleth
+dalles
+dalton
+damage
+damans
+damars
+damask
+dammar
+dammed
+dammer
+dammit
+damned
+damner
+damped
+dampen
+damper
+damply
+damsel
+damson
+danced
+dancer
+dances
+dander
+dandle
+danged
+danger
+dangle
+dangly
+danios
+danish
+danker
+dankly
+daphne
+dapped
+dapper
+dapple
+darbar
+darers
+darics
+daring
+darked
+darken
+darker
+darkey
+darkie
+darkle
+darkly
+darned
+darnel
+darner
+darted
+darter
+dartle
+dashed
+dasher
+dashes
+dashis
+dassie
+datary
+datcha
+daters
+dating
+dative
+dattos
+datums
+datura
+daubed
+dauber
+daubes
+daubry
+daunts
+dauted
+dautie
+davens
+davies
+davits
+dawdle
+dawing
+dawned
+dawted
+dawtie
+daybed
+dayfly
+daylit
+dazing
+dazzle
+deacon
+deaden
+deader
+deadly
+deafen
+deafer
+deafly
+deairs
+dealer
+deaned
+dearer
+dearie
+dearly
+dearth
+deasil
+deaths
+deathy
+deaved
+deaves
+debags
+debark
+debars
+debase
+debate
+debeak
+debits
+debone
+debris
+debtor
+debugs
+debunk
+debuts
+debyes
+decade
+decafs
+decals
+decamp
+decane
+decant
+decare
+decays
+deceit
+decent
+decern
+decide
+decile
+decked
+deckel
+decker
+deckle
+declaw
+decoct
+decode
+decors
+decoys
+decree
+decury
+dedans
+deduce
+deduct
+deeded
+deejay
+deemed
+deepen
+deeper
+deeply
+deewan
+deface
+defame
+defang
+defats
+defeat
+defect
+defend
+defers
+deffer
+defied
+defier
+defies
+defile
+define
+deflea
+defoam
+defogs
+deform
+defrag
+defray
+defter
+deftly
+defuel
+defund
+defuse
+defuze
+degage
+degame
+degami
+degerm
+degree
+degums
+degust
+dehorn
+dehort
+deiced
+deicer
+deices
+deific
+deigns
+deisms
+deists
+deixis
+deject
+dekare
+deking
+dekkos
+delate
+delays
+delead
+delete
+delfts
+delict
+delime
+delish
+delist
+deltas
+deltic
+delude
+deluge
+deluxe
+delved
+delver
+delves
+demand
+demark
+demast
+demean
+dement
+demies
+demise
+demits
+demobs
+demode
+demoed
+demons
+demote
+demure
+demurs
+denari
+denars
+denary
+dengue
+denial
+denied
+denier
+denies
+denims
+denned
+denote
+denser
+dental
+dented
+dentil
+dentin
+denude
+deodar
+depart
+depend
+deperm
+depict
+deploy
+depone
+deport
+depose
+depots
+depths
+depute
+deputy
+derail
+derate
+derats
+derays
+deride
+derive
+dermal
+dermas
+dermic
+dermis
+derris
+desalt
+desand
+descry
+desert
+design
+desire
+desist
+desman
+desmid
+desorb
+desoxy
+despot
+detach
+detail
+detain
+detect
+detent
+deters
+detest
+detick
+detour
+deuced
+deuces
+devein
+devels
+devest
+device
+devils
+devise
+devoid
+devoir
+devons
+devote
+devour
+devout
+dewans
+dewars
+dewier
+dewily
+dewing
+dewlap
+dewool
+deworm
+dexies
+dexter
+dextro
+dezinc
+dharma
+dharna
+dhobis
+dholes
+dhooly
+dhoora
+dhooti
+dhotis
+dhurna
+dhutis
+diacid
+diadem
+dialed
+dialer
+dialog
+diamin
+diaper
+diapir
+diatom
+diazin
+dibbed
+dibber
+dibble
+dibbuk
+dicast
+dicers
+dicier
+dicing
+dicots
+dictum
+didact
+diddle
+diddly
+didies
+didoes
+dieing
+dienes
+dieoff
+diesel
+dieses
+diesis
+dieted
+dieter
+differ
+digamy
+digest
+digged
+digger
+dights
+digits
+diglot
+dikdik
+dikers
+diking
+diktat
+dilate
+dildoe
+dildos
+dilled
+dilute
+dimers
+dimity
+dimmed
+dimmer
+dimout
+dimple
+dimply
+dimwit
+dinars
+dindle
+dinero
+diners
+dinged
+dinger
+dinges
+dingey
+dinghy
+dingle
+dingus
+dining
+dinked
+dinkey
+dinkly
+dinkum
+dinned
+dinner
+dinted
+diobol
+diodes
+dioecy
+dioxan
+dioxid
+dioxin
+diplex
+diploe
+dipnet
+dipody
+dipole
+dipped
+dipper
+dipsas
+dipsos
+diquat
+dirams
+dirdum
+direct
+direly
+direst
+dirges
+dirham
+dirked
+dirled
+dirndl
+disarm
+disbar
+disbud
+disced
+discos
+discus
+diseur
+dished
+dishes
+disked
+dismal
+dismay
+dismes
+disown
+dispel
+dissed
+disses
+distal
+distil
+disuse
+dither
+dittos
+ditzes
+diuron
+divans
+divers
+divert
+divest
+divide
+divine
+diving
+divots
+diwans
+dixits
+dizens
+djebel
+djinni
+djinns
+djinny
+doable
+doated
+dobber
+dobbin
+dobies
+doblas
+doblon
+dobras
+dobros
+dobson
+docent
+docile
+docked
+docker
+docket
+doctor
+dodder
+dodged
+dodgem
+dodger
+dodges
+dodoes
+doffed
+doffer
+dogdom
+dogear
+dogeys
+dogged
+dogger
+doggie
+dogies
+dogleg
+dogmas
+dognap
+doiled
+doings
+doited
+doling
+dollar
+dolled
+dollop
+dolman
+dolmas
+dolmen
+dolors
+dolour
+domain
+domine
+doming
+domino
+donate
+donees
+dongle
+donjon
+donkey
+donnas
+donned
+donnee
+donors
+donsie
+donuts
+donzel
+doobie
+doodad
+doodle
+doodoo
+doofus
+doolee
+doolie
+doomed
+doowop
+doozer
+doozie
+dopant
+dopers
+dopier
+dopily
+doping
+dorado
+dorbug
+dories
+dormer
+dormie
+dormin
+dorper
+dorsad
+dorsal
+dorsel
+dorser
+dorsum
+dosage
+dosers
+dosing
+dossal
+dossed
+dossel
+dosser
+dosses
+dossil
+dotage
+dotard
+doters
+dotier
+doting
+dotted
+dottel
+dotter
+dottle
+double
+doubly
+doubts
+doughs
+dought
+doughy
+doulas
+doumas
+dourah
+douras
+dourer
+dourly
+doused
+douser
+douses
+dovens
+dovish
+dowels
+dowers
+dowery
+dowing
+downed
+downer
+dowsed
+dowser
+dowses
+doxies
+doyens
+doyley
+dozens
+dozers
+dozier
+dozily
+dozing
+drably
+drachm
+draffs
+draffy
+drafts
+drafty
+dragee
+draggy
+dragon
+drails
+drains
+drakes
+dramas
+drawee
+drawer
+drawls
+drawly
+drayed
+dreads
+dreams
+dreamt
+dreamy
+drears
+dreary
+drecks
+drecky
+dredge
+dreggy
+dreich
+dreidl
+dreigh
+drench
+dressy
+driegh
+driers
+driest
+drifts
+drifty
+drills
+drinks
+drippy
+drivel
+driven
+driver
+drives
+drogue
+droids
+droits
+drolls
+drolly
+dromon
+droned
+droner
+drones
+drongo
+drools
+drooly
+droops
+droopy
+dropsy
+drosky
+drossy
+drouks
+drouth
+droved
+drover
+droves
+drownd
+drowns
+drowse
+drowsy
+drudge
+druggy
+druids
+drumly
+drunks
+drupes
+druses
+dryads
+dryers
+dryest
+drying
+dryish
+drylot
+dually
+dubbed
+dubber
+dubbin
+ducats
+ducked
+ducker
+duckie
+ductal
+ducted
+duddie
+dudeen
+duding
+dudish
+dueled
+dueler
+duelli
+duello
+duende
+duenna
+dueted
+duffel
+duffer
+duffle
+dugong
+dugout
+duiker
+duking
+dulcet
+dulias
+dulled
+duller
+dulses
+dumbed
+dumber
+dumbly
+dumbos
+dumdum
+dumped
+dumper
+dunams
+dunces
+dunged
+dunite
+dunked
+dunker
+dunlin
+dunned
+dunner
+dunted
+duolog
+duomos
+dupers
+dupery
+duping
+duplex
+dupped
+durbar
+duress
+durian
+during
+durion
+durned
+durocs
+durras
+durrie
+durums
+dusked
+dusted
+duster
+dustup
+duties
+duvets
+dwarfs
+dweebs
+dweeby
+dwells
+dwined
+dwines
+dyable
+dyadic
+dybbuk
+dyeing
+dyings
+dyking
+dynamo
+dynast
+dynein
+dynels
+dynode
+dyvour
+eagers
+eagled
+eagles
+eaglet
+eagres
+earbud
+earful
+earing
+earlap
+earned
+earner
+earths
+earthy
+earwax
+earwig
+easels
+easier
+easies
+easily
+easing
+easter
+eaters
+eatery
+eating
+ebbets
+ebbing
+ebooks
+ecarte
+ecesic
+ecesis
+echard
+eching
+echini
+echoed
+echoer
+echoes
+echoey
+echoic
+eclair
+eclats
+ectype
+eczema
+eddied
+eddies
+eddoes
+edemas
+edenic
+edgers
+edgier
+edgily
+edging
+edible
+edicts
+ediles
+edited
+editor
+educed
+educes
+educts
+eelier
+eerier
+eerily
+efface
+effect
+effete
+effigy
+efflux
+effort
+effuse
+egesta
+egests
+eggars
+eggcup
+eggers
+egging
+eggnog
+egises
+egoism
+egoist
+egress
+egrets
+eiders
+eidola
+eighth
+eights
+eighty
+eikons
+either
+ejecta
+ejects
+ekuele
+elains
+elands
+elapid
+elapse
+elated
+elater
+elates
+elbows
+elders
+eldest
+elects
+elegit
+elemis
+eleven
+elevon
+elfins
+elfish
+elicit
+elided
+elides
+elints
+elites
+elixir
+elmier
+elodea
+eloign
+eloins
+eloped
+eloper
+elopes
+eluant
+eluate
+eluded
+eluder
+eludes
+eluent
+eluted
+elutes
+eluvia
+elvers
+elvish
+elytra
+emails
+embalm
+embank
+embark
+embars
+embays
+embeds
+embers
+emblem
+embody
+emboli
+emboly
+embosk
+emboss
+embows
+embrue
+embryo
+emceed
+emcees
+emdash
+emeers
+emends
+emerge
+emerod
+emeses
+emesis
+emetic
+emetin
+emeute
+emigre
+emmers
+emmets
+emodin
+emoted
+emoter
+emotes
+empale
+empery
+empire
+employ
+emydes
+enable
+enacts
+enamel
+enamor
+enates
+enatic
+encage
+encamp
+encase
+encash
+encina
+encode
+encore
+encyst
+endash
+endear
+enders
+ending
+endite
+endive
+endows
+endrin
+endued
+endues
+endure
+enduro
+energy
+enface
+enfold
+engage
+engild
+engine
+engird
+engirt
+englut
+engram
+engulf
+enhalo
+enigma
+enisle
+enjoin
+enjoys
+enlace
+enlist
+enmesh
+enmity
+ennead
+ennuis
+ennuye
+enokis
+enolic
+enosis
+enough
+enrage
+enrapt
+enrich
+enrobe
+enroll
+enrols
+enroot
+enserf
+ensign
+ensile
+ensoul
+ensued
+ensues
+ensure
+entail
+entera
+enters
+entice
+entire
+entity
+entoil
+entomb
+entrap
+entree
+enured
+enures
+envied
+envier
+envies
+enviro
+envois
+envoys
+enwind
+enwomb
+enwrap
+enzyme
+enzyms
+eocene
+eolian
+eolith
+eonian
+eonism
+eosine
+eosins
+epacts
+eparch
+ephahs
+ephebe
+ephebi
+ephods
+ephori
+ephors
+epical
+epigon
+epilog
+epimer
+epizoa
+epochs
+epodes
+eponym
+epopee
+eposes
+equals
+equate
+equids
+equine
+equips
+equity
+erased
+eraser
+erases
+erbium
+erects
+erenow
+ergate
+ergots
+ericas
+eringo
+ermine
+eroded
+erodes
+eroses
+erotic
+errand
+errant
+errata
+erring
+errors
+ersatz
+eructs
+erugos
+erupts
+ervils
+eryngo
+escape
+escarp
+escars
+eschar
+eschew
+escort
+escots
+escrow
+escudo
+eskars
+eskers
+espial
+espied
+espies
+esprit
+essays
+essoin
+estate
+esteem
+esters
+estops
+estral
+estray
+estrin
+estrum
+estrus
+etalon
+etamin
+etapes
+etched
+etcher
+etches
+eterne
+ethane
+ethene
+ethers
+ethics
+ethion
+ethnic
+ethnos
+ethoxy
+ethyls
+ethyne
+etoile
+etudes
+etwees
+etymon
+euchre
+eulogy
+eunuch
+eupnea
+eureka
+euripi
+euroky
+eutaxy
+evaded
+evader
+evades
+evened
+evener
+evenly
+events
+everts
+evicts
+eviler
+evilly
+evince
+evited
+evites
+evoked
+evoker
+evokes
+evolve
+evulse
+evzone
+exacta
+exacts
+exalts
+examen
+exarch
+exceed
+excels
+except
+excess
+excide
+excise
+excite
+excuse
+exedra
+exempt
+exequy
+exerts
+exeunt
+exhale
+exhort
+exhume
+exiled
+exiler
+exiles
+exilic
+exines
+exists
+exited
+exodoi
+exodos
+exodus
+exogen
+exonic
+exonym
+exotic
+expand
+expats
+expect
+expels
+expend
+expert
+expire
+expiry
+export
+expose
+exsect
+exsert
+extant
+extend
+extent
+extern
+extoll
+extols
+extort
+extras
+exuded
+exudes
+exults
+exurbs
+exuvia
+eyases
+eyebar
+eyecup
+eyeful
+eyeing
+eyelet
+eyelid
+eyries
+fabber
+fabled
+fabler
+fables
+fabric
+facade
+facers
+facete
+facets
+faceup
+facial
+facile
+facing
+factor
+facula
+fadein
+faders
+fading
+faenas
+faerie
+failed
+faille
+fainer
+faints
+faired
+fairer
+fairly
+faiths
+fajita
+fakeer
+fakers
+fakery
+faking
+fakirs
+falces
+falcon
+fallal
+fallen
+faller
+fallow
+falser
+falsie
+falter
+family
+famine
+faming
+famish
+famous
+famuli
+fandom
+fanega
+fanfic
+fangas
+fanged
+fanion
+fanjet
+fanned
+fanner
+fanons
+fantod
+fantom
+fanums
+faqirs
+faquir
+farads
+farced
+farcer
+farces
+farcie
+farded
+fardel
+farers
+farfal
+farfel
+farina
+faring
+farles
+farmed
+farmer
+farrow
+farted
+fasces
+fascia
+fashed
+fashes
+fasted
+fasten
+faster
+father
+fathom
+fating
+fatwas
+faucal
+fauces
+faucet
+faulds
+faults
+faulty
+faunae
+faunal
+faunas
+fauves
+favela
+favism
+favors
+favour
+fawned
+fawner
+faxing
+faying
+fazing
+fealty
+feared
+fearer
+feased
+feases
+feasts
+feater
+featly
+feazed
+feazes
+feckly
+fecund
+fedora
+feeble
+feebly
+feeder
+feeing
+feeler
+feezed
+feezes
+feigns
+feijoa
+feints
+feirie
+feists
+feisty
+felids
+feline
+fellah
+fellas
+felled
+feller
+felloe
+fellow
+felons
+felony
+felsic
+felted
+female
+femmes
+femora
+femurs
+fenced
+fencer
+fences
+fended
+fender
+fennec
+fennel
+feoffs
+ferals
+ferbam
+feriae
+ferial
+ferias
+ferine
+ferity
+ferlie
+fermis
+ferrel
+ferret
+ferric
+ferrum
+ferula
+ferule
+fervid
+fervor
+fescue
+fessed
+fesses
+festal
+fester
+fetial
+fetich
+feting
+fetish
+fetors
+fetted
+fetter
+fettle
+feuars
+feudal
+feuded
+feuing
+fevers
+fewest
+feyest
+fezzed
+fezzes
+fiacre
+fiance
+fiasco
+fibbed
+fibber
+fibers
+fibres
+fibril
+fibrin
+fibula
+fiches
+fichus
+ficins
+fickle
+fickly
+ficoes
+fiddle
+fiddly
+fidged
+fidges
+fidget
+fields
+fiends
+fierce
+fiesta
+fifers
+fifing
+fifths
+figged
+fights
+figure
+filers
+filets
+filial
+filing
+filled
+filler
+filles
+fillet
+fillip
+fillos
+filmed
+filmer
+filmic
+filmis
+filose
+filter
+filths
+filthy
+fimble
+finale
+finals
+fincas
+finder
+finely
+finery
+finest
+finger
+finial
+fining
+finish
+finite
+finito
+finked
+finned
+fiords
+fipple
+fiques
+firers
+firing
+firkin
+firman
+firmed
+firmer
+firmly
+firsts
+firths
+fiscal
+fished
+fisher
+fishes
+fistic
+fitchy
+fitful
+fitted
+fitter
+fivers
+fixate
+fixers
+fixing
+fixity
+fixure
+fizgig
+fizzed
+fizzer
+fizzes
+fizzle
+fjelds
+fjords
+flabby
+flacks
+flacon
+flaggy
+flagon
+flails
+flairs
+flaked
+flaker
+flakes
+flakey
+flambe
+flamed
+flamen
+flamer
+flames
+flanes
+flanks
+flappy
+flared
+flares
+flashy
+flasks
+flatly
+flatus
+flaunt
+flauta
+flavin
+flavor
+flawed
+flaxen
+flaxes
+flayed
+flayer
+fleams
+fleche
+flecks
+flecky
+fledge
+fledgy
+fleece
+fleech
+fleecy
+fleers
+fleets
+flench
+flense
+fleshy
+fletch
+fleury
+flexed
+flexes
+flexor
+fleyed
+flicks
+fliers
+fliest
+flight
+flimsy
+flinch
+flings
+flints
+flinty
+flippy
+flirts
+flirty
+flitch
+flited
+flites
+floats
+floaty
+flocci
+flocks
+flocky
+flongs
+floods
+flooey
+flooie
+floors
+floosy
+floozy
+floppy
+florae
+floral
+floras
+floret
+florid
+florin
+flossy
+flotas
+flours
+floury
+flouts
+flowed
+flower
+fluent
+fluffs
+fluffy
+fluids
+fluish
+fluked
+flukes
+flukey
+flumed
+flumes
+flumps
+flunks
+flunky
+fluors
+flurry
+fluted
+fluter
+flutes
+flutey
+fluxed
+fluxes
+fluyts
+flyboy
+flybys
+flyers
+flying
+flyman
+flymen
+flyoff
+flysch
+flyted
+flytes
+flyway
+foaled
+foamed
+foamer
+fobbed
+fodder
+fodgel
+foehns
+foeman
+foemen
+foetal
+foetid
+foetor
+foetus
+fogbow
+fogdog
+fogeys
+fogged
+fogger
+fogies
+foible
+foiled
+foined
+foison
+foists
+folate
+folded
+folder
+foldup
+foleys
+foliar
+folios
+folium
+folkie
+folksy
+folles
+follis
+follow
+foment
+fomite
+fonded
+fonder
+fondle
+fondly
+fondue
+fondus
+fontal
+foodie
+fooled
+footed
+footer
+footie
+footle
+footsy
+foozle
+fopped
+forage
+forams
+forays
+forbad
+forbid
+forbye
+forced
+forcer
+forces
+forded
+fordid
+foreby
+foredo
+forego
+forest
+forgat
+forged
+forger
+forges
+forget
+forgot
+forint
+forked
+forker
+formal
+format
+formed
+formee
+former
+formes
+formic
+formol
+formyl
+fornix
+forrit
+fortes
+fortis
+forums
+forwhy
+fossae
+fossas
+fosses
+fossil
+foster
+fought
+fouled
+fouler
+foully
+founds
+founts
+fourth
+foveae
+foveal
+foveas
+fowled
+fowler
+foxier
+foxily
+foxing
+foyers
+fozier
+fracas
+fracti
+fraena
+frails
+fraise
+framed
+framer
+frames
+francs
+franks
+frappe
+frater
+frauds
+frayed
+frazil
+freaks
+freaky
+freely
+freers
+freest
+freeze
+french
+frenum
+frenzy
+freres
+fresco
+fretty
+friars
+friary
+fridge
+friend
+friers
+frieze
+friges
+fright
+frigid
+frijol
+frills
+frilly
+fringe
+fringy
+frisee
+frises
+frisks
+frisky
+frites
+friths
+fritts
+frivol
+frized
+frizer
+frizes
+frizzy
+frocks
+froggy
+frolic
+fronds
+fronts
+frosts
+frosty
+froths
+frothy
+frouzy
+frowns
+frowst
+frowsy
+frowzy
+frozen
+frugal
+fruits
+fruity
+frumps
+frumpy
+frusta
+fryers
+frying
+frypan
+fubbed
+fucoid
+fucose
+fucous
+fuddle
+fudged
+fudges
+fueled
+fueler
+fugato
+fugged
+fugios
+fugled
+fugles
+fugued
+fugues
+fuhrer
+fulcra
+fulfil
+fulgid
+fulham
+fullam
+fulled
+fuller
+fulmar
+fumble
+fumers
+fumets
+fumier
+fuming
+fumuli
+funded
+funder
+fundus
+funest
+fungal
+fungic
+fungus
+funked
+funker
+funkia
+funned
+funnel
+funner
+furane
+furans
+furfur
+furies
+furled
+furler
+furore
+furors
+furred
+furrow
+furzes
+fusain
+fusees
+fusels
+fusile
+fusils
+fusing
+fusion
+fussed
+fusser
+fusses
+fustic
+fusuma
+futile
+futons
+future
+futzed
+futzes
+fuzees
+fuzils
+fuzing
+fuzzed
+fuzzes
+fylfot
+fynbos
+fyttes
+gabbed
+gabber
+gabble
+gabbro
+gabies
+gabion
+gabled
+gables
+gaboon
+gadded
+gadder
+gaddis
+gadfly
+gadget
+gadids
+gadoid
+gaeing
+gaffed
+gaffer
+gaffes
+gagaku
+gagers
+gagged
+gagger
+gaggle
+gaging
+gagman
+gagmen
+gaiety
+gaijin
+gained
+gainer
+gainly
+gainst
+gaited
+gaiter
+galago
+galahs
+galaxy
+galeae
+galeas
+galena
+galere
+galiot
+galled
+gallet
+galley
+gallic
+gallon
+gallop
+gallus
+galoot
+galops
+galore
+galosh
+galyac
+galyak
+gamays
+gambas
+gambes
+gambia
+gambir
+gambit
+gamble
+gambol
+gamely
+gamers
+gamest
+gamete
+gamier
+gamily
+gamine
+gaming
+gamins
+gammas
+gammed
+gammer
+gammon
+gamuts
+gander
+ganefs
+ganevs
+ganged
+ganger
+gangly
+gangue
+ganjah
+ganjas
+gannet
+ganofs
+ganoid
+gantry
+gaoled
+gaoler
+gapers
+gaping
+gapped
+garage
+garbed
+garble
+garcon
+gardai
+garden
+garget
+gargle
+garish
+garlic
+garner
+garnet
+garote
+garred
+garret
+garron
+garter
+garths
+garvey
+gasbag
+gascon
+gashed
+gasher
+gashes
+gasify
+gasket
+gaskin
+gaslit
+gasman
+gasmen
+gasped
+gasper
+gassed
+gasser
+gasses
+gasted
+gaster
+gateau
+gaters
+gather
+gating
+gators
+gauche
+gaucho
+gauged
+gauger
+gauges
+gaults
+gaumed
+gauzes
+gavage
+gavels
+gavial
+gavots
+gawked
+gawker
+gawped
+gawper
+gawsie
+gayals
+gazabo
+gazars
+gazebo
+gazers
+gazing
+gazoos
+gazump
+geared
+gecked
+geckos
+geegaw
+geeing
+geeked
+geests
+geezer
+geisha
+gelada
+gelant
+gelate
+gelati
+gelato
+gelcap
+gelded
+gelder
+gelees
+gelled
+gemmae
+gemmed
+gemote
+gemots
+gender
+genera
+genets
+geneva
+genial
+genies
+genips
+genius
+genoas
+genome
+genoms
+genres
+genros
+gentes
+gentil
+gentle
+gently
+gentoo
+gentry
+geodes
+geodic
+geoids
+gerahs
+gerbil
+gerent
+german
+germen
+gerund
+gestes
+gestic
+getter
+getups
+gewgaw
+geyser
+gharri
+gharry
+ghauts
+ghazis
+gherao
+ghetto
+ghibli
+ghosts
+ghosty
+ghouls
+ghylls
+giants
+giaour
+gibbed
+gibber
+gibbet
+gibbon
+gibers
+gibing
+giblet
+gibson
+giddap
+gieing
+gifted
+giftee
+gigged
+giggle
+giggly
+giglet
+giglot
+gigolo
+gigots
+gigues
+gilded
+gilder
+gilled
+giller
+gillie
+gimbal
+gimels
+gimlet
+gimmal
+gimmes
+gimmie
+gimped
+gingal
+ginger
+gingko
+ginkgo
+ginned
+ginner
+gipons
+gipped
+gipper
+girded
+girder
+girdle
+girlie
+girned
+girons
+girted
+girths
+gismos
+gitano
+gitted
+gittin
+givens
+givers
+giving
+gizmos
+glaces
+glacis
+glades
+gladly
+glaire
+glairs
+glairy
+glaive
+glamor
+glance
+glands
+glared
+glares
+glassy
+glazed
+glazer
+glazes
+gleams
+gleamy
+gleans
+glebae
+glebes
+gledes
+gleeds
+gleeks
+gleets
+gleety
+glegly
+gleyed
+glibly
+glided
+glider
+glides
+gliffs
+glimed
+glimes
+glints
+glinty
+glioma
+glitch
+glitzy
+gloams
+gloats
+global
+globby
+globed
+globes
+globin
+gloggs
+glomus
+glooms
+gloomy
+gloppy
+gloria
+glossa
+glossy
+glosts
+glouts
+gloved
+glover
+gloves
+glowed
+glower
+glozed
+glozes
+glucan
+gluers
+gluier
+gluily
+gluing
+glumes
+glumly
+glumpy
+glunch
+gluons
+glutei
+gluten
+glutes
+glycan
+glycin
+glycol
+glycyl
+glyphs
+gnarls
+gnarly
+gnarrs
+gnatty
+gnawed
+gnawer
+gneiss
+gnomes
+gnomic
+gnomon
+gnoses
+gnosis
+goaded
+goaled
+goalie
+goanna
+goatee
+gobang
+gobans
+gobbed
+gobbet
+gobble
+gobies
+goblet
+goblin
+goboes
+gobony
+godets
+godown
+godson
+gofers
+goffer
+goggle
+goggly
+goglet
+goings
+golden
+golder
+golems
+golfed
+golfer
+golosh
+gombos
+gomers
+gomuti
+gonefs
+goners
+gonged
+goniff
+gonifs
+gonion
+gonium
+gonofs
+gonoph
+goodby
+goodie
+goodly
+goofed
+googly
+googol
+gooier
+gooney
+goonie
+gooral
+goosed
+gooses
+goosey
+gopher
+gorals
+gorged
+gorger
+gorges
+gorget
+gorgon
+gorhen
+gorier
+gorily
+goring
+gormed
+gorses
+gospel
+gossan
+gossip
+gotcha
+gothic
+gotten
+gouged
+gouger
+gouges
+gourde
+gourds
+govern
+gowans
+gowany
+gowned
+goyish
+graals
+grabby
+graben
+graced
+graces
+graded
+grader
+grades
+gradin
+gradus
+grafts
+graham
+grails
+grains
+grainy
+gramas
+gramma
+gramme
+grampa
+gramps
+grands
+grange
+granny
+grants
+granum
+grapes
+grapey
+graphs
+grappa
+grasps
+grassy
+grated
+grater
+grates
+gratin
+gratis
+graved
+gravel
+graven
+graver
+graves
+gravid
+grayed
+grayer
+grayly
+grazed
+grazer
+grazes
+grease
+greasy
+greats
+greave
+grebes
+greeds
+greedy
+greens
+greeny
+greets
+gregos
+greige
+gremmy
+greyed
+greyer
+greyly
+grided
+grides
+griefs
+grieve
+griffe
+griffs
+grifts
+grigri
+grille
+grills
+grilse
+grimed
+grimes
+grimly
+grinch
+grinds
+gringa
+gringo
+griots
+griped
+griper
+gripes
+gripey
+grippe
+grippy
+grisly
+grison
+grists
+griths
+gritty
+grivet
+groans
+groats
+grocer
+groggy
+grooms
+groove
+groovy
+groped
+groper
+gropes
+grosze
+groszy
+grotto
+grotty
+grouch
+ground
+groups
+grouse
+grouts
+grouty
+groved
+grovel
+groves
+grower
+growls
+growly
+growth
+groyne
+grubby
+grudge
+gruels
+gruffs
+gruffy
+grugru
+grumes
+grumps
+grumpy
+grunge
+grungy
+grunts
+grutch
+guacos
+guaiac
+guanay
+guanin
+guanos
+guards
+guavas
+guenon
+guests
+guffaw
+guggle
+guglet
+guided
+guider
+guides
+guidon
+guilds
+guiled
+guiles
+guilts
+guilty
+guimpe
+guinea
+guiros
+guised
+guises
+guitar
+gulags
+gulden
+gulfed
+gulled
+gullet
+gulley
+gulped
+gulper
+gumbos
+gummas
+gummed
+gummer
+gundog
+gunite
+gunman
+gunmen
+gunned
+gunnel
+gunnen
+gunner
+gunsel
+gurged
+gurges
+gurgle
+gurnet
+gurney
+gushed
+gusher
+gushes
+gusset
+gussie
+gusted
+guttae
+gutted
+gutter
+guttle
+guying
+guyots
+guzzle
+gweduc
+gybing
+gyozas
+gypped
+gypper
+gypsum
+gyrase
+gyrate
+gyrene
+gyring
+gyrons
+gyrose
+gyttja
+gyving
+habile
+habits
+haboob
+haceks
+hacked
+hackee
+hackie
+hackle
+hackly
+hading
+hadith
+hadjee
+hadjes
+hadjis
+hadron
+haeing
+haemal
+haemic
+haemin
+haeres
+haffet
+haffit
+hafted
+hafter
+hagbut
+hagdon
+hagged
+haggis
+haggle
+haikus
+hailed
+hailer
+haints
+hairdo
+haired
+hajjes
+hajjis
+hakeem
+hakims
+halala
+halals
+halers
+haleru
+halest
+halide
+halids
+haling
+halite
+hallah
+hallal
+hallel
+halloa
+halloo
+hallos
+hallot
+hallow
+hallux
+halmas
+haloed
+haloes
+haloid
+halons
+halted
+halter
+halutz
+halvah
+halvas
+halved
+halves
+hamada
+hamals
+hamate
+hamaul
+hamlet
+hammal
+hammam
+hammed
+hammer
+hamper
+hamuli
+hamzah
+hamzas
+hances
+handax
+handed
+hander
+handle
+hangar
+hanger
+hangul
+hangup
+haniwa
+hanked
+hanker
+hankie
+hansas
+hansel
+hanses
+hansom
+hanted
+hantle
+haoles
+happed
+happen
+hapten
+haptic
+harbor
+harden
+harder
+hardly
+hareem
+harems
+haring
+harked
+harken
+harlot
+harmed
+harmer
+harmin
+harped
+harper
+harpin
+harrow
+hartal
+hashed
+hashes
+haslet
+hasped
+hassel
+hassle
+hasted
+hasten
+hastes
+hatbox
+haters
+hatful
+hating
+hatpin
+hatred
+hatted
+hatter
+haughs
+hauled
+hauler
+haulms
+haulmy
+haunch
+haunts
+hausen
+havens
+havers
+having
+havior
+havocs
+hawala
+hawing
+hawked
+hawker
+hawkey
+hawkie
+hawser
+hawses
+hayers
+haying
+haymow
+hazans
+hazard
+hazels
+hazers
+hazier
+hazily
+hazing
+hazmat
+hazzan
+headed
+header
+healed
+healer
+health
+heaped
+heaper
+hearer
+hearse
+hearth
+hearts
+hearty
+heated
+heater
+heaths
+heathy
+heaume
+heaved
+heaven
+heaver
+heaves
+heckle
+hectic
+hector
+heddle
+heders
+hedged
+hedger
+hedges
+heeded
+heeder
+heehaw
+heeled
+heeler
+heezed
+heezes
+hefted
+hefter
+hegari
+hegira
+heifer
+height
+heiled
+heinie
+heired
+heishi
+heists
+hejira
+heliac
+helios
+helium
+helled
+heller
+hellos
+helmed
+helmet
+helots
+helped
+helper
+helved
+helves
+hemins
+hemmed
+hemmer
+hemoid
+hempen
+hempie
+henbit
+henges
+henley
+hennas
+henrys
+hented
+hepcat
+hepper
+heptad
+herald
+herbal
+herbed
+herded
+herder
+herdic
+hereat
+hereby
+herein
+hereof
+hereon
+heresy
+hereto
+heriot
+hermae
+hermai
+hermit
+hernia
+heroes
+heroic
+herons
+herpes
+hetero
+hetman
+heuchs
+heughs
+hewers
+hewing
+hexade
+hexads
+hexane
+hexers
+hexing
+hexone
+hexose
+hexyls
+heyday
+heydey
+hiatal
+hiatus
+hiccup
+hickey
+hickie
+hidden
+hiders
+hiding
+hieing
+hiemal
+higgle
+higher
+highly
+highth
+hights
+hijabs
+hijack
+hijrah
+hijras
+hikers
+hiking
+hilled
+hiller
+hilloa
+hillos
+hilted
+hinder
+hinged
+hinger
+hinges
+hinted
+hinter
+hipped
+hipper
+hippos
+hirees
+hirers
+hiring
+hirple
+hirsel
+hirsle
+hispid
+hissed
+hisser
+hisses
+histed
+hither
+hitman
+hitmen
+hitter
+hiving
+hoagie
+hoards
+hoarse
+hoaxed
+hoaxer
+hoaxes
+hobbed
+hobber
+hobbit
+hobble
+hobnob
+hoboed
+hoboes
+hocked
+hocker
+hockey
+hodads
+hodden
+hoddin
+hoeing
+hogans
+hogged
+hogger
+hogget
+hognut
+hogtie
+hoicks
+hoiden
+hoised
+hoises
+hoists
+hokier
+hokily
+hoking
+hokums
+holard
+holden
+holder
+holdup
+holier
+holies
+holily
+holing
+holism
+holist
+holked
+hollas
+holler
+holloa
+holloo
+hollos
+hollow
+holmic
+holpen
+homage
+hombre
+homely
+homers
+homily
+homing
+hominy
+hommos
+honans
+honcho
+hondas
+hondle
+honers
+honest
+honied
+honing
+honked
+honker
+honors
+honour
+hooded
+hoodoo
+hooeys
+hoofed
+hoofer
+hooked
+hookey
+hookup
+hoolie
+hooped
+hooper
+hoopla
+hoopoe
+hoopoo
+hoorah
+hooray
+hootch
+hooted
+hooter
+hooved
+hoover
+hooves
+hopers
+hoping
+hopped
+hopper
+hopple
+horahs
+horary
+horded
+hordes
+horned
+hornet
+horrid
+horror
+horsed
+horses
+horsey
+horste
+horsts
+hosels
+hosers
+hoseys
+hosier
+hosing
+hostas
+hosted
+hostel
+hostly
+hotbed
+hotbox
+hotdog
+hotels
+hotrod
+hotted
+hotter
+hottie
+houdah
+hounds
+houris
+hourly
+housed
+housel
+houser
+houses
+hovels
+hovers
+howdah
+howdie
+howffs
+howked
+howled
+howler
+howlet
+hoyden
+hoyles
+hryvna
+hubbly
+hubbub
+hubcap
+hubris
+huckle
+huddle
+huffed
+hugely
+hugest
+hugged
+hugger
+huipil
+hulked
+hulled
+huller
+hulloa
+hulloo
+hullos
+humane
+humans
+humate
+humble
+humbly
+humbug
+humeri
+hummed
+hummer
+hummus
+humors
+humour
+humped
+humper
+humphs
+humvee
+hunger
+hungry
+hunker
+hunkey
+hunkie
+hunted
+hunter
+huppah
+hurdle
+hurled
+hurler
+hurley
+hurrah
+hurray
+hursts
+hurter
+hurtle
+hushed
+hushes
+husked
+husker
+hussar
+hustle
+hutted
+hutzpa
+huzzah
+huzzas
+hyaena
+hyalin
+hybrid
+hybris
+hydrae
+hydras
+hydria
+hydric
+hydrid
+hydros
+hyenas
+hyenic
+hyetal
+hymens
+hymnal
+hymned
+hyoids
+hypers
+hyphae
+hyphal
+hyphen
+hyping
+hypnic
+hypoed
+hysons
+hyssop
+iambic
+iambus
+iatric
+ibexes
+ibices
+ibidem
+ibises
+icebox
+icecap
+iceman
+icemen
+ichors
+icicle
+iciest
+icings
+ickers
+ickier
+ickily
+icones
+iconic
+ideals
+ideate
+idiocy
+idioms
+idiots
+idlers
+idlest
+idling
+idylls
+iffier
+igging
+igloos
+ignify
+ignite
+ignore
+iguana
+ihrams
+ilexes
+iliads
+illest
+illite
+illude
+illume
+imaged
+imager
+images
+imagos
+imaret
+imaums
+imbalm
+imbark
+imbeds
+imbibe
+imbody
+imbrue
+imbued
+imbues
+imides
+imidic
+imines
+immane
+immesh
+immies
+immune
+immure
+impact
+impair
+impala
+impale
+impark
+impart
+impawn
+impede
+impels
+impend
+imphee
+imping
+impish
+impled
+impone
+import
+impose
+impost
+improv
+impugn
+impure
+impute
+inaner
+inanes
+inarch
+inarms
+inborn
+inbred
+incage
+incant
+incase
+incent
+incept
+incest
+inched
+incher
+inches
+incise
+incite
+inclip
+incogs
+income
+incony
+incubi
+incult
+incurs
+incuse
+indaba
+indeed
+indene
+indent
+indict
+indies
+indign
+indigo
+indite
+indium
+indole
+indols
+indoor
+indows
+indris
+induce
+induct
+indued
+indues
+indult
+inerts
+infall
+infamy
+infant
+infare
+infect
+infers
+infest
+infill
+infirm
+inflow
+influx
+infold
+inform
+infuse
+ingate
+ingest
+ingles
+ingots
+ingulf
+inhale
+inhaul
+inhere
+inhume
+inions
+inject
+injure
+injury
+inkers
+inkier
+inking
+inkjet
+inkles
+inkpot
+inlace
+inlaid
+inland
+inlays
+inlets
+inlier
+inmate
+inmesh
+inmost
+innage
+innate
+inners
+inning
+inpour
+inputs
+inroad
+inruns
+inrush
+insane
+inseam
+insect
+insert
+insets
+inside
+insist
+insole
+insoul
+inspan
+instal
+instar
+instep
+instil
+insult
+insure
+intact
+intake
+intend
+intent
+intern
+inters
+intima
+intime
+intine
+intomb
+intone
+intort
+intown
+intron
+intros
+intuit
+inturn
+inulin
+inured
+inures
+inurns
+invade
+invars
+invent
+invert
+invest
+invite
+invoke
+inwall
+inward
+inwind
+inwove
+inwrap
+iodate
+iodide
+iodids
+iodine
+iodins
+iodise
+iodism
+iodize
+iodous
+iolite
+ionics
+ionise
+ionium
+ionize
+ionone
+ipecac
+irades
+irater
+ireful
+irenic
+irides
+iridic
+irised
+irises
+iritic
+iritis
+irking
+irokos
+ironed
+ironer
+irones
+ironic
+irreal
+irrupt
+isatin
+ischia
+island
+islets
+isling
+isobar
+isogon
+isohel
+isolog
+isomer
+isopod
+isseis
+issued
+issuer
+issues
+isthmi
+istles
+italic
+itched
+itches
+itemed
+iterum
+itself
+ixodid
+ixoras
+ixtles
+izzard
+jabbed
+jabber
+jabiru
+jabots
+jacals
+jacana
+jackal
+jacked
+jacker
+jacket
+jading
+jadish
+jaeger
+jagers
+jagged
+jagger
+jagras
+jaguar
+jailed
+jailer
+jailor
+jalaps
+jalops
+jalopy
+jambed
+jambes
+jammed
+jammer
+jangle
+jangly
+japans
+japers
+japery
+japing
+jarful
+jargon
+jarina
+jarrah
+jarred
+jarvey
+jasmin
+jasper
+jassid
+jauked
+jaunce
+jaunts
+jaunty
+jauped
+jawans
+jawing
+jaygee
+jayvee
+jazzbo
+jazzed
+jazzer
+jazzes
+jeaned
+jebels
+jeeing
+jeeped
+jeered
+jeerer
+jehads
+jejuna
+jejune
+jelled
+jellos
+jennet
+jerboa
+jereed
+jerids
+jerked
+jerrid
+jersey
+jessed
+jesses
+jested
+jester
+jesuit
+jetlag
+jetons
+jetsam
+jetsom
+jetted
+jetton
+jetway
+jewels
+jezail
+jibbed
+jibber
+jibers
+jibing
+jicama
+jigged
+jigger
+jiggle
+jiggly
+jigsaw
+jihads
+jilted
+jilter
+jiminy
+jimmie
+jimper
+jimply
+jingal
+jingko
+jingle
+jingly
+jinked
+jinker
+jinnee
+jinnis
+jitney
+jitter
+jivers
+jivier
+jiving
+jnanas
+jobbed
+jobber
+jockey
+jockos
+jocose
+jocund
+jogged
+jogger
+joggle
+johnny
+joined
+joiner
+joints
+joists
+jojoba
+jokers
+jokier
+jokily
+joking
+jolted
+jolter
+jorams
+jordan
+jorums
+joseph
+joshed
+josher
+joshes
+josses
+jostle
+jotted
+jotter
+jouals
+jouked
+joules
+jounce
+jouncy
+journo
+jousts
+jovial
+jowars
+jowing
+jowled
+joyful
+joying
+joyous
+joypop
+jubbah
+jubhah
+jubile
+judder
+judged
+judger
+judges
+judoka
+jugate
+jugful
+jugged
+juggle
+jugula
+jugums
+juiced
+juicer
+juices
+jujube
+juking
+juleps
+jumbal
+jumble
+jumbos
+jumped
+jumper
+juncos
+jungle
+jungly
+junior
+junked
+junker
+junket
+juntas
+juntos
+jupons
+jurant
+jurats
+jurels
+juried
+juries
+jurist
+jurors
+justed
+juster
+justle
+justly
+jutted
+kababs
+kabaka
+kabala
+kabars
+kabaya
+kabiki
+kabobs
+kabuki
+kaffir
+kafirs
+kaftan
+kahuna
+kaiaks
+kainit
+kaiser
+kakapo
+kalams
+kalian
+kalifs
+kaliph
+kalium
+kalmia
+kalong
+kalpac
+kalpak
+kalpas
+kamala
+kamiks
+kamsin
+kanaka
+kanban
+kanjis
+kantar
+kanzus
+kaolin
+kaonic
+kapoks
+kappas
+kaputt
+karate
+karats
+karmas
+karmic
+karoos
+kaross
+karroo
+karsts
+kasbah
+kashas
+kasher
+kation
+kauris
+kavass
+kayaks
+kayles
+kayoed
+kayoes
+kazoos
+kebabs
+kebars
+kebbie
+keblah
+kebobs
+kecked
+keckle
+keddah
+kedged
+kedges
+keeked
+keeled
+keened
+keener
+keenly
+keeper
+keeves
+kefirs
+kegged
+kegger
+kegler
+keleps
+kelims
+keloid
+kelped
+kelpie
+kelson
+kelter
+kelvin
+kenafs
+kendos
+kenned
+kennel
+kentes
+kepped
+keppen
+kerbed
+kerfed
+kermes
+kermis
+kerned
+kernel
+kernes
+kerria
+kersey
+ketene
+ketols
+ketone
+ketose
+kettle
+kevels
+kevils
+kewpie
+keying
+keypad
+keypal
+keyset
+keyway
+khadis
+khakis
+khalif
+khaphs
+khazen
+khedah
+khedas
+kheths
+khoums
+kiangs
+kiaugh
+kibbeh
+kibbes
+kibbis
+kibble
+kibeis
+kibitz
+kiblah
+kiblas
+kibosh
+kicked
+kicker
+kickup
+kidded
+kidder
+kiddie
+kiddos
+kidnap
+kidney
+kidvid
+kilims
+killed
+killer
+killie
+kilned
+kilted
+kilter
+kiltie
+kimchi
+kimono
+kinara
+kinase
+kinder
+kindle
+kindly
+kinema
+kinged
+kingly
+kinins
+kinked
+kiosks
+kipped
+kippen
+kipper
+kirned
+kirsch
+kirtle
+kishka
+kishke
+kismat
+kismet
+kissed
+kisser
+kisses
+kitbag
+kiters
+kithed
+kithes
+kiting
+kitsch
+kitted
+kittel
+kitten
+kittle
+klatch
+klaxon
+klepht
+klepto
+klicks
+klongs
+kloofs
+kludge
+kludgy
+kluged
+kluges
+klutzy
+knacks
+knarry
+knaurs
+knaves
+knawel
+knawes
+kneads
+kneels
+knells
+knifed
+knifer
+knifes
+knight
+knives
+knobby
+knocks
+knolls
+knolly
+knosps
+knotty
+knouts
+knower
+knowns
+knubby
+knurls
+knurly
+koalas
+kobold
+koines
+kolhoz
+kolkoz
+kombus
+konked
+koodoo
+kookie
+kopeck
+kopeks
+kopjes
+koppas
+koppie
+korats
+kormas
+koruna
+koruny
+kosher
+kotows
+koumis
+koumys
+kouroi
+kouros
+kousso
+kowtow
+kraals
+krafts
+kraits
+kraken
+krater
+krauts
+kreeps
+krewes
+krills
+krises
+kronen
+kroner
+kronor
+kronur
+krooni
+kroons
+krubis
+krubut
+kuchen
+kudzus
+kugels
+kukris
+kulaki
+kulaks
+kultur
+kumiss
+kummel
+kurgan
+kurtas
+kussos
+kuvasz
+kvases
+kvells
+kvetch
+kwacha
+kwanza
+kyacks
+kybosh
+kyries
+kythed
+kythes
+laager
+labara
+labels
+labile
+labium
+labors
+labour
+labret
+labrum
+lacers
+laches
+lacier
+lacily
+lacing
+lacked
+lacker
+lackey
+lactam
+lactic
+lacuna
+lacune
+ladder
+laddie
+ladens
+laders
+ladies
+lading
+ladino
+ladled
+ladler
+ladles
+ladron
+lagans
+lagend
+lagers
+lagged
+lagger
+lagoon
+laguna
+lagune
+lahars
+laical
+laichs
+laighs
+lairds
+laired
+lakers
+lakier
+laking
+lallan
+lalled
+lambda
+lambed
+lamber
+lambie
+lamedh
+lameds
+lamely
+lament
+lamest
+lamiae
+lamias
+lamina
+laming
+lammed
+lampad
+lampas
+lamped
+lanais
+lanate
+lanced
+lancer
+lances
+lancet
+landau
+landed
+lander
+lanely
+langue
+langur
+lanker
+lankly
+lanner
+lanose
+lanugo
+laogai
+lapdog
+lapels
+lapful
+lapins
+lapped
+lapper
+lappet
+lapsed
+lapser
+lapses
+lapsus
+laptop
+larded
+larder
+lardon
+larees
+larger
+larges
+largos
+lariat
+larine
+larked
+larker
+larrup
+larums
+larvae
+larval
+larvas
+larynx
+lascar
+lasers
+lashed
+lasher
+lashes
+lasing
+lasses
+lassie
+lassis
+lassos
+lasted
+laster
+lastly
+lateen
+lately
+latens
+latent
+latest
+lathed
+lather
+lathes
+lathis
+latigo
+latina
+latino
+latish
+latkes
+latria
+latten
+latter
+lattes
+lattin
+lauans
+lauded
+lauder
+laughs
+launce
+launch
+laurae
+lauras
+laurel
+lavabo
+lavage
+lavash
+laveer
+lavers
+laving
+lavish
+lawful
+lawine
+lawing
+lawman
+lawmen
+lawyer
+laxest
+laxity
+layers
+laying
+layins
+layman
+laymen
+layoff
+layout
+layups
+lazars
+lazied
+lazier
+lazies
+lazily
+lazing
+lazuli
+leachy
+leaded
+leaden
+leader
+leafed
+league
+leaked
+leaker
+leally
+lealty
+leaned
+leaner
+leanly
+leaped
+leaper
+learns
+learnt
+leased
+leaser
+leases
+leasts
+leaved
+leaven
+leaver
+leaves
+lebens
+leched
+lecher
+leches
+lechwe
+lectin
+lector
+ledger
+ledges
+leered
+leeway
+lefter
+legacy
+legals
+legate
+legato
+legend
+legers
+legged
+leggin
+legion
+legist
+legits
+legman
+legmen
+legong
+legume
+lehuas
+lekked
+lekvar
+lemans
+lemmas
+lemons
+lemony
+lemurs
+lender
+length
+lenite
+lenity
+lensed
+lenses
+lenten
+lentic
+lentil
+lentos
+leones
+lepers
+leptin
+lepton
+lesion
+lessee
+lessen
+lesser
+lesson
+lessor
+lethal
+lethes
+letted
+letter
+letups
+leucin
+leudes
+leukon
+levant
+leveed
+levees
+levels
+levers
+levied
+levier
+levies
+levins
+levity
+lewder
+lewdly
+lexeme
+lexica
+lezzes
+lezzie
+liable
+liaise
+lianas
+lianes
+liangs
+liards
+libber
+libels
+libers
+libido
+liblab
+librae
+libras
+lichee
+lichen
+liches
+lichis
+lichts
+licked
+licker
+lictor
+lidars
+lidded
+lieder
+liefer
+liefly
+lieges
+lienal
+lierne
+liever
+lifers
+lifted
+lifter
+ligand
+ligans
+ligase
+ligate
+ligers
+lights
+lignan
+lignin
+ligula
+ligule
+ligure
+likely
+likens
+likers
+likest
+liking
+likuta
+lilacs
+lilied
+lilies
+lilted
+limans
+limbas
+limbed
+limber
+limbic
+limbos
+limbus
+limens
+limeys
+limier
+limina
+liming
+limits
+limmer
+limned
+limner
+limnic
+limpas
+limped
+limper
+limpet
+limpid
+limply
+limpsy
+limuli
+linacs
+linage
+linden
+lineal
+linear
+linens
+lineny
+liners
+lineup
+lingam
+lingas
+linger
+lingua
+linier
+lining
+linins
+linked
+linker
+linkup
+linnet
+linsey
+linted
+lintel
+linter
+lintol
+linums
+lipase
+lipide
+lipids
+lipins
+lipoid
+lipoma
+lipped
+lippen
+lipper
+liquid
+liquor
+liroth
+lisles
+lisped
+lisper
+lissom
+listed
+listee
+listel
+listen
+lister
+litany
+litchi
+liters
+lither
+lithia
+lithic
+lithos
+litmus
+litres
+litten
+litter
+little
+lively
+livens
+livers
+livery
+livest
+livier
+living
+livres
+livyer
+lizard
+llamas
+llanos
+loaded
+loader
+loafed
+loafer
+loamed
+loaned
+loaner
+loathe
+loaves
+lobate
+lobbed
+lobber
+lobule
+locale
+locals
+locate
+lochan
+lochia
+locked
+locker
+locket
+lockup
+locoed
+locoes
+locule
+loculi
+locums
+locust
+lodens
+lodged
+lodger
+lodges
+lofted
+lofter
+logans
+logged
+logger
+loggia
+loggie
+logics
+logier
+logily
+logins
+logion
+logjam
+logons
+logway
+loided
+loiter
+lolled
+loller
+lollop
+lomein
+loment
+lonely
+loners
+longan
+longed
+longer
+longes
+longly
+looeys
+loofah
+loofas
+looies
+looing
+looked
+looker
+lookup
+loomed
+looped
+looper
+loosed
+loosen
+looser
+looses
+looted
+looter
+lopers
+loping
+lopped
+lopper
+loquat
+lorans
+lorded
+lordly
+loreal
+lorica
+lories
+losels
+losers
+losing
+losses
+lotahs
+lotion
+lotted
+lotter
+lottes
+lottos
+louche
+louden
+louder
+loudly
+loughs
+louies
+loumas
+lounge
+loungy
+louped
+loupen
+loupes
+loured
+loused
+louses
+louted
+louver
+louvre
+lovage
+lovats
+lovely
+lovers
+loving
+lowboy
+lowers
+lowery
+lowest
+lowing
+lowish
+loxing
+lubber
+lubing
+lubric
+lucent
+lucern
+lucite
+lucked
+luckie
+lucres
+luetic
+luffas
+luffed
+lugers
+lugged
+lugger
+luggie
+luging
+lulled
+luller
+lumbar
+lumber
+lumens
+lumina
+lummox
+lumped
+lumpen
+lumper
+lunacy
+lunars
+lunate
+lunets
+lungan
+lunged
+lungee
+lunger
+lunges
+lungis
+lungyi
+lunier
+lunies
+lunker
+lunted
+lunula
+lunule
+lupine
+lupins
+lupous
+lurdan
+lurers
+luring
+lurked
+lurker
+lushed
+lusher
+lushes
+lushly
+lusted
+luster
+lustra
+lustre
+luteal
+lutein
+luteum
+luting
+lutist
+lutzes
+luxate
+luxury
+lyases
+lycees
+lyceum
+lychee
+lyches
+lycras
+lyings
+lymphs
+lynxes
+lyrate
+lyrics
+lyrism
+lyrist
+lysate
+lysine
+lysing
+lysins
+lyssas
+lyttae
+lyttas
+macaco
+macaws
+macers
+maches
+machos
+macing
+mackle
+macled
+macles
+macons
+macron
+macros
+macula
+macule
+madame
+madams
+madcap
+madded
+madden
+madder
+madras
+madres
+madtom
+maduro
+maenad
+maffia
+mafias
+maftir
+maggot
+magian
+magics
+magilp
+maglev
+magmas
+magnet
+magnum
+magots
+magpie
+maguey
+mahoes
+mahout
+mahzor
+maiden
+maigre
+maihem
+mailed
+mailer
+mailes
+maills
+maimed
+maimer
+mainly
+maists
+maizes
+majors
+makars
+makers
+makeup
+making
+makuta
+malady
+malars
+malate
+malfed
+malgre
+malice
+malign
+maline
+malkin
+malled
+mallee
+mallei
+mallet
+mallow
+maloti
+malted
+maltha
+maltol
+mambas
+mambos
+mameys
+mamies
+mamluk
+mammae
+mammal
+mammas
+mammee
+mammer
+mammet
+mammey
+mammie
+mammon
+mamzer
+manage
+manana
+manats
+manche
+manege
+manful
+mangas
+mangel
+manger
+manges
+mangey
+mangle
+mangos
+maniac
+manias
+manics
+manila
+manioc
+manito
+manitu
+mannan
+mannas
+manned
+manner
+manors
+manque
+manses
+mantas
+mantel
+mantes
+mantic
+mantid
+mantis
+mantle
+mantra
+mantua
+manual
+manure
+maples
+mapped
+mapper
+maquis
+maraca
+maraud
+marble
+marbly
+marcel
+margay
+marges
+margin
+marina
+marine
+marish
+markas
+marked
+marker
+market
+markka
+markup
+marled
+marlin
+marmot
+maroon
+marque
+marram
+marred
+marrer
+marron
+marrow
+marses
+marshy
+marted
+marten
+martin
+martyr
+marvel
+masala
+mascon
+mascot
+masers
+mashed
+masher
+mashes
+mashie
+masjid
+masked
+maskeg
+masker
+masons
+masque
+massif
+masted
+master
+mastic
+mastix
+maters
+mateys
+matier
+mating
+matins
+matres
+matrix
+matron
+matsah
+matted
+matter
+mattes
+mattin
+mature
+matzah
+matzas
+matzoh
+matzos
+matzot
+mauger
+maugre
+mauled
+mauler
+maumet
+maunds
+maundy
+mauves
+mavens
+mavies
+mavins
+mawing
+maxima
+maxims
+maxing
+maxixe
+maybes
+mayday
+mayest
+mayfly
+mayhap
+mayhem
+maying
+mayors
+maypop
+mayvin
+mazard
+mazers
+mazier
+mazily
+mazing
+mazuma
+mbiras
+meadow
+meager
+meagre
+mealie
+meaner
+meanie
+meanly
+measle
+measly
+meatal
+meated
+meatus
+meccas
+medaka
+medals
+meddle
+medfly
+mediad
+mediae
+medial
+median
+medias
+medick
+medico
+medics
+medina
+medium
+medius
+medlar
+medley
+medusa
+meeker
+meekly
+meeter
+meetly
+megara
+megilp
+megohm
+megrim
+mehndi
+meikle
+meinie
+melded
+melder
+melees
+melena
+melled
+mellow
+melody
+meloid
+melons
+melted
+melter
+melton
+member
+memoir
+memory
+menace
+menads
+menage
+mended
+mender
+menhir
+menial
+meninx
+mensae
+mensal
+mensas
+mensch
+mensed
+menses
+mental
+mentee
+mentor
+mentum
+menudo
+meoued
+meowed
+mercer
+merces
+merdes
+merely
+merest
+merged
+mergee
+merger
+merges
+merino
+merits
+merles
+merlin
+merlon
+merlot
+merman
+mermen
+mescal
+meshed
+meshes
+mesial
+mesian
+mesnes
+mesons
+messan
+messed
+messes
+mestee
+metage
+metals
+metate
+meteor
+metepa
+meters
+method
+methyl
+metier
+meting
+metols
+metope
+metred
+metres
+metric
+metros
+mettle
+metump
+mewing
+mewled
+mewler
+mezcal
+mezuza
+mezzos
+miaous
+miaows
+miasma
+miasms
+miauls
+micell
+miched
+miches
+mickey
+mickle
+micron
+micros
+midair
+midcap
+midday
+midden
+middle
+midges
+midget
+midgut
+midleg
+midrib
+midsts
+midway
+miffed
+miggle
+mights
+mighty
+mignon
+mihrab
+mikado
+miking
+mikron
+mikvah
+mikveh
+mikvos
+mikvot
+miladi
+milady
+milage
+milded
+milden
+milder
+mildew
+mildly
+milers
+milieu
+milium
+milked
+milker
+milled
+miller
+milles
+millet
+milneb
+milord
+milpas
+milted
+milter
+mimbar
+mimeos
+mimers
+mimics
+miming
+mimosa
+minced
+minder
+miners
+mingle
+minify
+minima
+minims
+mining
+minion
+minish
+minium
+minkes
+minnow
+minors
+minted
+minter
+minuet
+minute
+minxes
+minyan
+mioses
+miosis
+miotic
+mirage
+mirier
+miring
+mirins
+mirker
+mirror
+mirths
+mirzas
+misact
+misadd
+misaim
+misate
+miscue
+miscut
+misdid
+miseat
+misers
+misery
+misfed
+misfit
+mishap
+miskal
+mislay
+misled
+mislie
+mislit
+mismet
+mispen
+missal
+missay
+missed
+missel
+misses
+misset
+missis
+missus
+misted
+mister
+misuse
+miters
+mither
+mitier
+mitral
+mitred
+mitres
+mitten
+mixers
+mixing
+mixups
+mizens
+mizuna
+mizzen
+mizzle
+mizzly
+moaned
+moaner
+moated
+mobbed
+mobber
+mobcap
+mobile
+mobled
+mochas
+mocked
+mocker
+mockup
+modals
+models
+modems
+modern
+modest
+modica
+modify
+modish
+module
+moduli
+modulo
+mogged
+moggie
+moghul
+moguls
+mohair
+mohawk
+mohels
+mohurs
+moiety
+moiled
+moiler
+moirai
+moires
+mojoes
+molars
+molded
+molder
+molies
+moline
+mollah
+mollie
+moloch
+molted
+molten
+molter
+moment
+mommas
+momser
+momzer
+monads
+mondes
+mondos
+moneys
+monger
+mongoe
+mongol
+mongos
+mongst
+monied
+monies
+monish
+monism
+monist
+monkey
+monody
+montes
+months
+mooing
+moolah
+moolas
+mooley
+mooned
+mooner
+moored
+mooted
+mooter
+mopeds
+mopers
+mopery
+mopier
+moping
+mopish
+mopoke
+mopped
+mopper
+moppet
+morale
+morals
+morays
+morbid
+moreen
+morels
+morgan
+morgen
+morgue
+morion
+morose
+morpho
+morphs
+morris
+morros
+morrow
+morsel
+mortal
+mortar
+morula
+mosaic
+moseys
+moshav
+moshed
+mosher
+moshes
+mosque
+mossed
+mosser
+mosses
+mostly
+motels
+motets
+mother
+motifs
+motile
+motion
+motive
+motley
+motmot
+motors
+mottes
+mottle
+mottos
+moujik
+moulds
+mouldy
+moulin
+moults
+mounds
+mounts
+mourns
+moused
+mouser
+mouses
+mousey
+mousse
+mouths
+mouthy
+mouton
+movers
+movies
+moving
+mowers
+mowing
+moxies
+muches
+muchly
+mucins
+mucked
+mucker
+muckle
+mucluc
+mucoid
+mucors
+mucosa
+mucose
+mucous
+mudbug
+mudcap
+mudcat
+mudded
+mudder
+muddle
+muddly
+mudhen
+mudras
+muesli
+muffed
+muffin
+muffle
+muftis
+mugful
+muggar
+mugged
+muggee
+mugger
+muggur
+mughal
+mujiks
+mukluk
+muktuk
+mulcts
+muleta
+muleys
+muling
+mulish
+mullah
+mullas
+mulled
+mullen
+muller
+mullet
+mulley
+mumble
+mumbly
+mummed
+mummer
+mumped
+mumper
+mungos
+muntin
+muonic
+murals
+murder
+murein
+murids
+murine
+muring
+murker
+murkly
+murmur
+murphy
+murras
+murres
+murrey
+murrha
+muscae
+muscat
+muscid
+muscle
+muscly
+musers
+museum
+mushed
+musher
+mushes
+musick
+musics
+musing
+musjid
+muskeg
+musket
+muskie
+muskit
+muskox
+muslin
+mussed
+mussel
+musses
+musted
+mustee
+muster
+musths
+mutant
+mutase
+mutate
+mutely
+mutest
+mutine
+muting
+mutiny
+mutism
+mutons
+mutter
+mutton
+mutual
+mutuel
+mutule
+muumuu
+muzhik
+muzjik
+muzzle
+myases
+myasis
+mycele
+myelin
+mylars
+mynahs
+myomas
+myopes
+myopia
+myopic
+myoses
+myosin
+myosis
+myotic
+myriad
+myrica
+myrrhs
+myrtle
+myself
+mysids
+mysost
+mystic
+mythic
+mythoi
+mythos
+myxoid
+myxoma
+nabbed
+nabber
+nabobs
+nachas
+naches
+nachos
+nacred
+nacres
+nadirs
+naevus
+naffed
+nagana
+nagged
+nagger
+naiads
+nailed
+nailer
+nairas
+nairus
+naiver
+naives
+nakfas
+naleds
+namely
+namers
+naming
+nances
+nandin
+nanism
+nankin
+nannie
+napalm
+napery
+napkin
+nappas
+napped
+napper
+nappes
+nappie
+narcos
+narial
+narine
+narked
+narrow
+narwal
+nasals
+nasial
+nasion
+nastic
+natant
+nation
+native
+natron
+natter
+nature
+naught
+nausea
+nautch
+navaid
+navars
+navels
+navies
+nawabs
+naysay
+nazify
+nearby
+neared
+nearer
+nearly
+neaten
+neater
+neatly
+nebula
+nebule
+nebuly
+necked
+necker
+nectar
+needed
+needer
+needle
+negate
+neighs
+nekton
+nellie
+nelson
+neocon
+neoned
+nepeta
+nephew
+nereid
+nereis
+neroli
+nerols
+nerved
+nerves
+nesses
+nested
+nester
+nestle
+nestor
+nether
+netops
+netted
+netter
+nettle
+nettly
+neumes
+neumic
+neural
+neuron
+neuter
+nevoid
+newbie
+newels
+newest
+newies
+newish
+newsie
+newton
+niacin
+nibbed
+nibble
+nicads
+nicely
+nicest
+nicety
+niched
+niches
+nicked
+nickel
+nicker
+nickle
+nicols
+nidate
+nidget
+nidify
+niding
+nieces
+nielli
+niello
+nieves
+niffer
+niggle
+niggly
+nighed
+nigher
+nights
+nighty
+nihils
+nilgai
+nilgau
+nilled
+nimble
+nimbly
+nimbus
+nimmed
+nimrod
+ninety
+ninjas
+ninons
+ninths
+niobic
+nipped
+nipper
+niseis
+niters
+nitery
+nitons
+nitres
+nitric
+nitrid
+nitril
+nitros
+nitwit
+nixies
+nixing
+nizams
+nobble
+nobler
+nobles
+nobody
+nocent
+nocked
+nodded
+nodder
+noddle
+nodose
+nodous
+nodule
+noesis
+noetic
+nogged
+noggin
+noised
+noises
+nomads
+nomina
+nomism
+nonage
+nonart
+nonces
+noncom
+nonego
+nonets
+nonfan
+nonfat
+nongay
+nonman
+nonmen
+nonpar
+nontax
+nonuse
+nonwar
+nonyls
+noodge
+noodle
+noogie
+nookie
+noosed
+nooser
+nooses
+nopals
+nordic
+norias
+norite
+normal
+normed
+norths
+noshed
+nosher
+noshes
+nosier
+nosily
+nosing
+nostoc
+notary
+notate
+noters
+nother
+notice
+notify
+noting
+notion
+nougat
+nought
+nounal
+nouses
+novels
+novena
+novice
+noways
+nowise
+noyade
+nozzle
+nuance
+nubbin
+nubble
+nubbly
+nubias
+nubile
+nubuck
+nuchae
+nuchal
+nuclei
+nudely
+nudest
+nudged
+nudger
+nudges
+nudies
+nudism
+nudist
+nudity
+nudnik
+nugget
+nuking
+nullah
+nulled
+numbat
+numbed
+number
+numbly
+numina
+nuncio
+nuncle
+nurled
+nursed
+nurser
+nurses
+nutant
+nutate
+nutlet
+nutmeg
+nutria
+nuzzle
+nyalas
+oafish
+oakier
+oakums
+oaring
+oaters
+obeahs
+obelia
+obelus
+obento
+obeyed
+obeyer
+obiism
+object
+objets
+oblast
+oblate
+oblige
+oblong
+oboist
+oboles
+obolus
+obsess
+obtain
+obtect
+obtest
+obtund
+obtuse
+obvert
+occult
+occupy
+occurs
+oceans
+ocelli
+ocelot
+ochers
+ochery
+ochone
+ochrea
+ochred
+ochres
+ocicat
+ockers
+ocreae
+octads
+octane
+octans
+octant
+octave
+octavo
+octets
+octopi
+octroi
+octyls
+ocular
+oculus
+oddest
+oddish
+oddity
+odeons
+odeums
+odious
+odists
+odiums
+odored
+odours
+odyles
+oedema
+oeuvre
+offals
+offcut
+offend
+offers
+office
+offing
+offish
+offkey
+offset
+oftest
+ogdoad
+oghams
+ogival
+ogives
+oglers
+ogling
+ogress
+ogrish
+ogrism
+ohmage
+oidium
+oilcan
+oilcup
+oilers
+oilier
+oilily
+oiling
+oilman
+oilmen
+oilway
+oinked
+okapis
+okayed
+oldest
+oldies
+oldish
+oleate
+olefin
+oleine
+oleins
+oleums
+olingo
+olives
+omasum
+ombers
+ombres
+omegas
+omelet
+omened
+omenta
+onager
+onagri
+onions
+oniony
+onlays
+online
+onload
+onrush
+onsets
+onside
+onuses
+onward
+onyxes
+oocyst
+oocyte
+oodles
+oogamy
+oogeny
+oohing
+oolite
+oolith
+oology
+oolong
+oomiac
+oomiak
+oompah
+oomphs
+oorali
+ootids
+oozier
+oozily
+oozing
+opaque
+opened
+opener
+openly
+operas
+operon
+ophite
+opiate
+opined
+opines
+opioid
+opiums
+oppose
+oppugn
+opsins
+optics
+optima
+optime
+opting
+option
+opuses
+orache
+oracle
+orally
+orange
+orangs
+orangy
+orated
+orates
+orator
+orbier
+orbing
+orbits
+orcein
+orchid
+orchil
+orchis
+orcins
+ordain
+ordeal
+orders
+ordure
+oreads
+oreide
+orfray
+organs
+orgone
+oribis
+oriels
+orient
+origan
+origin
+oriole
+orisha
+orison
+orlons
+orlops
+ormers
+ormolu
+ornate
+ornery
+oroide
+orphan
+orphic
+orpine
+orpins
+orrery
+orrice
+oryxes
+oscine
+oscula
+oscule
+osetra
+osiers
+osmics
+osmium
+osmole
+osmols
+osmose
+osmous
+osmund
+osprey
+ossein
+ossify
+osteal
+ostium
+ostler
+ostomy
+otalgy
+others
+otiose
+otitic
+otitis
+ottars
+ottava
+otters
+ouched
+ouches
+oughts
+ounces
+ouphes
+ourang
+ourari
+ourebi
+ousels
+ousted
+ouster
+outact
+outadd
+outage
+outask
+outate
+outbeg
+outbid
+outbox
+outbuy
+outbye
+outcry
+outdid
+outeat
+outers
+outfit
+outfly
+outfox
+outgas
+outgun
+outhit
+outing
+outjut
+outlaw
+outlay
+outled
+outlet
+outlie
+outman
+output
+outran
+outrig
+outrow
+outrun
+outsat
+outsaw
+outsay
+outsee
+outset
+outsin
+outsit
+outvie
+outwar
+outwit
+ouzels
+ovally
+overdo
+overed
+overly
+ovibos
+ovines
+ovisac
+ovoids
+ovolos
+ovonic
+ovular
+ovules
+owlets
+owlish
+owners
+owning
+oxalic
+oxalis
+oxbows
+oxcart
+oxeyes
+oxford
+oxides
+oxidic
+oximes
+oxlike
+oxlips
+oxtail
+oxters
+oxygen
+oyezes
+oyster
+ozalid
+ozones
+ozonic
+pablum
+pacers
+pachas
+pacier
+pacify
+pacing
+packed
+packer
+packet
+packly
+padauk
+padded
+padder
+paddle
+padles
+padnag
+padouk
+padres
+paeans
+paella
+paeons
+paesan
+pagans
+pagers
+paging
+pagoda
+pagods
+paiked
+painch
+pained
+paints
+painty
+paired
+paisan
+paisas
+pajama
+pakeha
+pakora
+palace
+palais
+palapa
+palate
+paleae
+paleal
+palely
+palest
+palets
+palier
+paling
+palish
+palled
+pallet
+pallia
+pallid
+pallor
+palmar
+palmed
+palmer
+palpal
+palped
+palpus
+palter
+paltry
+pampas
+pamper
+panada
+panama
+pandas
+pander
+pandit
+panels
+panfry
+panful
+pangas
+panged
+pangen
+panics
+panier
+panini
+panino
+panned
+panner
+pannes
+panted
+pantie
+pantos
+pantry
+panzer
+papacy
+papain
+papaws
+papaya
+papers
+papery
+papism
+papist
+pappus
+papula
+papule
+papyri
+parade
+paramo
+parang
+paraph
+parcel
+pardah
+pardee
+pardie
+pardon
+parent
+pareos
+parers
+pareus
+pareve
+parged
+parges
+parget
+pargos
+pariah
+parian
+paries
+paring
+parish
+parity
+parkas
+parked
+parker
+parlay
+parled
+parles
+parley
+parlor
+parody
+parole
+parols
+parous
+parral
+parred
+parrel
+parrot
+parsec
+parsed
+parser
+parses
+parson
+partan
+parted
+partly
+parton
+parura
+parure
+parvis
+parvos
+pascal
+paseos
+pashas
+pashed
+pashes
+pastas
+pasted
+pastel
+paster
+pastes
+pastie
+pastil
+pastis
+pastor
+pastry
+pataca
+patchy
+patens
+patent
+paters
+pathos
+patina
+patine
+patins
+patios
+patois
+patrol
+patron
+patted
+pattee
+patten
+patter
+pattie
+patzer
+paulin
+paunch
+pauper
+pausal
+paused
+pauser
+pauses
+pavane
+pavans
+paveed
+pavers
+paving
+pavins
+pavior
+pavise
+pawers
+pawing
+pawned
+pawnee
+pawner
+pawnor
+pawpaw
+paxwax
+payday
+payees
+payers
+paying
+paynim
+payoff
+payola
+payors
+payout
+pazazz
+peaced
+peaces
+peachy
+peages
+peahen
+peaked
+pealed
+peanut
+pearls
+pearly
+peasen
+peases
+peavey
+pebble
+pebbly
+pecans
+pechan
+peched
+pecked
+pecten
+pectic
+pectin
+pedalo
+pedals
+pedant
+pedate
+peddle
+pedlar
+pedler
+pedros
+peeing
+peeked
+peeled
+peeler
+peened
+peered
+peerie
+pegged
+peined
+peised
+peises
+pekans
+pekins
+pekoes
+pelage
+pelite
+pellet
+pelmet
+pelota
+pelted
+pelter
+peltry
+pelves
+pelvic
+pelvis
+penang
+pencel
+pencil
+pended
+pengos
+penman
+penmen
+pennae
+penned
+penner
+pennon
+pensee
+pensil
+pentad
+pentyl
+penult
+penury
+peones
+people
+pepino
+peplos
+peplum
+peplus
+pepped
+pepper
+pepsin
+peptic
+peptid
+perdie
+perdue
+perdus
+pereia
+pereon
+perils
+period
+perish
+periti
+perked
+permed
+permit
+pernio
+pernod
+peroxy
+perron
+perses
+person
+perter
+pertly
+peruke
+peruse
+pesade
+peseta
+pesewa
+pester
+pestle
+pestos
+petals
+petard
+peters
+petite
+petnap
+petrel
+petrol
+petsai
+petted
+petter
+pettle
+pewees
+pewits
+pewter
+phages
+pharos
+phased
+phases
+phasic
+phasis
+phatic
+phenix
+phenol
+phenom
+phenyl
+phials
+phizes
+phlegm
+phloem
+phobia
+phobic
+phoebe
+phonal
+phoned
+phones
+phoney
+phonic
+phonon
+phonos
+phooey
+photic
+photog
+photon
+photos
+phrase
+phreak
+phylae
+phylar
+phylic
+phyllo
+phylon
+phylum
+physed
+physes
+physic
+physis
+phytin
+phytol
+phyton
+piaffe
+pianic
+pianos
+piazza
+piazze
+pibals
+picara
+picaro
+pickax
+picked
+picker
+picket
+pickle
+pickup
+picnic
+picots
+picric
+piculs
+piddle
+piddly
+pidgin
+pieced
+piecer
+pieces
+pieing
+pierce
+pietas
+piffle
+pigeon
+pigged
+piggie
+piggin
+piglet
+pignus
+pignut
+pigout
+pigpen
+pigsty
+pikake
+pikers
+piking
+pilaff
+pilafs
+pilaus
+pilaws
+pileum
+pileup
+pileus
+pilfer
+piling
+pillar
+pilled
+pillow
+pilose
+pilots
+pilous
+pilule
+pimped
+pimple
+pimply
+pinang
+pinata
+pincer
+pinder
+pineal
+pinene
+pinery
+pineta
+pinged
+pinger
+pingos
+pinier
+pining
+pinion
+pinite
+pinked
+pinken
+pinker
+pinkey
+pinkie
+pinkly
+pinkos
+pinnae
+pinnal
+pinnas
+pinned
+pinner
+pinole
+pinons
+pinots
+pintas
+pintle
+pintos
+pinups
+pinyin
+pinyon
+piolet
+pionic
+pipage
+pipals
+pipers
+pipets
+pipier
+piping
+pipits
+pipkin
+pipped
+pippin
+piqued
+piques
+piquet
+piracy
+pirana
+pirate
+piraya
+pirogi
+piscos
+pistil
+pistol
+piston
+pistou
+pitaya
+pitchy
+pithed
+pitied
+pitier
+pities
+pitman
+pitmen
+pitons
+pitsaw
+pittas
+pitted
+pivots
+pixels
+pixies
+pizazz
+pizzas
+pizzaz
+pizzle
+placed
+placer
+places
+placet
+placid
+placks
+plagal
+plages
+plague
+plaguy
+plaice
+plaids
+plains
+plaint
+plaits
+planar
+planch
+planed
+planer
+planes
+planet
+planks
+plants
+plaque
+plashy
+plasma
+plasms
+platan
+plated
+platen
+plater
+plates
+platys
+playas
+played
+player
+plazas
+pleach
+pleads
+please
+pleats
+plebes
+pledge
+pleiad
+plench
+plenty
+plenum
+pleons
+pleura
+plexal
+plexes
+plexor
+plexus
+pliant
+plicae
+plical
+pliers
+plight
+plinks
+plinth
+plisky
+plisse
+ploidy
+plonks
+plotty
+plough
+plover
+plowed
+plower
+ployed
+plucks
+plucky
+plumbs
+plumed
+plumes
+plummy
+plumps
+plunge
+plunks
+plunky
+plural
+pluses
+plushy
+plutei
+pluton
+plyers
+plying
+pneuma
+poachy
+poboys
+pocked
+pocket
+podded
+podite
+podium
+podsol
+podzol
+poetic
+poetry
+pogeys
+pogies
+pogrom
+poilus
+poinds
+pointe
+points
+pointy
+poised
+poiser
+poises
+poisha
+poison
+pokers
+pokeys
+pokier
+pokies
+pokily
+poking
+polars
+polder
+poleax
+poleis
+polers
+poleyn
+police
+policy
+polies
+poling
+polios
+polish
+polite
+polity
+polkas
+polled
+pollee
+pollen
+poller
+pollex
+polyol
+polypi
+polyps
+pomace
+pomade
+pomelo
+pommee
+pommel
+pommie
+pompom
+pompon
+ponced
+ponces
+poncho
+ponded
+ponder
+ponent
+ponged
+pongee
+pongid
+ponied
+ponies
+pontes
+pontil
+ponton
+popery
+popgun
+popish
+poplar
+poplin
+poppas
+popped
+popper
+poppet
+popple
+popsie
+poring
+porism
+porked
+porker
+pornos
+porose
+porous
+portal
+ported
+porter
+portly
+posada
+posers
+poseur
+posher
+poshly
+posies
+posing
+posits
+posole
+posses
+posset
+possum
+postal
+posted
+poster
+postie
+postin
+postop
+potage
+potash
+potato
+potboy
+poteen
+potent
+potful
+pother
+pothos
+potion
+potman
+potmen
+potpie
+potsie
+potted
+potter
+pottle
+pottos
+potzer
+pouchy
+poufed
+pouffe
+pouffs
+pouffy
+poults
+pounce
+pounds
+poured
+pourer
+pouted
+pouter
+powder
+powers
+powter
+powwow
+poxier
+poxing
+poyous
+pozole
+praams
+prahus
+praise
+prajna
+prance
+prangs
+pranks
+prases
+prated
+prater
+prates
+prawns
+praxes
+praxis
+prayed
+prayer
+preach
+preact
+preamp
+prearm
+prebid
+prebuy
+precis
+precut
+predry
+preens
+prefab
+prefer
+prefix
+prelaw
+prelim
+preman
+premed
+premen
+premie
+premix
+preops
+prepay
+preppy
+preset
+presto
+prests
+pretax
+pretor
+pretty
+prevue
+prewar
+prexes
+preyed
+preyer
+prezes
+priapi
+priced
+pricer
+prices
+pricey
+prided
+prides
+priers
+priest
+prills
+primal
+primas
+primed
+primer
+primes
+primly
+primos
+primps
+primus
+prince
+prinks
+prints
+prions
+priors
+priory
+prised
+prises
+prisms
+prison
+prissy
+privet
+prized
+prizer
+prizes
+probed
+prober
+probes
+probit
+proems
+profit
+progun
+projet
+prolan
+proleg
+proles
+prolix
+prolog
+promos
+prompt
+prongs
+pronto
+proofs
+propel
+proper
+propyl
+prosed
+proser
+proses
+prosit
+prosos
+protea
+protei
+proton
+protyl
+proved
+proven
+prover
+proves
+prowar
+prower
+prowls
+prudes
+pruned
+pruner
+prunes
+prunus
+prutah
+prutot
+pryers
+prying
+psalms
+pseudo
+pseuds
+pshaws
+psocid
+psyche
+psycho
+psychs
+psylla
+psyops
+psywar
+pterin
+ptisan
+ptooey
+ptoses
+ptosis
+ptotic
+public
+pucker
+puddle
+puddly
+pueblo
+puffed
+puffer
+puffin
+pugged
+puggry
+pugree
+puisne
+pujahs
+puking
+pulers
+puling
+pulled
+puller
+pullet
+pulley
+pullup
+pulpal
+pulped
+pulper
+pulpit
+pulque
+pulsar
+pulsed
+pulser
+pulses
+pumelo
+pumice
+pummel
+pumped
+pumper
+punchy
+pundit
+pungle
+punier
+punily
+punish
+punjis
+punkah
+punkas
+punker
+punkey
+punkie
+punkin
+punned
+punner
+punnet
+punted
+punter
+puntos
+pupate
+pupils
+pupped
+puppet
+purana
+purdah
+purdas
+pureed
+purees
+purely
+purest
+purfle
+purged
+purger
+purges
+purify
+purine
+purins
+purism
+purist
+purity
+purled
+purlin
+purple
+purply
+purred
+pursed
+purser
+purses
+pursue
+purvey
+pushed
+pushes
+pushup
+pusley
+putlog
+putoff
+putons
+putout
+putrid
+putsch
+putted
+puttee
+putter
+puttie
+putzed
+putzes
+puzzle
+pyemia
+pyemic
+pyjama
+pyknic
+pylons
+pylori
+pyoses
+pyosis
+pyrans
+pyrene
+pyrite
+pyrola
+pyrone
+pyrope
+pyrrol
+python
+pyuria
+pyxies
+qabala
+qanats
+qindar
+qintar
+qiviut
+quacks
+quacky
+quaere
+quaffs
+quagga
+quaggy
+quahog
+quaich
+quaigh
+quails
+quaint
+quaked
+quaker
+quakes
+qualia
+qualms
+qualmy
+quango
+quanta
+quants
+quarks
+quarry
+quarte
+quarto
+quarts
+quartz
+quasar
+quatre
+quaver
+qubits
+qubyte
+queans
+queasy
+queazy
+queens
+queers
+quelea
+quells
+quench
+querns
+quests
+queued
+queuer
+queues
+quezal
+quiche
+quicks
+quiets
+quiffs
+quills
+quilts
+quince
+quinic
+quinin
+quinoa
+quinol
+quinsy
+quinta
+quinte
+quints
+quippu
+quippy
+quipus
+quired
+quires
+quirks
+quirky
+quirts
+quitch
+quiver
+quohog
+quoins
+quoits
+quokka
+quolls
+quorum
+quotas
+quoted
+quoter
+quotes
+quotha
+qurush
+qwerty
+rabato
+rabats
+rabbet
+rabbin
+rabbis
+rabbit
+rabble
+rabies
+raceme
+racers
+rachet
+rachis
+racier
+racily
+racing
+racked
+racker
+racket
+rackle
+racons
+racoon
+radars
+radded
+raddle
+radial
+radian
+radios
+radish
+radium
+radius
+radome
+radons
+radula
+raffia
+raffle
+rafted
+rafter
+ragbag
+ragees
+ragged
+raggee
+raggle
+raging
+raglan
+ragman
+ragmen
+ragout
+ragtag
+ragtop
+raided
+raider
+railed
+railer
+rained
+raised
+raiser
+raises
+raisin
+raitas
+rajahs
+rakees
+rakers
+raking
+rakish
+rallye
+ralphs
+ramada
+ramate
+rambla
+ramble
+ramees
+ramets
+ramies
+ramify
+ramjet
+rammed
+rammer
+ramona
+ramose
+ramous
+ramped
+ramrod
+ramson
+ramtil
+rances
+rancho
+rancid
+rancor
+randan
+random
+ranees
+ranged
+ranger
+ranges
+ranids
+ranked
+ranker
+rankle
+rankly
+ransom
+ranted
+ranter
+ranula
+rarefy
+rarely
+rarest
+rarify
+raring
+rarity
+rascal
+rasers
+rasher
+rashes
+rashly
+rasing
+rasped
+rasper
+rassle
+raster
+rasure
+ratals
+ratans
+ratany
+ratbag
+ratels
+raters
+rather
+ratify
+ratine
+rating
+ration
+ratios
+ratite
+ratlin
+ratoon
+rattan
+ratted
+ratten
+ratter
+rattle
+rattly
+ratton
+raunch
+ravage
+ravels
+ravens
+ravers
+ravine
+raving
+ravins
+ravish
+rawest
+rawins
+rawish
+raxing
+rayahs
+raying
+rayons
+razeed
+razees
+razers
+razing
+razors
+razzed
+razzes
+reacts
+readds
+reader
+reagin
+realer
+reales
+realia
+really
+realms
+realty
+reamed
+reamer
+reaped
+reaper
+reared
+rearer
+rearms
+reason
+reatas
+reaved
+reaver
+reaves
+reavow
+rebait
+rebars
+rebate
+rebato
+rebbes
+rebeck
+rebecs
+rebels
+rebids
+rebill
+rebind
+rebody
+reboil
+rebook
+reboot
+rebops
+rebore
+reborn
+rebozo
+rebred
+rebuff
+rebuke
+rebury
+rebuts
+rebuys
+recall
+recane
+recant
+recaps
+recast
+recces
+recede
+recent
+recept
+recess
+rechew
+recipe
+recite
+recits
+recked
+reckon
+reclad
+recoal
+recoat
+recode
+recoil
+recoin
+recomb
+recons
+recook
+recopy
+record
+recork
+recoup
+rectal
+rector
+rectos
+recurs
+recuse
+recuts
+redact
+redans
+redate
+redbay
+redbud
+redbug
+redcap
+redded
+redden
+redder
+reddle
+redear
+redeem
+redefy
+redeny
+redeye
+redfin
+rediae
+redial
+redias
+reding
+redips
+redipt
+redleg
+redock
+redoes
+redone
+redons
+redout
+redowa
+redraw
+redrew
+redtop
+redubs
+reduce
+redyed
+redyes
+reearn
+reecho
+reechy
+reeded
+reedit
+reefed
+reeled
+reeler
+reemit
+reests
+reeved
+reeves
+reface
+refall
+refect
+refeed
+refeel
+refell
+refels
+refelt
+refers
+reffed
+refile
+refill
+refilm
+refind
+refine
+refire
+refits
+reflag
+reflet
+reflew
+reflex
+reflow
+reflux
+refold
+reform
+refuel
+refuge
+refund
+refuse
+refute
+regain
+regale
+regard
+regave
+regear
+regent
+reggae
+regild
+regilt
+regime
+regina
+region
+regius
+regive
+reglet
+reglow
+reglue
+regnal
+regnum
+regret
+regrew
+regrow
+reguli
+rehabs
+rehang
+rehash
+rehear
+reheat
+reheel
+rehems
+rehire
+rehung
+reigns
+reined
+reinks
+reived
+reiver
+reives
+reject
+rejigs
+rejoin
+rekeys
+reknit
+reknot
+relace
+relaid
+reland
+relate
+relays
+relend
+relent
+relets
+releve
+relics
+relict
+relied
+relief
+relier
+relies
+reline
+relink
+relish
+relist
+relive
+reload
+reloan
+relock
+relook
+reluct
+relume
+remade
+remail
+remain
+remake
+remand
+remans
+remaps
+remark
+remate
+remedy
+remeet
+remelt
+remend
+remind
+remint
+remise
+remiss
+remits
+remixt
+remold
+remora
+remote
+remove
+remuda
+renail
+rename
+rended
+render
+renege
+renest
+renews
+renigs
+renins
+rennet
+rennin
+renown
+rental
+rented
+renter
+rentes
+renvoi
+reoils
+reopen
+repack
+repaid
+repair
+repand
+repark
+repass
+repast
+repave
+repays
+repeal
+repeat
+repegs
+repels
+repent
+reperk
+repine
+repins
+replan
+replay
+repled
+replot
+replow
+repoll
+report
+repose
+repots
+repour
+repped
+repros
+repugn
+repump
+repute
+requin
+rerack
+reread
+rerent
+rerigs
+rerise
+reroll
+reroof
+rerose
+reruns
+resaid
+resail
+resale
+resawn
+resaws
+resays
+rescue
+reseal
+reseat
+reseau
+resect
+reseda
+reseed
+reseek
+reseen
+resees
+resell
+resend
+resent
+resets
+resewn
+resews
+reshes
+reship
+reshod
+reshoe
+reshot
+reshow
+reside
+resids
+resift
+resign
+resile
+resins
+resiny
+resist
+resite
+resits
+resize
+resoak
+resods
+resold
+resole
+resorb
+resort
+resown
+resows
+respot
+rested
+rester
+result
+resume
+retack
+retags
+retail
+retain
+retake
+retape
+reteam
+retear
+retell
+retems
+retene
+retest
+retial
+retied
+reties
+retile
+retime
+retina
+retine
+retint
+retire
+retold
+retook
+retool
+retore
+retorn
+retort
+retral
+retrim
+retros
+retted
+retune
+return
+retuse
+retype
+reused
+reuses
+revamp
+reveal
+revels
+reverb
+revere
+revers
+revert
+revery
+revest
+revets
+review
+revile
+revise
+revive
+revoke
+revolt
+revote
+revues
+revved
+rewake
+reward
+rewarm
+rewash
+rewear
+reweds
+reweld
+rewets
+rewind
+rewins
+rewire
+rewoke
+reword
+rewore
+rework
+reworn
+rewove
+rewrap
+rexine
+rezero
+rezone
+rhaphe
+rhebok
+rhemes
+rhesus
+rhetor
+rheums
+rheumy
+rhinal
+rhinos
+rhodic
+rhombi
+rhombs
+rhotic
+rhumba
+rhumbs
+rhuses
+rhymed
+rhymer
+rhymes
+rhythm
+rhyton
+rialto
+riatas
+ribald
+riband
+ribbed
+ribber
+ribbon
+ribier
+riblet
+ribose
+ricers
+richen
+richer
+riches
+richly
+ricing
+ricins
+ricked
+rickey
+ricrac
+rictal
+rictus
+ridded
+ridden
+ridder
+riddle
+rident
+riders
+ridged
+ridgel
+ridges
+ridgil
+riding
+ridley
+riever
+rifely
+rifest
+riffed
+riffle
+rifled
+rifler
+rifles
+riflip
+rifted
+rigged
+rigger
+righto
+rights
+righty
+rigors
+rigour
+riling
+rilled
+rilles
+rillet
+rimers
+rimier
+rimose
+rimous
+rimple
+rinded
+ringed
+ringer
+rinsed
+rinser
+rinses
+riojas
+rioted
+rioter
+ripely
+ripens
+ripest
+riping
+ripoff
+ripost
+ripped
+ripper
+ripple
+ripply
+riprap
+ripsaw
+risers
+rishis
+rising
+risked
+risker
+risque
+ristra
+ritard
+ritter
+ritual
+ritzes
+rivage
+rivals
+rivers
+rivets
+riving
+riyals
+roadeo
+roadie
+roamed
+roamer
+roared
+roarer
+roasts
+robalo
+roband
+robbed
+robber
+robbin
+robing
+robins
+robles
+robots
+robust
+rochet
+rocked
+rocker
+rocket
+rococo
+rodded
+rodent
+rodeos
+rodman
+rodmen
+rogers
+rogued
+rogues
+roiled
+rolfed
+rolfer
+rolled
+roller
+romaji
+romano
+romans
+romeos
+rondel
+rondos
+ronion
+ronnel
+ronyon
+roofed
+roofer
+roofie
+rooked
+rookie
+roomed
+roomer
+roomie
+roosed
+rooser
+rooses
+roosts
+rooted
+rooter
+rootle
+ropers
+ropery
+ropier
+ropily
+roping
+roques
+roquet
+rosary
+roscoe
+rosery
+rosets
+roshis
+rosier
+rosily
+rosing
+rosins
+rosiny
+roster
+rostra
+rotary
+rotate
+rotche
+rotgut
+rotors
+rotund
+rouble
+rouche
+rouens
+rouged
+rouges
+roughs
+roughy
+rounds
+rouped
+roupet
+roused
+rouser
+rouses
+rousts
+routed
+router
+routes
+rouths
+rovers
+roving
+rowans
+rowels
+rowens
+rowers
+rowing
+rowths
+royals
+rozzer
+ruanas
+rubace
+rubati
+rubato
+rubbed
+rubber
+rubble
+rubbly
+rubels
+rubied
+rubier
+rubies
+rubigo
+rubles
+ruboff
+rubout
+rubric
+ruched
+ruches
+rucked
+ruckle
+ruckus
+rudder
+ruddle
+rudely
+rudery
+rudest
+rueful
+ruffed
+ruffes
+ruffle
+ruffly
+rufous
+rugate
+rugged
+rugger
+rugola
+rugosa
+rugose
+rugous
+ruined
+ruiner
+rulers
+rulier
+ruling
+rumaki
+rumbas
+rumble
+rumbly
+rumens
+rumina
+rummer
+rumors
+rumour
+rumple
+rumply
+rumpus
+rundle
+runkle
+runlet
+runnel
+runner
+runoff
+runout
+runway
+rupees
+rupiah
+rurban
+rushed
+rushee
+rusher
+rushes
+rusine
+russet
+rusted
+rustic
+rustle
+rutile
+rutins
+rutted
+ryking
+ryokan
+sabals
+sabbat
+sabbed
+sabers
+sabine
+sabins
+sabirs
+sables
+sabots
+sabras
+sabred
+sabres
+sacbut
+sachem
+sachet
+sacked
+sacker
+sacque
+sacral
+sacred
+sacrum
+sadden
+sadder
+saddhu
+saddle
+sadhes
+sadhus
+safari
+safely
+safest
+safety
+safrol
+sagbut
+sagely
+sagest
+saggar
+sagged
+sagger
+sagier
+sahibs
+saices
+saigas
+sailed
+sailer
+sailor
+saimin
+sained
+saints
+saithe
+saiyid
+sajous
+sakers
+salaam
+salads
+salals
+salami
+salary
+saleps
+salify
+salina
+saline
+saliva
+sallet
+sallow
+salmis
+salmon
+salols
+salons
+saloon
+saloop
+salpae
+salpas
+salpid
+salsas
+salted
+salter
+saltie
+saluki
+salute
+salved
+salver
+salves
+salvia
+salvor
+salvos
+samara
+sambal
+sambar
+sambas
+sambos
+sambur
+samech
+samekh
+sameks
+samiel
+samite
+samlet
+samosa
+sampan
+sample
+samshu
+sancta
+sandal
+sanded
+sander
+sandhi
+sanely
+sanest
+sangar
+sangas
+sanger
+sanghs
+sanies
+saning
+sanity
+sanjak
+sannop
+sannup
+sansar
+sansei
+santir
+santol
+santos
+santur
+sapors
+sapota
+sapote
+sapour
+sapped
+sapper
+sarans
+sarape
+sardar
+sarees
+sarges
+sargos
+sarins
+sarode
+sarods
+sarong
+sarsar
+sarsen
+sartor
+sashay
+sashed
+sashes
+sasins
+sassed
+sasses
+satang
+satara
+satays
+sateen
+sating
+satins
+satiny
+satire
+satori
+satrap
+satyrs
+sauced
+saucer
+sauces
+sauchs
+sauger
+saughs
+saughy
+saults
+saunas
+saurel
+sauted
+sautes
+savage
+savant
+savate
+savers
+savine
+saving
+savins
+savior
+savors
+savory
+savour
+savoys
+sawers
+sawfly
+sawing
+sawlog
+sawney
+sawyer
+saxony
+sayeds
+sayers
+sayest
+sayids
+saying
+sayyid
+scabby
+scalar
+scalds
+scaled
+scaler
+scales
+scalls
+scalps
+scampi
+scamps
+scants
+scanty
+scaped
+scapes
+scarab
+scarce
+scared
+scarer
+scares
+scarey
+scarfs
+scarph
+scarps
+scarry
+scarts
+scathe
+scatts
+scatty
+scaups
+scaurs
+scenas
+scends
+scenes
+scenic
+scents
+schavs
+schema
+scheme
+schism
+schist
+schlep
+schlub
+schmoe
+schmos
+schnoz
+school
+schorl
+schrik
+schrod
+schtik
+schuit
+schuln
+schuls
+schuss
+schwas
+scilla
+scions
+sclaff
+sclera
+scoffs
+scolds
+scolex
+sconce
+scones
+scooch
+scoops
+scoots
+scoped
+scopes
+scorch
+scored
+scorer
+scores
+scoria
+scorns
+scotch
+scoter
+scotia
+scours
+scouse
+scouth
+scouts
+scowed
+scowls
+scrags
+scrams
+scrape
+scraps
+scrawl
+screak
+scream
+screed
+screen
+screes
+screws
+screwy
+scribe
+scried
+scries
+scrimp
+scrims
+scrips
+script
+scrive
+scrods
+scroll
+scroop
+scrota
+scrubs
+scruff
+scrums
+scubas
+scuffs
+sculch
+sculks
+sculls
+sculps
+sculpt
+scurfs
+scurfy
+scurry
+scurvy
+scutch
+scutes
+scutum
+scyphi
+scythe
+seabag
+seabed
+seadog
+sealed
+sealer
+seaman
+seamed
+seamer
+seance
+search
+seared
+searer
+season
+seated
+seater
+seawan
+seaway
+sebums
+secant
+seccos
+secede
+secern
+second
+secpar
+secret
+sector
+secund
+secure
+sedans
+sedate
+seders
+sedges
+sedile
+seduce
+sedums
+seeded
+seeder
+seeing
+seeker
+seeled
+seemed
+seemer
+seemly
+seeped
+seesaw
+seethe
+seggar
+segnos
+segued
+segues
+seiche
+seidel
+seined
+seiner
+seines
+seised
+seiser
+seises
+seisin
+seisms
+seisor
+seitan
+seized
+seizer
+seizes
+seizin
+seizor
+sejant
+selahs
+seldom
+select
+selfed
+selkie
+seller
+selles
+selsyn
+selvas
+selves
+sememe
+semple
+sempre
+senary
+senate
+sendal
+sended
+sender
+sendup
+seneca
+senega
+senhor
+senile
+senior
+seniti
+sennas
+sennet
+sennit
+senora
+senors
+senryu
+sensed
+sensei
+senses
+sensor
+sensum
+sentry
+sepals
+sepias
+sepoys
+sepses
+sepsis
+septal
+septet
+septic
+septum
+sequel
+sequin
+seracs
+serail
+serais
+serape
+seraph
+serdab
+serein
+serene
+serest
+serged
+serger
+serges
+serial
+series
+serifs
+serine
+sering
+serins
+sermon
+serosa
+serous
+serows
+serums
+serval
+served
+server
+serves
+servos
+sesame
+sestet
+setoff
+setons
+setose
+setous
+setout
+settee
+setter
+settle
+setups
+sevens
+severe
+severs
+sewage
+sewans
+sewars
+sewers
+sewing
+shabby
+shacko
+shacks
+shaded
+shader
+shades
+shadow
+shaduf
+shafts
+shaggy
+shaird
+shairn
+shaken
+shaker
+shakes
+shakos
+shaled
+shales
+shaley
+shalom
+shaman
+shamas
+shamed
+shames
+shammy
+shamos
+shamoy
+shamus
+shandy
+shanks
+shanny
+shanti
+shanty
+shaped
+shapen
+shaper
+shapes
+shards
+shared
+sharer
+shares
+sharia
+sharif
+sharks
+sharns
+sharny
+sharps
+sharpy
+shaugh
+shauls
+shaved
+shaven
+shaver
+shaves
+shavie
+shawed
+shawls
+shawms
+shazam
+sheafs
+sheals
+shears
+sheath
+sheave
+sheens
+sheeny
+sheers
+sheesh
+sheets
+sheeve
+sheikh
+sheiks
+sheila
+shekel
+shells
+shelly
+shelta
+shelty
+shelve
+shelvy
+shends
+sheols
+sheqel
+sherds
+sherif
+sherpa
+sherry
+sheuch
+sheugh
+shewed
+shewer
+shibah
+shield
+shiels
+shiers
+shiest
+shifts
+shifty
+shikar
+shiksa
+shikse
+shills
+shimmy
+shindy
+shined
+shiner
+shines
+shinny
+shires
+shirks
+shirrs
+shirts
+shirty
+shists
+shivah
+shivas
+shiver
+shives
+shlepp
+shleps
+shlock
+shlubs
+shlump
+shmear
+shmoes
+shmuck
+shnaps
+shnook
+shoals
+shoaly
+shoats
+shocks
+shoddy
+shoers
+shofar
+shogis
+shogun
+shojis
+sholom
+shooed
+shooks
+shools
+shoots
+shoppe
+shoran
+shored
+shores
+shorls
+shorts
+shorty
+shotes
+shotts
+should
+shouts
+shoved
+shovel
+shover
+shoves
+showed
+shower
+shoyus
+shrank
+shreds
+shrewd
+shrews
+shriek
+shrift
+shrike
+shrill
+shrimp
+shrine
+shrink
+shrive
+shroff
+shroud
+shrove
+shrubs
+shrugs
+shrunk
+shtetl
+shtick
+shtiks
+shucks
+shunts
+shuted
+shutes
+shyers
+shyest
+shying
+sialic
+sialid
+sibyls
+siccan
+sicced
+sicked
+sickee
+sicken
+sicker
+sickie
+sickle
+sickly
+sickos
+siddur
+siding
+sidled
+sidler
+sidles
+sieged
+sieges
+sienna
+sierra
+siesta
+sieurs
+sieved
+sieves
+sifaka
+sifted
+sifter
+sighed
+sigher
+sights
+sigils
+sigloi
+siglos
+siglum
+sigmas
+signal
+signed
+signee
+signer
+signet
+signor
+silage
+silane
+sileni
+silent
+silica
+silked
+silken
+silkie
+siller
+siloed
+silted
+silvae
+silvan
+silvas
+silver
+silvex
+simars
+simian
+simile
+simlin
+simmer
+simnel
+simony
+simoom
+simoon
+simper
+simple
+simply
+sinews
+sinewy
+sinful
+singed
+singer
+singes
+single
+singly
+sinker
+sinned
+sinner
+sinter
+siphon
+siping
+sipped
+sipper
+sippet
+sirdar
+sirees
+sirens
+siring
+sirrah
+sirras
+sirree
+sirups
+sirupy
+sisals
+siskin
+sisses
+sister
+sistra
+sitars
+sitcom
+siting
+sitten
+sitter
+situps
+sivers
+sixmos
+sixtes
+sixths
+sizars
+sizers
+sizier
+sizing
+sizzle
+skalds
+skated
+skater
+skates
+skatol
+skeane
+skeans
+skeens
+skeets
+skeigh
+skeins
+skells
+skelms
+skelps
+skenes
+skerry
+sketch
+skewed
+skewer
+skibob
+skiddy
+skidoo
+skiers
+skiffs
+skiing
+skills
+skimos
+skimps
+skimpy
+skinks
+skinny
+skirls
+skirrs
+skirts
+skited
+skites
+skived
+skiver
+skives
+skivvy
+sklent
+skoals
+skorts
+skulks
+skulls
+skunks
+skunky
+skybox
+skycap
+skying
+skylit
+skyman
+skymen
+skyway
+slacks
+slaggy
+slaked
+slaker
+slakes
+slalom
+slangs
+slangy
+slants
+slanty
+slatch
+slated
+slater
+slates
+slatey
+slaved
+slaver
+slaves
+slavey
+slayed
+slayer
+sleave
+sleaze
+sleazo
+sleazy
+sledge
+sleeks
+sleeky
+sleeps
+sleepy
+sleets
+sleety
+sleeve
+sleigh
+sleuth
+slewed
+sliced
+slicer
+slices
+slicks
+slider
+slides
+sliest
+slieve
+slight
+slimed
+slimes
+slimly
+slimsy
+slings
+slinks
+slinky
+sliped
+slipes
+slippy
+slipup
+slitty
+sliver
+slobby
+slogan
+sloids
+slojds
+sloops
+sloped
+sloper
+slopes
+sloppy
+sloshy
+sloths
+slouch
+slough
+sloven
+slowed
+slower
+slowly
+sloyds
+sludge
+sludgy
+sluffs
+sluice
+sluicy
+sluing
+slummy
+slumps
+slurbs
+slurps
+slurry
+slushy
+slyest
+slypes
+smacks
+smalls
+smalti
+smalto
+smalts
+smarms
+smarmy
+smarts
+smarty
+smazes
+smears
+smeary
+smeeks
+smegma
+smells
+smelly
+smelts
+smerks
+smidge
+smilax
+smiled
+smiler
+smiles
+smiley
+smirch
+smirks
+smirky
+smiter
+smites
+smiths
+smithy
+smocks
+smoggy
+smoked
+smoker
+smokes
+smokey
+smolts
+smooch
+smoosh
+smooth
+smudge
+smudgy
+smugly
+smutch
+snacks
+snafus
+snaggy
+snails
+snaked
+snakes
+snakey
+snappy
+snared
+snarer
+snares
+snarfs
+snarks
+snarky
+snarls
+snarly
+snatch
+snathe
+snaths
+snawed
+snazzy
+sneaks
+sneaky
+sneaps
+snecks
+sneers
+sneery
+sneesh
+sneeze
+sneezy
+snells
+snicks
+snider
+sniffs
+sniffy
+sniped
+sniper
+snipes
+snippy
+snitch
+snivel
+snobby
+snoods
+snooks
+snools
+snoops
+snoopy
+snoots
+snooty
+snooze
+snoozy
+snored
+snorer
+snores
+snorts
+snotty
+snouts
+snouty
+snowed
+snubby
+snuffs
+snuffy
+snugly
+soaked
+soaker
+soaped
+soaper
+soared
+soarer
+soaves
+sobbed
+sobber
+sobeit
+sobers
+sobful
+socage
+soccer
+social
+socked
+socket
+socles
+socman
+socmen
+sodded
+sodden
+sodium
+soever
+sofars
+soffit
+softas
+soften
+softer
+softie
+softly
+sogged
+soigne
+soiled
+soiree
+sokols
+solace
+soland
+solano
+solans
+solate
+soldan
+solder
+solely
+solemn
+soleus
+solgel
+solidi
+solids
+soling
+solion
+soloed
+solons
+solums
+solute
+solved
+solver
+solves
+somans
+somata
+somber
+sombre
+somite
+somoni
+sonant
+sonars
+sonata
+sonder
+sondes
+sonics
+sonnet
+sonsie
+sooner
+sooted
+soothe
+sooths
+sopite
+sopors
+sopped
+sorbed
+sorbet
+sorbic
+sordid
+sordor
+sorels
+sorely
+sorest
+sorgho
+sorgos
+soring
+sorned
+sorner
+sorrel
+sorrow
+sorted
+sorter
+sortie
+sotols
+sotted
+souari
+soucar
+soudan
+soughs
+sought
+souled
+sounds
+souped
+source
+soured
+sourer
+sourly
+soused
+souses
+souter
+souths
+soviet
+sovran
+sowans
+sowars
+sowcar
+sowens
+sowers
+sowing
+sozine
+sozins
+spaced
+spacer
+spaces
+spacey
+spaded
+spader
+spades
+spadix
+spahee
+spahis
+spails
+spaits
+spales
+spalls
+spanks
+spared
+sparer
+spares
+sparge
+sparid
+sparks
+sparky
+sparry
+sparse
+spasms
+spates
+spathe
+spavie
+spavin
+spawns
+spayed
+speaks
+speans
+spears
+specie
+specks
+speech
+speedo
+speeds
+speedy
+speels
+speers
+speils
+speirs
+speise
+speiss
+spells
+spelts
+speltz
+spence
+spends
+spendy
+spense
+spewed
+spewer
+sphene
+sphere
+sphery
+sphinx
+sphynx
+spicae
+spicas
+spiced
+spicer
+spices
+spicey
+spicks
+spider
+spiels
+spiers
+spiffs
+spiffy
+spigot
+spiked
+spiker
+spikes
+spikey
+spiled
+spiles
+spills
+spilth
+spinal
+spined
+spinel
+spines
+spinet
+spinny
+spinor
+spinto
+spiral
+spirea
+spired
+spirem
+spires
+spirit
+spirts
+spital
+spited
+spites
+spivvy
+splake
+splash
+splats
+splays
+spleen
+splent
+splice
+spline
+splint
+splits
+splore
+splosh
+spodes
+spoils
+spoilt
+spoked
+spoken
+spokes
+sponge
+spongy
+spoofs
+spoofy
+spooks
+spooky
+spools
+spoons
+spoony
+spoors
+sporal
+spored
+spores
+sports
+sporty
+spotty
+spouse
+spouts
+sprags
+sprain
+sprang
+sprats
+sprawl
+sprays
+spread
+sprees
+sprent
+sprier
+sprigs
+spring
+sprint
+sprite
+sprits
+spritz
+sprout
+spruce
+sprucy
+sprues
+sprugs
+sprung
+spryer
+spryly
+spuing
+spumed
+spumes
+spurns
+spurry
+spying
+squabs
+squads
+squall
+squama
+square
+squark
+squash
+squats
+squawk
+squaws
+squeak
+squeal
+squegs
+squibs
+squids
+squill
+squint
+squire
+squirm
+squirt
+squish
+squush
+sradha
+stable
+stably
+stacks
+stacte
+stades
+stadia
+staffs
+staged
+stager
+stages
+stagey
+staggy
+staigs
+stains
+stairs
+staked
+stakes
+stalag
+staled
+staler
+stales
+stalks
+stalky
+stalls
+stamen
+stamps
+stance
+stanch
+stands
+staned
+stanes
+stangs
+stanks
+stanol
+stanza
+stapes
+staphs
+staple
+starch
+stared
+starer
+stares
+starry
+starts
+starve
+stases
+stasis
+statal
+stated
+stater
+states
+static
+statin
+stator
+statue
+status
+staved
+staves
+stayed
+stayer
+steads
+steady
+steaks
+steals
+steams
+steamy
+steeds
+steeks
+steels
+steely
+steeps
+steers
+steeve
+steins
+stelae
+stelai
+stelar
+steles
+stelic
+stella
+stemma
+stemmy
+stench
+stenos
+stents
+steppe
+stereo
+steres
+steric
+sterna
+sterns
+sterol
+stewed
+stichs
+sticks
+sticky
+stiffs
+stifle
+stigma
+stiles
+stills
+stilly
+stilts
+stimes
+stingo
+stings
+stints
+stiped
+stipel
+stipes
+stirks
+stirps
+stitch
+stithy
+stiver
+stoats
+stocks
+stocky
+stodge
+stodgy
+stogey
+stogie
+stoics
+stoked
+stoker
+stokes
+stoled
+stolen
+stoles
+stolid
+stolon
+stomal
+stomas
+stomps
+stoned
+stoner
+stones
+stoney
+stooge
+stooks
+stoops
+stoped
+stoper
+stopes
+storax
+stored
+storer
+stores
+storey
+storks
+storms
+stormy
+stotin
+stotts
+stound
+stoups
+stoure
+stours
+stoury
+stouts
+stover
+stoves
+stowed
+stowps
+strafe
+strain
+strait
+strake
+strand
+strang
+straps
+strass
+strata
+strath
+strati
+straws
+strawy
+strays
+streak
+stream
+streek
+streel
+street
+streps
+stress
+strewn
+strews
+striae
+strick
+strict
+stride
+strife
+strike
+string
+stripe
+strips
+stript
+stripy
+strive
+strobe
+strode
+stroke
+stroll
+stroma
+strong
+strook
+strops
+stroud
+strove
+strown
+strows
+stroys
+struck
+struma
+strums
+strung
+strunt
+struts
+stubby
+stucco
+studio
+studly
+stuffs
+stuffy
+stulls
+stumps
+stumpy
+stunts
+stupas
+stupes
+stupor
+sturdy
+sturts
+stying
+stylar
+styled
+styler
+styles
+stylet
+stylus
+stymie
+styrax
+suable
+suably
+suaver
+subahs
+subbed
+subdeb
+subdue
+subers
+subfix
+subgum
+subito
+sublet
+sublot
+submit
+subnet
+suborn
+subpar
+subsea
+subset
+subtle
+subtly
+suburb
+subway
+succah
+succor
+sucres
+sudary
+sudden
+sudors
+sudsed
+sudser
+sudses
+sueded
+suedes
+suffer
+suffix
+sugars
+sugary
+sughed
+suints
+suited
+suiter
+suites
+suitor
+sukkah
+sukkot
+sulcal
+sulcus
+suldan
+sulfas
+sulfid
+sulfur
+sulked
+sulker
+sullen
+sulpha
+sultan
+sultry
+sumach
+sumacs
+summae
+summas
+summed
+summer
+summit
+summon
+sunbow
+sundae
+sunder
+sundew
+sundog
+sundry
+sunken
+sunket
+sunlit
+sunnah
+sunnas
+sunned
+sunray
+sunset
+suntan
+sunups
+superb
+supers
+supine
+supped
+supper
+supple
+supply
+surahs
+surely
+surest
+surety
+surfed
+surfer
+surged
+surger
+surges
+surimi
+surras
+surrey
+surtax
+survey
+sushis
+suslik
+sussed
+susses
+sutler
+sutras
+suttas
+suttee
+suture
+svaraj
+svelte
+swabby
+swaged
+swager
+swages
+swails
+swains
+swales
+swamis
+swamps
+swampy
+swanks
+swanky
+swanny
+swaraj
+swards
+swarfs
+swarms
+swarth
+swarty
+swatch
+swathe
+swaths
+swayed
+swayer
+swears
+sweats
+sweaty
+swedes
+sweeny
+sweeps
+sweepy
+sweets
+swells
+swerve
+sweven
+swifts
+swills
+swimmy
+swinge
+swings
+swingy
+swinks
+swiped
+swipes
+swiple
+swirls
+swirly
+swishy
+switch
+swithe
+swived
+swivel
+swives
+swivet
+swoons
+swoony
+swoops
+swoopy
+swoosh
+swords
+swound
+swouns
+syboes
+sycees
+sylphs
+sylphy
+sylvae
+sylvan
+sylvas
+sylvin
+symbol
+synced
+synchs
+syncom
+syndet
+syndic
+syngas
+synods
+syntax
+synths
+synura
+sypher
+syphon
+syrens
+syrinx
+syrups
+syrupy
+sysops
+system
+syzygy
+tabard
+tabbed
+tabbis
+tabers
+tablas
+tabled
+tables
+tablet
+taboos
+tabors
+tabour
+tabued
+tabuli
+tabuns
+taches
+tacked
+tacker
+tacket
+tackey
+tackle
+tactic
+taenia
+taffia
+tafias
+tagged
+tagger
+tagrag
+tahini
+tahsil
+taigas
+tailed
+tailer
+taille
+tailor
+taints
+taipan
+takahe
+takers
+takeup
+taking
+takins
+talars
+talced
+talcky
+talcum
+talent
+talers
+talion
+talked
+talker
+talkie
+taller
+tallis
+tallit
+tallol
+tallow
+talons
+taluka
+taluks
+tamale
+tamals
+tamari
+tambac
+tambak
+tambur
+tamein
+tamely
+tamers
+tamest
+taming
+tammie
+tampan
+tamped
+tamper
+tampon
+tandem
+tanged
+tangle
+tangly
+tangos
+tanist
+tankas
+tanked
+tanker
+tanned
+tanner
+tannic
+tannin
+tannoy
+tanrec
+tantra
+tanuki
+tapalo
+tapers
+tapeta
+taping
+tapirs
+tapped
+tapper
+tappet
+tarama
+targes
+target
+tariff
+taring
+tarmac
+tarnal
+tarocs
+taroks
+tarots
+tarpan
+tarpon
+tarred
+tarres
+tarsal
+tarsia
+tarsus
+tartan
+tartar
+tarted
+tarter
+tartly
+tarzan
+tasked
+tassel
+tasses
+tasset
+tassie
+tasted
+taster
+tastes
+tatami
+tatars
+taters
+tatsoi
+tatted
+tatter
+tattie
+tattle
+tattoo
+taught
+taunts
+tauons
+taupes
+tauted
+tauten
+tauter
+tautly
+tautog
+tavern
+tawdry
+tawers
+tawing
+tawney
+tawpie
+tawsed
+tawses
+taxeme
+taxers
+taxied
+taxies
+taxing
+taxite
+taxman
+taxmen
+taxols
+taxons
+tazzas
+teabox
+teacup
+teamed
+teapot
+teapoy
+teared
+tearer
+teased
+teasel
+teaser
+teases
+teated
+teazel
+teazle
+teched
+techie
+techno
+tectal
+tectum
+tedded
+tedder
+tedium
+teeing
+teemed
+teemer
+teener
+teensy
+teepee
+teeter
+teethe
+teflon
+tegmen
+teguas
+teiids
+teinds
+tekkie
+telcos
+teledu
+telega
+telfer
+telial
+telium
+teller
+tellys
+telnet
+telome
+telson
+temped
+tempeh
+temper
+temple
+tempos
+tempts
+tenace
+tenail
+tenant
+tended
+tender
+tendon
+tendus
+tenets
+teniae
+tenias
+tenner
+tennis
+tenons
+tenors
+tenour
+tenpin
+tenrec
+tensed
+tenser
+tenses
+tensor
+tented
+tenter
+tenths
+tentie
+tenues
+tenuis
+tenure
+tenuti
+tenuto
+teopan
+tepals
+tepees
+tepefy
+tephra
+tepoys
+terais
+teraph
+terbia
+terbic
+tercel
+terces
+tercet
+teredo
+terete
+tergal
+tergum
+termed
+termer
+termly
+termor
+ternes
+terrae
+terras
+terret
+territ
+terror
+terser
+teslas
+testae
+tested
+testee
+tester
+teston
+tetany
+tetchy
+tether
+tetrad
+tetras
+tetris
+tetryl
+tetter
+tewing
+thacks
+thairm
+thaler
+thalli
+thanes
+thanks
+tharms
+thatch
+thawed
+thawer
+thebes
+thecae
+thecal
+thefts
+thegns
+theine
+theins
+theirs
+theism
+theist
+themed
+themes
+thenal
+thenar
+thence
+theory
+theres
+therme
+therms
+theses
+thesis
+thesps
+thetas
+thetic
+thicks
+thieve
+thighs
+thills
+things
+thinks
+thinly
+thiols
+thiram
+thirds
+thirls
+thirst
+thirty
+tholed
+tholes
+tholoi
+tholos
+thongs
+thorax
+thoria
+thoric
+thorns
+thorny
+thoron
+thorpe
+thorps
+thoued
+though
+thrall
+thrash
+thrave
+thrawn
+thraws
+thread
+threap
+threat
+threep
+threes
+thresh
+thrice
+thrift
+thrill
+thrips
+thrive
+throat
+throbs
+throes
+throne
+throng
+throve
+thrown
+throws
+thrums
+thrust
+thujas
+thulia
+thumbs
+thumps
+thunks
+thurls
+thusly
+thuyas
+thwack
+thwart
+thymes
+thymey
+thymic
+thymol
+thymus
+thyrse
+thyrsi
+tiaras
+tibiae
+tibial
+tibias
+ticals
+ticced
+ticked
+ticker
+ticket
+tickle
+tictac
+tictoc
+tidbit
+tiddly
+tidied
+tidier
+tidies
+tidily
+tiding
+tieing
+tiepin
+tierce
+tiered
+tiffed
+tiffin
+tigers
+tights
+tiglon
+tigons
+tikkas
+tilaks
+tildes
+tilers
+tiling
+tilled
+tiller
+tilted
+tilter
+tilths
+timbal
+timber
+timbre
+timely
+timers
+timing
+tincal
+tincts
+tinder
+tineal
+tineas
+tineid
+tinful
+tinged
+tinges
+tingle
+tingly
+tinier
+tinily
+tining
+tinker
+tinkle
+tinkly
+tinman
+tinmen
+tinned
+tinner
+tinpot
+tinsel
+tinted
+tinter
+tipcat
+tipoff
+tipped
+tipper
+tippet
+tipple
+tiptoe
+tiptop
+tirade
+tiring
+tirled
+tisane
+tissue
+titans
+tmeses
+tmesis
+toasts
+toasty
+tobies
+tocher
+tocsin
+todays
+toddle
+todies
+toecap
+toeing
+toffee
+togaed
+togate
+togged
+toggle
+togues
+toiled
+toiler
+toiles
+toited
+tokays
+tokens
+tolane
+tolans
+tolars
+toledo
+toling
+tolled
+toller
+toluic
+toluid
+toluol
+toluyl
+tolyls
+tomans
+tomato
+tombac
+tombak
+tombal
+tombed
+tomboy
+tomcat
+tomcod
+tommed
+tomtit
+tondos
+toneme
+toners
+tongas
+tonged
+tonger
+tongue
+tonics
+tonier
+toning
+tonish
+tonlet
+tonner
+tonnes
+tonsil
+tooled
+tooler
+toonie
+tooted
+tooter
+tooths
+toothy
+tootle
+tootsy
+topees
+topers
+topful
+tophes
+tophus
+topics
+toping
+topped
+topper
+topple
+toques
+toquet
+torahs
+torchy
+torero
+torics
+tories
+toroid
+torose
+toroth
+torous
+torpid
+torpor
+torque
+torrid
+torses
+torsks
+torsos
+tortas
+torten
+tortes
+torula
+toshes
+tossed
+tosses
+tossup
+totals
+totems
+toters
+tother
+toting
+totted
+totter
+toucan
+touche
+touchy
+toughs
+toughy
+toupee
+toured
+tourer
+toused
+touses
+tousle
+touted
+touter
+touzle
+towage
+toward
+towels
+towers
+towery
+towhee
+towies
+towing
+townee
+townie
+toxics
+toxine
+toxins
+toxoid
+toyers
+toying
+toyish
+toyons
+traced
+tracer
+traces
+tracks
+tracts
+traded
+trader
+trades
+tragic
+tragus
+traiks
+trails
+trains
+traits
+tramel
+tramps
+trampy
+trance
+tranks
+tranny
+tranqs
+trapan
+trapes
+trashy
+trauma
+travel
+traves
+trawls
+treads
+treats
+treaty
+treble
+trebly
+treens
+trefah
+tremor
+trench
+trends
+trendy
+trepan
+trepid
+tressy
+trevet
+triacs
+triads
+triage
+trials
+tribal
+tribes
+triced
+tricep
+trices
+tricks
+tricky
+tricot
+triene
+triens
+triers
+trifid
+trifle
+trigly
+trigon
+trigos
+trijet
+trikes
+trilby
+trills
+trimer
+trimly
+trinal
+trined
+trines
+triode
+triols
+triose
+tripes
+triple
+triply
+tripod
+tripos
+trippy
+triste
+triter
+triton
+triune
+trivet
+trivia
+troaks
+trocar
+troche
+trocks
+trogon
+troika
+troked
+trokes
+trolls
+trolly
+trompe
+tromps
+tronas
+trones
+troops
+tropes
+trophy
+tropic
+tropin
+troths
+trotyl
+trough
+troupe
+trouts
+trouty
+trover
+troves
+trowed
+trowel
+trowth
+truant
+truced
+truces
+trucks
+trudge
+truest
+truffe
+truing
+truism
+trulls
+trumps
+trunks
+trusts
+trusty
+truths
+trying
+tryout
+tryste
+trysts
+tsades
+tsadis
+tsetse
+tsking
+tsktsk
+tsores
+tsoris
+tsuris
+tubate
+tubbed
+tubber
+tubers
+tubful
+tubing
+tubist
+tubule
+tuchun
+tucked
+tucker
+tucket
+tuffet
+tufoli
+tufted
+tufter
+tugged
+tugger
+tugrik
+tuille
+tuladi
+tulips
+tulles
+tumble
+tumefy
+tumors
+tumour
+tumped
+tumuli
+tumult
+tundra
+tuners
+tuneup
+tunica
+tunics
+tuning
+tunned
+tunnel
+tupelo
+tupiks
+tupped
+tuques
+turaco
+turban
+turbid
+turbit
+turbos
+turbot
+tureen
+turfed
+turgid
+turgor
+turion
+turkey
+turned
+turner
+turnip
+turnon
+turnup
+turret
+turtle
+turves
+tusche
+tushed
+tushes
+tushie
+tusked
+tusker
+tussal
+tusseh
+tusser
+tusses
+tussis
+tussle
+tussor
+tussur
+tutees
+tutors
+tutted
+tuttis
+tutued
+tuxedo
+tuyere
+tuyers
+twains
+twanky
+tweaks
+tweaky
+tweeds
+tweedy
+tweens
+tweeny
+tweets
+tweeze
+twelve
+twenty
+twerps
+twibil
+twiers
+twiggy
+twilit
+twills
+twined
+twiner
+twines
+twinge
+twirls
+twirly
+twirps
+twists
+twisty
+twitch
+twofer
+twyers
+tycoon
+tymbal
+tympan
+tyning
+typhon
+typhus
+typier
+typify
+typing
+typist
+tyrant
+tyring
+tythed
+tythes
+tzetze
+tzuris
+uakari
+ubiety
+ubique
+udders
+uglier
+uglies
+uglify
+uglily
+ugsome
+uhlans
+ukases
+ulamas
+ulcers
+ulemas
+ullage
+ulster
+ultima
+ultimo
+ultras
+umamis
+umbels
+umbers
+umbles
+umbrae
+umbral
+umbras
+umiack
+umiacs
+umiaks
+umiaqs
+umlaut
+umping
+umpire
+unable
+unaged
+unakin
+unarms
+unawed
+unaxed
+unbale
+unbans
+unbars
+unbear
+unbelt
+unbend
+unbent
+unbind
+unbolt
+unborn
+unbred
+unbusy
+uncage
+uncake
+uncaps
+uncase
+uncast
+unchic
+unciae
+uncial
+uncini
+unclad
+uncles
+unclip
+unclog
+uncoil
+uncool
+uncork
+uncuff
+uncurb
+uncurl
+uncute
+undead
+undies
+undine
+undock
+undoer
+undoes
+undone
+undraw
+undrew
+unduly
+undyed
+unease
+uneasy
+uneven
+unfair
+unfelt
+unfits
+unfixt
+unfold
+unfond
+unfree
+unfurl
+ungird
+ungirt
+unglue
+ungual
+ungues
+unguis
+ungula
+unhair
+unhand
+unhang
+unhats
+unhelm
+unhewn
+unholy
+unhood
+unhook
+unhung
+unhurt
+unhusk
+unific
+unions
+unipod
+unique
+unisex
+unison
+united
+uniter
+unites
+unjams
+unjust
+unkend
+unkent
+unkept
+unkind
+unkink
+unknit
+unknot
+unlace
+unlade
+unlaid
+unlash
+unlays
+unlead
+unless
+unlike
+unlink
+unlive
+unload
+unlock
+unmade
+unmake
+unmans
+unmask
+unmeet
+unmesh
+unmews
+unmixt
+unmold
+unmoor
+unmown
+unnail
+unopen
+unpack
+unpaid
+unpegs
+unpens
+unpent
+unpick
+unpile
+unpins
+unplug
+unpure
+unread
+unreal
+unreel
+unrent
+unrest
+unrigs
+unripe
+unrips
+unrobe
+unroll
+unroof
+unroot
+unrove
+unruly
+unsafe
+unsaid
+unsawn
+unsays
+unseal
+unseam
+unseat
+unseen
+unsell
+unsent
+unsets
+unsewn
+unsews
+unsexy
+unshed
+unship
+unshod
+unshut
+unsnag
+unsnap
+unsold
+unsown
+unspun
+unstep
+unstop
+unsung
+unsunk
+unsure
+untack
+untame
+untidy
+untied
+unties
+untold
+untorn
+untrim
+untrod
+untrue
+untuck
+untune
+unused
+unveil
+unvext
+unwary
+unwell
+unwept
+unwind
+unwise
+unwish
+unwits
+unworn
+unwove
+unwrap
+unyoke
+unzips
+upases
+upbear
+upbeat
+upbind
+upboil
+upbore
+upbows
+upcast
+upcoil
+upcurl
+updart
+update
+updive
+updove
+upends
+upflow
+upfold
+upgaze
+upgird
+upgirt
+upgrew
+upgrow
+upheap
+upheld
+uphill
+uphold
+uphove
+uphroe
+upkeep
+upland
+upleap
+uplift
+uplink
+upload
+upmost
+uppers
+uppile
+upping
+uppish
+uppity
+upprop
+uprate
+uprear
+uprise
+uproar
+uproot
+uprose
+uprush
+upsend
+upsent
+upsets
+upshot
+upside
+upsize
+upsoar
+upstep
+upstir
+uptake
+uptalk
+uptear
+uptick
+uptilt
+uptime
+uptore
+uptorn
+uptoss
+uptown
+upturn
+upwaft
+upward
+upwell
+upwind
+uracil
+uraeus
+urania
+uranic
+uranyl
+urares
+uraris
+urases
+urates
+uratic
+urbane
+urbias
+urchin
+urease
+uredia
+uredos
+ureide
+uremia
+uremic
+urgent
+urgers
+urging
+urials
+uropod
+urping
+ursids
+ursine
+urtext
+uruses
+usable
+usably
+usages
+usance
+useful
+ushers
+usneas
+usques
+usuals
+usurer
+usurps
+uterus
+utmost
+utopia
+utters
+uveous
+uvulae
+uvular
+uvulas
+vacant
+vacate
+vacuum
+vadose
+vagary
+vagile
+vagrom
+vaguer
+vahine
+vailed
+vainer
+vainly
+vakeel
+vakils
+valets
+valgus
+valine
+valise
+valkyr
+valley
+valors
+valour
+valses
+valued
+valuer
+values
+valuta
+valval
+valvar
+valved
+valves
+vamose
+vamped
+vamper
+vandal
+vandas
+vanish
+vanity
+vanman
+vanmen
+vanned
+vanner
+vapors
+vapory
+vapour
+varias
+varied
+varier
+varies
+varlet
+varnas
+varoom
+varved
+varves
+vassal
+vaster
+vastly
+vatful
+vatted
+vaults
+vaulty
+vaunts
+vaunty
+vaward
+vealed
+vealer
+vector
+veejay
+veenas
+veepee
+veered
+vegans
+vegete
+vegged
+veggie
+vegies
+veiled
+veiler
+veinal
+veined
+veiner
+velars
+velate
+velcro
+veldts
+vellum
+veloce
+velour
+velure
+velvet
+vended
+vendee
+vender
+vendor
+vendue
+veneer
+venene
+venery
+venged
+venges
+venial
+venine
+venins
+venire
+venoms
+venose
+venous
+vented
+venter
+venues
+venule
+verbal
+verbid
+verdin
+verged
+verger
+verges
+verier
+verify
+verily
+verism
+verist
+verite
+verity
+vermes
+vermin
+vermis
+vernal
+vernix
+versal
+versed
+verser
+verses
+verset
+versos
+verste
+versts
+versus
+vertex
+vertus
+verves
+vervet
+vesica
+vesper
+vespid
+vessel
+vestal
+vestas
+vested
+vestee
+vestry
+vetoed
+vetoer
+vetoes
+vetted
+vetter
+vexers
+vexils
+vexing
+viable
+viably
+vialed
+viands
+viatic
+viator
+vibist
+vibrio
+vicars
+vicing
+victim
+victor
+vicuna
+videos
+viewed
+viewer
+vigias
+vigils
+vigors
+vigour
+viking
+vilely
+vilest
+vilify
+villae
+villas
+villus
+vimina
+vinals
+vincas
+vineal
+vinery
+vinier
+vinify
+vining
+vinous
+vinyls
+violas
+violet
+violin
+vipers
+virago
+vireos
+virgas
+virgin
+virile
+virion
+viroid
+virtue
+virtus
+visaed
+visage
+visard
+viscid
+viscus
+viseed
+vising
+vision
+visits
+visive
+visors
+vistas
+visual
+vitals
+vitric
+vittae
+vittle
+vivace
+vivary
+vivers
+vivify
+vixens
+vizard
+vizier
+vizirs
+vizors
+vizsla
+vocabs
+vocals
+vodkas
+vodoun
+vodous
+voduns
+vogued
+voguer
+vogues
+voiced
+voicer
+voices
+voided
+voider
+voiles
+volant
+volery
+voling
+volley
+volost
+voltes
+volume
+volute
+volvas
+volvox
+vomers
+vomica
+voodoo
+vortex
+votary
+voters
+voting
+votive
+voudon
+vowels
+vowers
+vowing
+voyage
+voyeur
+vrooms
+vrouws
+wabble
+wabbly
+wadded
+wadder
+waddie
+waddle
+waddly
+waders
+wadies
+wading
+wadmal
+wadmel
+wadmol
+wadset
+waeful
+wafers
+wafery
+waffed
+waffie
+waffle
+waffly
+wafted
+wafter
+wagers
+wagged
+wagger
+waggle
+waggly
+waggon
+waging
+wagons
+wahine
+wahoos
+waifed
+wailed
+wailer
+waired
+waists
+waited
+waiter
+waived
+waiver
+waives
+wakame
+wakens
+wakers
+wakiki
+waking
+walers
+walies
+waling
+walked
+walker
+walkup
+wallah
+wallas
+walled
+wallet
+wallie
+wallop
+wallow
+walnut
+walrus
+wamble
+wambly
+wammus
+wampum
+wampus
+wander
+wandle
+wanier
+waning
+wanion
+wanned
+wanner
+wanted
+wanter
+wanton
+wapiti
+wapped
+warble
+warded
+warden
+warder
+warier
+warily
+waring
+warked
+warmed
+warmer
+warmly
+warmth
+warmup
+warned
+warner
+warped
+warper
+warred
+warren
+warsaw
+warsle
+warted
+wasabi
+washed
+washer
+washes
+washup
+wastes
+wastry
+watape
+wataps
+waters
+watery
+watter
+wattle
+waucht
+waught
+wauked
+wauled
+wavers
+wavery
+waveys
+wavier
+wavies
+wavily
+waving
+wawled
+waxers
+waxier
+waxily
+waxing
+waylay
+wazoos
+weaken
+weaker
+weakly
+weakon
+wealds
+wealth
+weaned
+weaner
+weapon
+wearer
+weasel
+weason
+weaved
+weaver
+weaves
+webbed
+webcam
+webers
+webfed
+weblog
+wechts
+wedded
+wedder
+wedeln
+wedels
+wedged
+wedges
+wedgie
+weeded
+weeder
+weekly
+weened
+weenie
+weensy
+weeper
+weepie
+weeted
+weever
+weevil
+weewee
+weighs
+weight
+weirds
+weirdy
+welded
+welder
+weldor
+welkin
+welled
+wellie
+welted
+welter
+wended
+weskit
+wester
+wether
+wetted
+whacks
+whacky
+whaled
+whaler
+whales
+whammo
+whammy
+whangs
+wharfs
+wharve
+whaups
+wheals
+wheats
+wheels
+wheens
+wheeps
+wheeze
+wheezy
+whelks
+whelky
+whelms
+whelps
+whenas
+whence
+wheres
+wherry
+wherve
+wheyey
+whidah
+whiffs
+whiled
+whiles
+whilom
+whilst
+whimsy
+whined
+whiner
+whines
+whiney
+whinge
+whinny
+whippy
+whirls
+whirly
+whirrs
+whirry
+whisht
+whisks
+whisky
+whists
+whited
+whiten
+whiter
+whites
+whitey
+whizzy
+wholes
+wholly
+whomps
+whomso
+whoofs
+whoops
+whoosh
+whorls
+whorts
+whosis
+whumps
+whydah
+wiccan
+wiccas
+wiches
+wicked
+wicker
+wicket
+wicopy
+widder
+widdie
+widdle
+widely
+widens
+widest
+widget
+widish
+widows
+widths
+wields
+wieldy
+wiener
+wienie
+wifely
+wifeys
+wifing
+wigans
+wigeon
+wigged
+wiggle
+wiggly
+wights
+wiglet
+wigwag
+wigwam
+wikiup
+wilded
+wilder
+wildly
+wilful
+wilier
+wilily
+wiling
+willed
+willer
+willet
+willie
+willow
+wilted
+wimble
+wimmin
+wimped
+wimple
+winced
+wincer
+winces
+wincey
+winded
+winder
+windle
+window
+windup
+winery
+winged
+winger
+winier
+wining
+winish
+winked
+winker
+winkle
+winned
+winner
+winnow
+winoes
+winter
+wintle
+wintry
+winzes
+wipers
+wiping
+wirers
+wirier
+wirily
+wiring
+wisdom
+wisely
+wisent
+wisest
+wished
+wisher
+wishes
+wising
+wisped
+wissed
+wisses
+wisted
+witans
+witchy
+withal
+withed
+wither
+withes
+within
+witing
+witney
+witted
+wittol
+wivern
+wivers
+wiving
+wizard
+wizens
+wizzen
+wizzes
+woaded
+woalds
+wobble
+wobbly
+wodges
+woeful
+wolfed
+wolfer
+wolver
+wolves
+womans
+wombat
+wombed
+womera
+wonder
+wonned
+wonner
+wonted
+wonton
+wooded
+wooden
+woodie
+woodsy
+wooers
+woofed
+woofer
+wooing
+wooled
+woolen
+wooler
+woolie
+woolly
+worded
+worked
+worker
+workup
+worlds
+wormed
+wormer
+wormil
+worrit
+worsen
+worser
+worses
+worset
+worsts
+worths
+worthy
+wotted
+wounds
+wovens
+wowing
+wowser
+wracks
+wraith
+wrangs
+wrasse
+wraths
+wrathy
+wreaks
+wreath
+wrecks
+wrench
+wrests
+wretch
+wricks
+wriest
+wright
+wrings
+wrists
+wristy
+writer
+writes
+writhe
+wrongs
+wryest
+wrying
+wursts
+wurzel
+wusses
+wuther
+wyches
+wyling
+wyting
+wyvern
+xebecs
+xenial
+xenias
+xenons
+xylans
+xylems
+xylene
+xyloid
+xylols
+xylose
+xylyls
+xyster
+xystoi
+xystos
+xystus
+yabber
+yabbie
+yachts
+yacked
+yaffed
+yagers
+yahoos
+yairds
+yakked
+yakker
+yakuza
+yamens
+yammer
+yamuns
+yanked
+yanqui
+yantra
+yapock
+yapoks
+yapons
+yapped
+yapper
+yarded
+yarder
+yarely
+yarest
+yarned
+yarner
+yarrow
+yasmak
+yatter
+yauped
+yauper
+yaupon
+yautia
+yawing
+yawled
+yawned
+yawner
+yawped
+yawper
+yclept
+yeaned
+yearly
+yearns
+yeasts
+yeasty
+yecchs
+yeelin
+yelled
+yeller
+yellow
+yelped
+yelper
+yenned
+yentas
+yentes
+yeoman
+yeomen
+yerbas
+yerked
+yessed
+yesses
+yester
+yeuked
+yields
+yipped
+yippee
+yippie
+yirred
+yirths
+yobbos
+yocked
+yodels
+yodled
+yodler
+yodles
+yogees
+yogini
+yogins
+yogurt
+yoicks
+yokels
+yoking
+yolked
+yonder
+yonker
+youngs
+youpon
+youths
+yowies
+yowing
+yowled
+yowler
+yttria
+yttric
+yuccas
+yucked
+yukked
+yulans
+yupons
+yuppie
+yutzes
+zaddik
+zaffar
+zaffer
+zaffir
+zaffre
+zaftig
+zagged
+zaikai
+zaires
+zamias
+zanana
+zander
+zanier
+zanies
+zanily
+zanzas
+zapped
+zapper
+zareba
+zariba
+zayins
+zazens
+zealot
+zeatin
+zebeck
+zebecs
+zebras
+zechin
+zenana
+zenith
+zephyr
+zeroed
+zeroes
+zeroth
+zested
+zester
+zeugma
+zibeth
+zibets
+zigged
+zigzag
+zillah
+zinced
+zincic
+zincky
+zinebs
+zinged
+zinger
+zinnia
+zipped
+zipper
+zirams
+zircon
+zither
+zizith
+zizzle
+zlotys
+zoaria
+zocalo
+zodiac
+zoecia
+zoftig
+zombie
+zombis
+zonary
+zonate
+zoners
+zoning
+zonked
+zonula
+zonule
+zooids
+zooier
+zoomed
+zoonal
+zooned
+zorils
+zoster
+zouave
+zounds
+zoysia
+zydeco
+zygoid
+zygoma
+zygose
+zygote
+zymase
diff --git a/vendor/ezyang/htmlpurifier/CREDITS b/vendor/ezyang/htmlpurifier/CREDITS
new file mode 100644
index 0000000..7921b45
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/CREDITS
@@ -0,0 +1,9 @@
+
+CREDITS
+
+Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks
+to the DevNetwork Community for their help (see docs/ref-devnetwork.html for
+more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake
+for letting me package his fantastic XSS cheatsheet for a smoketest.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/INSTALL b/vendor/ezyang/htmlpurifier/INSTALL
new file mode 100644
index 0000000..e6dd02a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/INSTALL
@@ -0,0 +1,373 @@
+
+Install
+ How to install HTML Purifier
+
+HTML Purifier is designed to run out of the box, so actually using the
+library is extremely easy. (Although... if you were looking for a
+step-by-step installation GUI, you've downloaded the wrong software!)
+
+While the impatient can get going immediately with some of the sample
+code at the bottom of this library, it's well worth reading this entire
+document--most of the other documentation assumes that you are familiar
+with these contents.
+
+
+---------------------------------------------------------------------------
+1. Compatibility
+
+HTML Purifier is PHP 5 and PHP 7, and is actively tested from PHP 5.0.5
+and up. It has no core dependencies with other libraries.
+
+These optional extensions can enhance the capabilities of HTML Purifier:
+
+ * iconv : Converts text to and from non-UTF-8 encodings
+ * bcmath : Used for unit conversion and imagecrash protection
+ * tidy : Used for pretty-printing HTML
+
+These optional libraries can enhance the capabilities of HTML Purifier:
+
+ * CSSTidy : Clean CSS stylesheets using %Core.ExtractStyleBlocks
+ Note: You should use the modernized fork of CSSTidy available
+ at https://github.com/Cerdic/CSSTidy
+ * Net_IDNA2 (PEAR) : IRI support using %Core.EnableIDNA
+ Note: This is not necessary for PHP 5.3 or later
+
+---------------------------------------------------------------------------
+2. Reconnaissance
+
+A big plus of HTML Purifier is its inerrant support of standards, so
+your web-pages should be standards-compliant. (They should also use
+semantic markup, but that's another issue altogether, one HTML Purifier
+cannot fix without reading your mind.)
+
+HTML Purifier can process these doctypes:
+
+* XHTML 1.0 Transitional (default)
+* XHTML 1.0 Strict
+* HTML 4.01 Transitional
+* HTML 4.01 Strict
+* XHTML 1.1
+
+...and these character encodings:
+
+* UTF-8 (default)
+* Any encoding iconv supports (with crippled internationalization support)
+
+These defaults reflect what my choices would be if I were authoring an
+HTML document, however, what you choose depends on the nature of your
+codebase. If you don't know what doctype you are using, you can determine
+the doctype from this identifier at the top of your source code:
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+...and the character encoding from this code:
+
+ <meta http-equiv="Content-type" content="text/html;charset=ENCODING">
+
+If the character encoding declaration is missing, STOP NOW, and
+read 'docs/enduser-utf8.html' (web accessible at
+http://htmlpurifier.org/docs/enduser-utf8.html). In fact, even if it is
+present, read this document anyway, as many websites specify their
+document's character encoding incorrectly.
+
+
+---------------------------------------------------------------------------
+3. Including the library
+
+The procedure is quite simple:
+
+ require_once '/path/to/library/HTMLPurifier.auto.php';
+
+This will setup an autoloader, so the library's files are only included
+when you use them.
+
+Only the contents in the library/ folder are necessary, so you can remove
+everything else when using HTML Purifier in a production environment.
+
+If you installed HTML Purifier via PEAR, all you need to do is:
+
+ require_once 'HTMLPurifier.auto.php';
+
+Please note that the usual PEAR practice of including just the classes you
+want will not work with HTML Purifier's autoloading scheme.
+
+Advanced users, read on; other users can skip to section 4.
+
+Autoload compatibility
+----------------------
+
+ HTML Purifier attempts to be as smart as possible when registering an
+ autoloader, but there are some cases where you will need to change
+ your own code to accomodate HTML Purifier. These are those cases:
+
+ PHP VERSION IS LESS THAN 5.1.2, AND YOU'VE DEFINED __autoload
+ Because spl_autoload_register() doesn't exist in early versions
+ of PHP 5, HTML Purifier has no way of adding itself to the autoload
+ stack. Modify your __autoload function to test
+ HTMLPurifier_Bootstrap::autoload($class)
+
+ For example, suppose your autoload function looks like this:
+
+ function __autoload($class) {
+ require str_replace('_', '/', $class) . '.php';
+ return true;
+ }
+
+ A modified version with HTML Purifier would look like this:
+
+ function __autoload($class) {
+ if (HTMLPurifier_Bootstrap::autoload($class)) return true;
+ require str_replace('_', '/', $class) . '.php';
+ return true;
+ }
+
+ Note that there *is* some custom behavior in our autoloader; the
+ original autoloader in our example would work for 99% of the time,
+ but would fail when including language files.
+
+ AN __autoload FUNCTION IS DECLARED AFTER OUR AUTOLOADER IS REGISTERED
+ spl_autoload_register() has the curious behavior of disabling
+ the existing __autoload() handler. Users need to explicitly
+ spl_autoload_register('__autoload'). Because we use SPL when it
+ is available, __autoload() will ALWAYS be disabled. If __autoload()
+ is declared before HTML Purifier is loaded, this is not a problem:
+ HTML Purifier will register the function for you. But if it is
+ declared afterwards, it will mysteriously not work. This
+ snippet of code (after your autoloader is defined) will fix it:
+
+ spl_autoload_register('__autoload')
+
+ Users should also be on guard if they use a version of PHP previous
+ to 5.1.2 without an autoloader--HTML Purifier will define __autoload()
+ for you, which can collide with an autoloader that was added by *you*
+ later.
+
+
+For better performance
+----------------------
+
+ Opcode caches, which greatly speed up PHP initialization for scripts
+ with large amounts of code (HTML Purifier included), don't like
+ autoloaders. We offer an include file that includes all of HTML Purifier's
+ files in one go in an opcode cache friendly manner:
+
+ // If /path/to/library isn't already in your include path, uncomment
+ // the below line:
+ // require '/path/to/library/HTMLPurifier.path.php';
+
+ require 'HTMLPurifier.includes.php';
+
+ Optional components still need to be included--you'll know if you try to
+ use a feature and you get a class doesn't exists error! The autoloader
+ can be used in conjunction with this approach to catch classes that are
+ missing. Simply add this afterwards:
+
+ require 'HTMLPurifier.autoload.php';
+
+Standalone version
+------------------
+
+ HTML Purifier has a standalone distribution; you can also generate
+ a standalone file from the full version by running the script
+ maintenance/generate-standalone.php . The standalone version has the
+ benefit of having most of its code in one file, so parsing is much
+ faster and the library is easier to manage.
+
+ If HTMLPurifier.standalone.php exists in the library directory, you
+ can use it like this:
+
+ require '/path/to/HTMLPurifier.standalone.php';
+
+ This is equivalent to including HTMLPurifier.includes.php, except that
+ the contents of standalone/ will be added to your path. To override this
+ behavior, specify a new HTMLPURIFIER_PREFIX where standalone files can
+ be found (usually, this will be one directory up, the "true" library
+ directory in full distributions). Don't forget to set your path too!
+
+ The autoloader can be added to the end to ensure the classes are
+ loaded when necessary; otherwise you can manually include them.
+ To use the autoloader, use this:
+
+ require 'HTMLPurifier.autoload.php';
+
+For advanced users
+------------------
+
+ HTMLPurifier.auto.php performs a number of operations that can be done
+ individually. These are:
+
+ HTMLPurifier.path.php
+ Puts /path/to/library in the include path. For high performance,
+ this should be done in php.ini.
+
+ HTMLPurifier.autoload.php
+ Registers our autoload handler HTMLPurifier_Bootstrap::autoload($class).
+
+ You can do these operations by yourself--in fact, you must modify your own
+ autoload handler if you are using a version of PHP earlier than PHP 5.1.2
+ (See "Autoload compatibility" above).
+
+
+---------------------------------------------------------------------------
+4. Configuration
+
+HTML Purifier is designed to run out-of-the-box, but occasionally HTML
+Purifier needs to be told what to do. If you answer no to any of these
+questions, read on; otherwise, you can skip to the next section (or, if you're
+into configuring things just for the heck of it, skip to 4.3).
+
+* Am I using UTF-8?
+* Am I using XHTML 1.0 Transitional?
+
+If you answered no to any of these questions, instantiate a configuration
+object and read on:
+
+ $config = HTMLPurifier_Config::createDefault();
+
+
+4.1. Setting a different character encoding
+
+You really shouldn't use any other encoding except UTF-8, especially if you
+plan to support multilingual websites (read section three for more details).
+However, switching to UTF-8 is not always immediately feasible, so we can
+adapt.
+
+HTML Purifier uses iconv to support other character encodings, as such,
+any encoding that iconv supports <http://www.gnu.org/software/libiconv/>
+HTML Purifier supports with this code:
+
+ $config->set('Core.Encoding', /* put your encoding here */);
+
+An example usage for Latin-1 websites (the most common encoding for English
+websites):
+
+ $config->set('Core.Encoding', 'ISO-8859-1');
+
+Note that HTML Purifier's support for non-Unicode encodings is crippled by the
+fact that any character not supported by that encoding will be silently
+dropped, EVEN if it is ampersand escaped. If you want to work around
+this, you are welcome to read docs/enduser-utf8.html for a fix,
+but please be cognizant of the issues the "solution" creates (for this
+reason, I do not include the solution in this document).
+
+
+4.2. Setting a different doctype
+
+For those of you using HTML 4.01 Transitional, you can disable
+XHTML output like this:
+
+ $config->set('HTML.Doctype', 'HTML 4.01 Transitional');
+
+Other supported doctypes include:
+
+ * HTML 4.01 Strict
+ * HTML 4.01 Transitional
+ * XHTML 1.0 Strict
+ * XHTML 1.0 Transitional
+ * XHTML 1.1
+
+
+4.3. Other settings
+
+There are more configuration directives which can be read about
+here: <http://htmlpurifier.org/live/configdoc/plain.html> They're a bit boring,
+but they can help out for those of you who like to exert maximum control over
+your code. Some of the more interesting ones are configurable at the
+demo <http://htmlpurifier.org/demo.php> and are well worth looking into
+for your own system.
+
+For example, you can fine tune allowed elements and attributes, convert
+relative URLs to absolute ones, and even autoparagraph input text! These
+are, respectively, %HTML.Allowed, %URI.MakeAbsolute and %URI.Base, and
+%AutoFormat.AutoParagraph. The %Namespace.Directive naming convention
+translates to:
+
+ $config->set('Namespace.Directive', $value);
+
+E.g.
+
+ $config->set('HTML.Allowed', 'p,b,a[href],i');
+ $config->set('URI.Base', 'http://www.example.com');
+ $config->set('URI.MakeAbsolute', true);
+ $config->set('AutoFormat.AutoParagraph', true);
+
+
+---------------------------------------------------------------------------
+5. Caching
+
+HTML Purifier generates some cache files (generally one or two) to speed up
+its execution. For maximum performance, make sure that
+library/HTMLPurifier/DefinitionCache/Serializer is writeable by the webserver.
+
+If you are in the library/ folder of HTML Purifier, you can set the
+appropriate permissions using:
+
+ chmod -R 0755 HTMLPurifier/DefinitionCache/Serializer
+
+If the above command doesn't work, you may need to assign write permissions
+to group:
+
+ chmod -R 0775 HTMLPurifier/DefinitionCache/Serializer
+
+You can also chmod files via your FTP client; this option
+is usually accessible by right clicking the corresponding directory and
+then selecting "chmod" or "file permissions".
+
+Starting with 2.0.1, HTML Purifier will generate friendly error messages
+that will tell you exactly what you have to chmod the directory to, if in doubt,
+follow its advice.
+
+If you are unable or unwilling to give write permissions to the cache
+directory, you can either disable the cache (and suffer a performance
+hit):
+
+ $config->set('Core.DefinitionCache', null);
+
+Or move the cache directory somewhere else (no trailing slash):
+
+ $config->set('Cache.SerializerPath', '/home/user/absolute/path');
+
+
+---------------------------------------------------------------------------
+6. Using the code
+
+The interface is mind-numbingly simple:
+
+ $purifier = new HTMLPurifier($config);
+ $clean_html = $purifier->purify( $dirty_html );
+
+That's it! For more examples, check out docs/examples/ (they aren't very
+different though). Also, docs/enduser-slow.html gives advice on what to
+do if HTML Purifier is slowing down your application.
+
+
+---------------------------------------------------------------------------
+7. Quick install
+
+First, make sure library/HTMLPurifier/DefinitionCache/Serializer is
+writable by the webserver (see Section 5: Caching above for details).
+If your website is in UTF-8 and XHTML Transitional, use this code:
+
+<?php
+ require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($config);
+ $clean_html = $purifier->purify($dirty_html);
+?>
+
+If your website is in a different encoding or doctype, use this code:
+
+<?php
+ require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Core.Encoding', 'ISO-8859-1'); // replace with your encoding
+ $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); // replace with your doctype
+ $purifier = new HTMLPurifier($config);
+
+ $clean_html = $purifier->purify($dirty_html);
+?>
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8 b/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8
new file mode 100644
index 0000000..95164ab
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8
@@ -0,0 +1,60 @@
+
+Installation
+ Comment installer HTML Purifier
+
+Attention : Ce document est encodé en UTF-8, si les lettres avec des accents
+ne s'affichent pas, prenez un meilleur éditeur de texte.
+
+L'installation de HTML Purifier est très simple, parce qu'il n'a pas besoin
+de configuration. Pour les utilisateurs impatients, le code se trouve dans le
+pied de page, mais je recommande de lire le document.
+
+1. Compatibilité
+
+HTML Purifier fonctionne avec PHP 5. PHP 5.0.5 est la dernière version testée.
+Il ne dépend pas d'autres librairies.
+
+Les extensions optionnelles sont iconv (généralement déjà installée) et tidy
+(répendue aussi). Si vous utilisez UTF-8 et que vous ne voulez pas l'indentation,
+vous pouvez utiliser HTML Purifier sans ces extensions.
+
+
+2. Inclure la librairie
+
+Quand vous devez l'utilisez, incluez le :
+
+ require_once('/path/to/library/HTMLPurifier.auto.php');
+
+Ne pas l'inclure si ce n'est pas nécessaire, car HTML Purifier est lourd.
+
+HTML Purifier utilise "autoload". Si vous avez défini la fonction __autoload,
+vous devez ajouter cette fonction :
+
+ spl_autoload_register('__autoload')
+
+Plus d'informations dans le document "INSTALL".
+
+3. Installation rapide
+
+Si votre site Web est en UTF-8 et XHTML Transitional, utilisez :
+
+<?php
+ require_once('/path/to/htmlpurifier/library/HTMLPurifier.auto.php');
+ $purificateur = new HTMLPurifier();
+ $html_propre = $purificateur->purify($html_a_purifier);
+?>
+
+Sinon, utilisez :
+
+<?php
+ require_once('/path/to/html/purifier/library/HTMLPurifier.auto.load');
+ $config = $HTMLPurifier_Config::createDefault();
+ $config->set('Core', 'Encoding', 'ISO-8859-1'); //Remplacez par votre
+ encodage
+ $config->set('Core', 'XHTML', true); //Remplacer par false si HTML 4.01
+ $purificateur = new HTMLPurifier($config);
+ $html_propre = $purificateur->purify($html_a_purifier);
+?>
+
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/LICENSE b/vendor/ezyang/htmlpurifier/LICENSE
new file mode 100644
index 0000000..8c88a20
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/LICENSE
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/NEWS b/vendor/ezyang/htmlpurifier/NEWS
new file mode 100644
index 0000000..9b6e102
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/NEWS
@@ -0,0 +1,1190 @@
+NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+= KEY ====================
+ # Breaks back-compat
+ ! Feature
+ - Bugfix
+ + Sub-comment
+ . Internal change
+==========================
+
+4.10.0, released 2018-02-22
+# PHP 5.3 is no longer officially supported by HTML Purifier
+ (we did not specifically break support, but we are no longer
+ testing on PHP 5.3)
+! Relative CSS length units are now supported
+- A few PHP 7.2 compatibility fixes, thanks John Flatness
+ <john@zerocrates.org>
+- Improve portability with old versions of libxml which don't
+ support accessing the data of a node
+- IDNA2008 is now used for converting domains to ASCII, fixing
+ some rather strange bugs with international domains
+- Fix race condition resulting in E_WARNING when creating
+ directories with Serializer
+
+4.9.3, released 2017-06-02
+- Workaround PHP 7.1 infinite loop when opcode cache is enabled.
+ Thanks @Xiphin (#134, #135)
+- Don't use autoloader when testing for DOMDocument. Hypothetically,
+ this could cause your install to start using DirectLex if you had
+ previously been monkeypatching in a custom, autoloaded implementation
+ of DOMDocument. Don't do that. Thanks @Izumi-kun (#130)
+
+4.9.2, released 2017-03-12
+- Fixes PHP 5.3 compatibility
+- Fix breakage when decoding decimal entities. Thanks @rybakit (#129)
+
+4.9.1, released 2017-03-08
+! %URI.DefaultScheme can now be set to null, in which case
+ all relative paths are removed.
+! New CSS properties: min-width, max-width, min-height, max-height (#94)
+! Transparency (rgba) and hsl/hsla supported where color CSS is present.
+ Thanks @fxbt for contributing the patch. (#118)
+- When idn_to_ascii is defined, we might accept malformed
+ hostnames. Apply validation to the result in such cases.
+- Close directory when done in Serializer DefinitionCache (#100)
+- Deleted some asserts to avoid linters from choking (#97)
+- Rework Serializer cache behavior to avoid chmod'ing if possible (#32)
+- Embedded semicolons in strings in CSS are now handled correctly!
+- We accidentally dropped certain Unicode characters if there was
+ one or more invalid characters. This has been fixed, thanks
+ to mpyw <ryosuke_i_628@yahoo.co.jp>
+- Fix for "Don't truncate upon encountering </div> when using DOMLex"
+ caused a regression with HTML 4.01 Strict parsing with libxml 2.9.1
+ (and maybe later versions, but known OK with libxml 2.9.4). The
+ fix is to go about handling truncation a bit more cleverly so that
+ we can wrap with divs (sidestepping the bug) but slurping out the
+ rest of the text in case it ran off the end. (#78)
+- Fix PREG_BACKTRACK_LIMIT_ERROR in HTMLPurifier_Filter_ExtractStyle.
+ Thanks @breathbath for contributing the report and fix (#120)
+- Fix entity decoding algorithm to be more conservative about
+ decoding entities that are missing trailing semicolon.
+ To get old behavior, set %Core.LegacyEntityDecoder to true.
+ (#119)
+- Workaround libxml bug when HTML tags are embedded inside
+ script tags. To disable workaround set %Core.AggressivelyRemoveScript
+ to false. (#83)
+# By default, when a link has a target attribute associated
+ with it, we now also add rel="noopener" in order to
+ prevent the new window from being able to overwrite
+ the original frame. To disable this protection,
+ set %HTML.TargetNoopener to FALSE.
+
+4.9.0 was cut on Git but never properly released; when we did the
+real release we decided to skip this version number.
+
+4.8.0, released 2016-07-16
+# By default, when a link has a target attribute associated
+ with it, we now also add rel="noreferrer" in order to
+ prevent the new window from being able to overwrite
+ the original frame. To disable this protection,
+ set %HTML.TargetNoreferrer to FALSE.
+! Full PHP 7 compatibility, the test suite is ALL GO.
+! %CSS.AllowDuplicates permits duplicate CSS properties.
+! Support for 'tel' URIs.
+! Partial support for 'border-radius' properties when %CSS.AllowProprietary is true.
+ The slash syntax, i.e., 'border-radius: 2em 1em 4em / 0.5em 3em' is not
+ yet supported.
+! %Attr.ID.HTML5 turns on HTML5-style ID handling.
+- alt truncation could result in malformed UTF-8 sequence. Don't
+ truncate. Thanks Brandon Farber for reporting.
+- Linkify regex is smarter, based off of Gruber's regex.
+- IDNA supported natively on PHP 5.3 and later.
+- Non all-numeric top-level names (e.g., foo.1f, 1f) are now
+ allowed.
+- Minor bounds error fix to squash a PHP 7 notice.
+- Support non-/tmp temporary directories for data:// validation
+- Give a better error message when a user attempts to allow
+ ul/ol without allowing li.
+- On some versions of PHP, the Serializer DefinitionCache could
+ infinite loop when the directory exists but is not listable. (#49)
+- Don't match for <body> inside comments with
+ %Core.ConvertDocumentToFragment. (#67)
+- SafeObject is now less case sensitive. (#57)
+- AutoFormat.RemoveEmpty.Predicate now correctly renders in
+ web form. (#85)
+
+4.7.0, released 2015-08-04
+# opacity is now considered a "tricky" CSS property rather than a
+ proprietary one.
+! %AutoFormat.RemoveEmpty.Predicate for specifying exactly when
+ an element should be considered "empty" (maybe preserve if it
+ has attributes), and modify iframe support so that the iframe
+ is removed if it is missing a src attribute. Thanks meeva for
+ reporting.
+- Don't truncate upon encountering </div> when using DOMLex. Thanks
+ Myrto Christina for finally convincing me to fix this.
+- Update YouTube filter for new code.
+- Fix parsing of rgb() values with spaces in them for 'border'
+ attribute.
+- Don't remove foo="" attributes if foo is a boolean attribute. Thanks
+ valME for reporting.
+
+4.6.0, released 2013-11-30
+# Secure URI munge hashing algorithm has changed to hash_hmac("sha256", $url, $secret).
+ Please update any verification scripts you may have.
+# URI parsing algorithm was made more strict, so only prefixes which
+ looks like schemes will actually be schemes. Thanks
+ Michael Gusev <mgusev@sugarcrm.com> for fixing.
+# %Core.EscapeInvalidChildren is no longer supported, and no longer does
+ anything.
+! New directive %Core.AllowHostnameUnderscore which allows underscores
+ in hostnames.
+- Eliminate quadratic behavior in DOMLex by using a proper queue.
+ Thanks Ole Laursen for noticing this.
+- Rewritten MakeWellFormed/FixNesting implementation eliminates quadratic
+ behavior in the rest of the purificaiton pipeline. Thanks Chedburn
+ Networks for sponsoring this work.
+- Made Linkify URL parser a bit less permissive, so that non-breaking
+ spaces and commas are not included as part of URL. Thanks nAS for fixing.
+- Fix some bad interactions with %HTML.Allowed and injectors. Thanks
+ David Hirtz for reporting.
+- Fix infinite loop in DirectLex. Thanks Ashar Javed (@soaj1664ashar)
+ for reporting.
+
+4.5.0, released 2013-02-17
+# Fix bug where stacked attribute transforms clobber each other;
+ this also means it's no longer possible to override attribute
+ transforms in later modules. No internal code was using this
+ but this may break some clients.
+# We now use SHA-1 to identify cached definitions, instead of MD5.
+! Support display:inline-block
+! Support for more white-space CSS values.
+! Permit underscores in font families
+! Support for page-break-* CSS3 properties when proprietary properties
+ are enabled.
+! New directive %Core.DisableExcludes; can be set to 'true' to turn off
+ SGML excludes checking. If HTML Purifier is removing too much text
+ and you don't care about full standards compliance, try setting this to
+ 'true'.
+- Use prepend for SPL autoloading on PHP 5.3 and later.
+- Fix bug with nofollow transform when pre-existing rel exists.
+- Fix bug where background:url() always gets lower-cased
+ (but not background-image:url())
+- Fix bug with non lower-case color names in HTML
+- Fix bug where data URI validation doesn't remove temporary files.
+ Thanks Javier Marín Ros <javiermarinros@gmail.com> for reporting.
+- Don't remove certain empty tags on RemoveEmpty.
+
+4.4.0, released 2012-01-18
+# Removed PEARSax3 handler.
+# URI.Munge now munges URIs inside the same host that go from https
+ to http. Reported by Neike Taika-Tessaro.
+# Core.EscapeNonASCIICharacters now always transforms entities to
+ entities, even if target encoding is UTF-8.
+# Tighten up selector validation in ExtractStyleBlocks.
+ Non-syntactically valid selectors are now rejected, along with
+ some of the more obscure ones such as attribute selectors, the
+ :lang pseudoselector, and anything not in CSS2.1. Furthermore,
+ ID and class selectors now work properly with the relevant
+ configuration attributes. Also, mute errors when parsing CSS
+ with CSS Tidy. Reported by Mario Heiderich and Norman Hippert.
+! Added support for 'scope' attribute on tables.
+! Added %HTML.TargetBlank, which adds target="blank" to all outgoing links.
+! Properly handle sub-lists directly nested inside of lists in
+ a standards compliant way, by moving them into the preceding <li>
+! Added %HTML.AllowedComments and %HTML.AllowedCommentsRegexp for
+ limited allowed comments in untrusted situations.
+! Implement iframes, and allow them to be used in untrusted mode with
+ %HTML.SafeIframe and %URI.SafeIframeRegexp. Thanks Bradley M. Froehle
+ <brad.froehle@gmail.com> for submitting an initial version of the patch.
+! The Forms module now works properly for transitional doctypes.
+! Added support for internationalized domain names. You need the PEAR
+ Net_IDNA2 module to be in your path; if it is installed, ensure the
+ class can be loaded and then set %Core.EnableIDNA to true.
+- Color keywords are now case insensitive. Thanks Yzmir Ramirez
+ <yramirez-htmlpurifier@adicio.com> for reporting.
+- Explicitly initialize anonModule variable to null.
+- Do not duplicate nofollow if already present. Thanks 178
+ for reporting.
+- Do not add nofollow if hostname matches our current host. Thanks 178
+ for reporting, and Neike Taika-Tessaro for helping diagnose.
+- Do not unset parser variable; this fixes intermittent serialization
+ problems. Thanks Neike Taika-Tessaro for reporting, bill
+ <10010tiger@gmail.com> for diagnosing.
+- Fix iconv truncation bug, where non-UTF-8 target encodings see
+ output truncated after around 8000 characters. Thanks Jörg Ludwig
+ <joerg.ludwig@iserv.eu> for reporting.
+- Fix broken table content model for XHTML1.1 (and also earlier
+ versions, although the W3C validator doesn't catch those violations).
+ Thanks GlitchMr <glitch.mr@gmail.com> for reporting.
+
+4.3.0, released 2011-03-27
+# Fixed broken caching of customized raw definitions, but requires an
+ API change. The old API still works but will emit a warning,
+ see http://htmlpurifier.org/docs/enduser-customize.html#optimized
+ for how to upgrade your code.
+# Protect against Internet Explorer innerHTML behavior by specially
+ treating attributes with backticks but no angled brackets, quotes or
+ spaces. This constitutes a slight semantic change, which can be
+ reverted using %Output.FixInnerHTML. Reported by Neike Taika-Tessaro
+ and Mario Heiderich.
+# Protect against cssText/innerHTML by restricting allowed characters
+ used in fonts further than mandated by the specification and encoding
+ some extra special characters in URLs. Reported by Neike
+ Taika-Tessaro and Mario Heiderich.
+! Added %HTML.Nofollow to add rel="nofollow" to external links.
+! More types of SPL autoloaders allowed on later versions of PHP.
+! Implementations for position, top, left, right, bottom, z-index
+ when %CSS.Trusted is on.
+! Add %Cache.SerializerPermissions option for custom serializer
+ directory/file permissions
+! Fix longstanding bug in Flash support for non-IE browsers, and
+ allow more wmode attributes.
+! Add %CSS.AllowedFonts to restrict permissible font names.
+- Switch to an iterative traversal of the DOM, which prevents us
+ from running out of stack space for deeply nested documents.
+ Thanks Maxim Krizhanovsky for contributing a patch.
+- Make removal of conditional IE comments ungreedy; thanks Bernd
+ for reporting.
+- Escape CDATA before removing Internet Explorer comments.
+- Fix removal of id attributes under certain conditions by ensuring
+ armor attributes are preserved when recreating tags.
+- Check if schema.ser was corrupted.
+- Check if zend.ze1_compatibility_mode is on, and error out if it is.
+ This safety check is only done for HTMLPurifier.auto.php; if you
+ are using standalone or the specialized includes files, you're
+ expected to know what you're doing.
+- Stop repeatedly writing the cache file after I'm done customizing a
+ raw definition. Reported by ajh.
+- Switch to using require_once in the Bootstrap to work around bad
+ interaction with Zend Debugger and APC. Reported by Antonio Parraga.
+- Fix URI handling when hostname is missing but scheme is present.
+ Reported by Neike Taika-Tessaro.
+- Fix missing numeric entities on DirectLex; thanks Neike Taika-Tessaro
+ for reporting.
+- Fix harmless notice from indexing into empty string. Thanks Matthijs
+ Kooijman <matthijs@stdin.nl> for reporting.
+- Don't autoclose no parent elements are able to support the element
+ that triggered the autoclose. In particular fixes strange behavior
+ of stray <li> tags. Thanks pkuliga@gmail.com for reporting and
+ Neike Taika-Tessaro <pinkgothic@gmail.com> for debugging assistance.
+
+4.2.0, released 2010-09-15
+! Added %Core.RemoveProcessingInstructions, which lets you remove
+ <? ... ?> statements.
+! Added %URI.DisableResources functionality; the directive originally
+ did nothing. Thanks David Rothstein for reporting.
+! Add documentation about configuration directive types.
+! Add %CSS.ForbiddenProperties configuration directive.
+! Add %HTML.FlashAllowFullScreen to permit embedded Flash objects
+ to utilize full-screen mode.
+! Add optional support for the <code>file</code> URI scheme, enable
+ by explicitly setting %URI.AllowedSchemes.
+! Add %Core.NormalizeNewlines options to allow turning off newline
+ normalization.
+- Fix improper handling of Internet Explorer conditional comments
+ by parser. Thanks zmonteca for reporting.
+- Fix missing attributes bug when running on Mac Snow Leopard and APC.
+ Thanks sidepodcast for the fix.
+- Warn if an element is allowed, but an attribute it requires is
+ not allowed.
+
+4.1.1, released 2010-05-31
+- Fix undefined index warnings in maintenance scripts.
+- Fix bug in DirectLex for parsing elements with a single attribute
+ with entities.
+- Rewrite CSS output logic for font-family and url(). Thanks Mario
+ Heiderich <mario.heiderich@googlemail.com> for reporting and Takeshi
+ Terada <t-terada@violet.plala.or.jp> for suggesting the fix.
+- Emit an error for CollectErrors if a body is extracted
+- Fix bug where in background-position for center keyword handling.
+- Fix infinite loop when a wrapper element is inserted in a context
+ where it's not allowed. Thanks Lars <lars@renoz.dk> for reporting.
+- Remove +x bit and shebang from index.php; only supported mode is to
+ explicitly call it with php.
+- Make test script less chatty when log_errors is on.
+
+4.1.0, released 2010-04-26
+! Support proprietary height attribute on table element
+! Support YouTube slideshows that contain /cp/ in their URL.
+! Support for data: URI scheme; not enabled by default, add it using
+ %URI.AllowedSchemes
+! Support flashvars when using %HTML.SafeObject and %HTML.SafeEmbed.
+! Support for Internet Explorer compatibility with %HTML.SafeObject
+ using %Output.FlashCompat.
+! Handle <ol><ol> properly, by inserting the necessary <li> tag.
+- Always quote the insides of url(...) in CSS.
+
+4.0.0, released 2009-07-07
+# APIs for ConfigSchema subsystem have substantially changed. See
+ docs/dev-config-bcbreaks.txt for details; in essence, anything that
+ had both namespace and directive now have a single unified key.
+# Some configuration directives were renamed, specifically:
+ %AutoFormatParam.PurifierLinkifyDocURL -> %AutoFormat.PurifierLinkify.DocURL
+ %FilterParam.ExtractStyleBlocksEscaping -> %Filter.ExtractStyleBlocks.Escaping
+ %FilterParam.ExtractStyleBlocksScope -> %Filter.ExtractStyleBlocks.Scope
+ %FilterParam.ExtractStyleBlocksTidyImpl -> %Filter.ExtractStyleBlocks.TidyImpl
+ As usual, the old directive names will still work, but will throw E_NOTICE
+ errors.
+# The allowed values for class have been relaxed to allow all of CDATA for
+ doctypes that are not XHTML 1.1 or XHTML 2.0. For old behavior, set
+ %Attr.ClassUseCDATA to false.
+# Instead of appending the content model to an old content model, a blank
+ element will replace the old content model. You can use #SUPER to get
+ the old content model.
+! More robust support for name="" and id=""
+! HTMLPurifier_Config::inherit($config) allows you to inherit one
+ configuration, and have changes to that configuration be propagated
+ to all of its children.
+! Implement %HTML.Attr.Name.UseCDATA, which relaxes validation rules on
+ the name attribute when set. Use with care. Thanks Ian Cook for
+ sponsoring.
+! Implement %AutoFormat.RemoveEmpty.RemoveNbsp, which removes empty
+ tags that contain non-breaking spaces as well other whitespace. You
+ can also modify which tags should have &nbsp; maintained with
+ %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.
+! Implement %Attr.AllowedClasses, which allows administrators to restrict
+ classes users can use to a specified finite set of classes, and
+ %Attr.ForbiddenClasses, which is the logical inverse.
+! You can now maintain your own configuration schema directories by
+ creating a config-schema.php file or passing an extra argument. Check
+ docs/dev-config-schema.html for more details.
+! Added HTMLPurifier_Config->serialize() method, which lets you save away
+ your configuration in a compact serial file, which you can unserialize
+ and use directly without having to go through the overhead of setup.
+- Fix bug where URIDefinition would not get cleared if it's directives got
+ changed.
+- Fix fatal error in HTMLPurifier_Encoder on certain platforms (probably NetBSD 5.0)
+- Fix bug in Linkify autoformatter involving <a><span>http://foo</span></a>
+- Make %URI.Munge not apply to links that have the same host as your host.
+- Prevent stray </body> tag from truncating output, if a second </body>
+ is present.
+. Created script maintenance/rename-config.php for renaming a configuration
+ directive while maintaining its alias. This script does not change source code.
+. Implement namespace locking for definition construction, to prevent
+ bugs where a directive is used for definition construction but is not
+ used to construct the cache hash.
+
+3.3.0, released 2009-02-16
+! Implement CSS property 'overflow' when %CSS.AllowTricky is true.
+! Implement generic property list classess
+- Fix bug with testEncodingSupportsASCII() algorithm when iconv() implementation
+ does not do the "right thing" with characters not supported in the output
+ set.
+- Spellcheck UTF-8: The Secret To Character Encoding
+- Fix improper removal of the contents of elements with only whitespace. Thanks
+ Eric Wald for reporting.
+- Fix broken test suite in versions of PHP without spl_autoload_register()
+- Fix degenerate case with YouTube filter involving double hyphens.
+ Thanks Pierre Attar for reporting.
+- Fix YouTube rendering problem on certain versions of Firefox.
+- Fix CSSDefinition Printer problems with decorators
+- Add text parameter to unit tests, forces text output
+. Add verbose mode to command line test runner, use (--verbose)
+. Turn on unit tests for UnitConverter
+. Fix missing version number in configuration %Attr.DefaultImageAlt (added 3.2.0)
+. Fix newline errors that caused spurious failures when CRLF HTML Purifier was
+ tested on Linux.
+. Removed trailing whitespace from all text files, see
+ remote-trailing-whitespace.php maintenance script.
+. Convert configuration to use property list backend.
+
+3.2.0, released 2008-10-31
+# Using %Core.CollectErrors forces line number/column tracking on, whereas
+ previously you could theoretically turn it off.
+# HTMLPurifier_Injector->notifyEnd() is formally deprecated. Please
+ use handleEnd() instead.
+! %Output.AttrSort for when you need your attributes in alphabetical order to
+ deal with a bug in FCKEditor. Requested by frank farmer.
+! Enable HTML comments when %HTML.Trusted is on. Requested by Waldo Jaquith.
+! Proper support for name attribute. It is now allowed and equivalent to the id
+ attribute in a and img tags, and is only converted to id when %HTML.TidyLevel
+ is heavy (for all doctypes).
+! %AutoFormat.RemoveEmpty to remove some empty tags from documents. Please don't
+ use on hand-written HTML.
+! Add error-cases for unsupported elements in MakeWellFormed. This enables
+ the strategy to be used, standalone, on untrusted input.
+! %Core.AggressivelyFixLt is on by default. This causes more sensible
+ processing of left angled brackets in smileys and other whatnot.
+! Test scripts now have a 'type' parameter, which lets you say 'htmlpurifier',
+ 'phpt', 'vtest', etc. in order to only execute those tests. This supercedes
+ the --only-phpt parameter, although for backwards-compatibility the flag
+ will still work.
+! AutoParagraph auto-formatter will now preserve double-newlines upon output.
+ Users who are not performing inbound filtering, this may seem a little
+ useless, but as a bonus, the test suite and handling of edge cases is also
+ improved.
+! Experimental implementation of forms for %HTML.Trusted
+! Track column numbers when maintain line numbers is on
+! Proprietary 'background' attribute on table-related elements converted into
+ corresponding CSS. Thanks Fusemail for sponsoring this feature!
+! Add forward(), forwardUntilEndToken(), backward() and current() to Injector
+ supertype.
+! HTMLPurifier_Injector->handleEnd() permits modification to end tokens. The
+ time of operation varies slightly from notifyEnd() as *all* end tokens are
+ processed by the injector before they are subject to the well-formedness rules.
+! %Attr.DefaultImageAlt allows overriding default behavior of setting alt to
+ basename of image when not present.
+! %AutoFormat.DisplayLinkURI neuters <a> tags into plain text URLs.
+- Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs,
+ the other involving an undefined $is_folder error.
+- Throw error when %Core.Encoding is set to a spurious value. Previously,
+ this errored silently and returned false.
+- Redirected stderr to stdout for flush error output.
+- %URI.DisableExternal will now use the host in %URI.Base if %URI.Host is not
+ available.
+- Do not re-munge URL if the output URL has the same host as the input URL.
+ Requested by Chris.
+- Fix error in documentation regarding %Filter.ExtractStyleBlocks
+- Prevent <![CDATA[<body></body>]]> from triggering %Core.ConvertDocumentToFragment
+- Fix bug with inline elements in blockquotes conflicting with strict doctype
+- Detect if HTML support is disabled for DOM by checking for loadHTML() method.
+- Fix bug where dots and double-dots in absolute URLs without hostname were
+ not collapsed by URIFilter_MakeAbsolute.
+- Fix bug with anonymous modules operating on SafeEmbed or SafeObject elements
+ by reordering their addition.
+- Will now throw exception on many error conditions during lexer creation; also
+ throw an exception when MaintainLineNumbers is true, but a non-tracksLineNumbers
+ is being used.
+- Detect if domxml extension is loaded, and use DirectLEx accordingly.
+- Improve handling of big numbers with floating point arithmetic in UnitConverter.
+ Reported by David Morton.
+. Strategy_MakeWellFormed now operates in-place, saving memory and allowing
+ for more interesting filter-backtracking
+. New HTMLPurifier_Injector->rewind() functionality, allows injectors to rewind
+ index to reprocess tokens.
+. StringHashParser now allows for multiline sections with "empty" content;
+ previously the section would remain undefined.
+. Added --quick option to multitest.php, which tests only the most recent
+ release for each series.
+. Added --distro option to multitest.php, which accepts either 'normal' or
+ 'standalone'. This supercedes --exclude-normal and --exclude-standalone
+
+3.1.1, released 2008-06-19
+# %URI.Munge now, by default, does not munge resources (for example, <img src="">)
+ In order to enable this again, please set %URI.MungeResources to true.
+! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength,
+ and height/width HTML with %HTML.MaxImgLength.
+! %URI.MungeSecretKey for secure URI munging. Thanks Chris
+ for sponsoring this feature. Check out the corresponding documentation
+ for details. (Att Nightly testers: The API for this feature changed before
+ the general release. Namely, rename your directives %URI.SecureMungeSecretKey =>
+ %URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge)
+! Implemented post URI filtering. Set member variable $post to true to set
+ a URIFilter as such.
+! Allow modules to define injectors via $info_injector. Injectors are
+ automatically disabled if injector's needed elements are not found.
+! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed.
+ Thanks Chris for sponsoring. If you've been using ad hoc code from the
+ forums, PLEASE use this instead.
+! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order,
+ embedded, tag name, attribute name, CSS property name). See %URI.Munge
+ for more details. Requested by Jochem Blok.
+- Disable percent height/width attributes for img.
+- AttrValidator operations are now atomic; updates to attributes are not
+ manifest in token until end of operations. This prevents naughty internal
+ code from directly modifying CurrentToken when they're not supposed to.
+ This semantics change was requested by frank farmer.
+- Percent encoding checks enabled for URI query and fragment
+- Fix stray backslashes in font-family; CSS Unicode character escapes are
+ now properly resolved (although *only* in font-family). Thanks Takeshi Terada
+ for reporting.
+- Improve parseCDATA algorithm to take into account newline normalization
+- Account for browser confusion between Yen character and backslash in
+ Shift_JIS encoding. This fix generalizes to any other encoding which is not
+ a strict superset of printable ASCII. Thanks Takeshi Terada for reporting.
+- Fix missing configuration parameter in Generator calls. Thanks vs for the
+ partial patch.
+- Improved adherence to Unicode by checking for non-character codepoints.
+ Thanks Geoffrey Sneddon for reporting. This may result in degraded
+ performance for extremely large inputs.
+- Allow CSS property-value pair ''text-decoration: none''. Thanks Jochem Blok
+ for reporting.
+. Added HTMLPurifier_UnitConverter and HTMLPurifier_Length for convenient
+ handling of CSS-style lengths. HTMLPurifier_AttrDef_CSS_Length now uses
+ this class.
+. API of HTMLPurifier_AttrDef_CSS_Length changed from __construct($disable_negative)
+ to __construct($min, $max). __construct(true) is equivalent to
+ __construct('0').
+. Added HTMLPurifier_AttrDef_Switch class
+. Rename HTMLPurifier_HTMLModule_Tidy->construct() to setup() and bubble method
+ up inheritance hierarchy to HTMLPurifier_HTMLModule. All HTMLModules
+ get this called with the configuration object. All modules now
+ use this rather than __construct(), although legacy code using constructors
+ will still work--the new format, however, lets modules access the
+ configuration object for HTML namespace dependant tweaks.
+. AttrDef_HTML_Pixels now takes a single construction parameter, pixels.
+. ConfigSchema data-structure heavily optimized; on average it uses a third
+ the memory it did previously. The interface has changed accordingly,
+ consult changes to HTMLPurifier_Config for details.
+. Variable parsing types now are magic integers instead of strings
+. Added benchmark for ConfigSchema
+. HTMLPurifier_Generator requires $config and $context parameters. If you
+ don't know what they should be, use HTMLPurifier_Config::createDefault()
+ and new HTMLPurifier_Context().
+. Printers now properly distinguish between output configuration, and
+ target configuration. This is not applicable to scripts using
+ the Printers for HTML Purifier related tasks.
+. HTML/CSS Printers must be primed with prepareGenerator($gen_config), otherwise
+ fatal errors will ensue.
+. URIFilter->prepare can return false in order to abort loading of the filter
+. Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds
+ an external resource.
+. %URI.Munge functionality factored out into a post-filter class.
+. Added CurrentCSSProperty context variable during CSS validation
+
+3.1.0, released 2008-05-18
+# Unnecessary references to objects (vestiges of PHP4) removed from method
+ signatures. The following methods do not need references when assigning from
+ them and will result in E_STRICT errors if you try:
+ + HTMLPurifier_Config->get*Definition() [* = HTML, CSS]
+ + HTMLPurifier_ConfigSchema::instance()
+ + HTMLPurifier_DefinitionCacheFactory::instance()
+ + HTMLPurifier_DefinitionCacheFactory->create()
+ + HTMLPurifier_DoctypeRegistry->register()
+ + HTMLPurifier_DoctypeRegistry->get()
+ + HTMLPurifier_HTMLModule->addElement()
+ + HTMLPurifier_HTMLModule->addBlankElement()
+ + HTMLPurifier_LanguageFactory::instance()
+# Printer_ConfigForm's get*() functions were static-ified
+# %HTML.ForbiddenAttributes requires attribute declarations to be in the
+ form of tag@attr, NOT tag.attr (which will throw an error and won't do
+ anything). This is for forwards compatibility with XML; you'd do best
+ to migrate an %HTML.AllowedAttributes directives to this syntax too.
+! Allow index to be false for config from form creation
+! Added HTMLPurifier::VERSION constant
+! Commas, not dashes, used for serializer IDs. This change is forwards-compatible
+ and allows for version numbers like "3.1.0-dev".
+! %HTML.Allowed deals gracefully with whitespace anywhere, anytime!
+! HTML Purifier's URI handling is a lot more robust, with much stricter
+ validation checks and better percent encoding handling. Thanks Gareth Heyes
+ for indicating security vulnerabilities from lax percent encoding.
+! Bootstrap autoloader deals more robustly with classes that don't exist,
+ preventing class_exists($class, true) from barfing.
+- InterchangeBuilder now alphabetizes its lists
+- Validation error in configdoc output fixed
+- Iconv and other encoding errors muted even with custom error handlers that
+ do not honor error_reporting
+- Add protection against imagecrash attack with CSS height/width
+- HTMLPurifier::instance() created for consistency, is equivalent to getInstance()
+- Fixed and revamped broken ConfigForm smoketest
+- Bug with bool/null fields in Printer_ConfigForm fixed
+- Bug with global forbidden attributes fixed
+- Improved error messages for allowed and forbidden HTML elements and attributes
+- Missing (or null) in configdoc documentation restored
+- If DOM throws and exception during parsing with PH5P (occurs in newer versions
+ of DOM), HTML Purifier punts to DirectLex
+- Fatal error with unserialization of ScriptRequired
+- Created directories are now chmod'ed properly
+- Fixed bug with fallback languages in LanguageFactory
+- Standalone testing setup properly with autoload
+. Out-of-date documentation revised
+. UTF-8 encoding check optimization as suggested by Diego
+. HTMLPurifier_Error removed in favor of exceptions
+. More copy() function removed; should use clone instead
+. More extensive unit tests for HTMLDefinition
+. assertPurification moved to central harness
+. HTMLPurifier_Generator accepts $config and $context parameters during
+ instantiation, not runtime
+. Double-quotes outside of attribute values are now unescaped
+
+3.1.0rc1, released 2008-04-22
+# Autoload support added. Internal require_once's removed in favor of an
+ explicit require list or autoloading. To use HTML Purifier,
+ you must now either use HTMLPurifier.auto.php
+ or HTMLPurifier.includes.php; setting the include path and including
+ HTMLPurifier.php is insufficient--in such cases include HTMLPurifier.autoload.php
+ as well to register our autoload handler (or modify your autoload function
+ to check HTMLPurifier_Bootstrap::getPath($class)). You can also use
+ HTMLPurifier.safe-includes.php for a less performance friendly but more
+ user-friendly library load.
+# HTMLPurifier_ConfigSchema static functions are officially deprecated. Schema
+ information is stored in the ConfigSchema directory, and the
+ maintenance/generate-schema-cache.php generates the schema.ser file, which
+ is now instantiated. Support for userland schema changes coming soon!
+# HTMLPurifier_Config will now throw E_USER_NOTICE when you use a directive
+ alias; to get rid of these errors just modify your configuration to use
+ the new directive name.
+# HTMLPurifier->addFilter is deprecated; built-in filters can now be
+ enabled using %Filter.$filter_name or by setting your own filters using
+ %Filter.Custom
+# Directive-level safety properties superceded in favor of module-level
+ safety. Internal method HTMLModule->addElement() has changed, although
+ the externally visible HTMLDefinition->addElement has *not* changed.
+! Extra utility classes for testing and non-library operations can
+ be found in extras/. Specifically, these are FSTools and ConfigDoc.
+ You may find a use for these in your own project, but right now they
+ are highly experimental and volatile.
+! Integration with PHPT allows for automated smoketests
+! Limited support for proprietary HTML elements, namely <marquee>, sponsored
+ by Chris. You can enable them with %HTML.Proprietary if your client
+ demands them.
+! Support for !important CSS cascade modifier. By default, this will be stripped
+ from CSS, but you can enable it using %CSS.AllowImportant
+! Support for display and visibility CSS properties added, set %CSS.AllowTricky
+ to true to use them.
+! HTML Purifier now has its own Exception hierarchy under HTMLPurifier_Exception.
+ Developer error (not enduser error) can cause these to be triggered.
+! Experimental kses() wrapper introduced with HTMLPurifier.kses.php
+! Finally %CSS.AllowedProperties for tweaking allowed CSS properties without
+ mucking around with HTMLPurifier_CSSDefinition
+! ConfigDoc output has been enhanced with version and deprecation info.
+! %HTML.ForbiddenAttributes and %HTML.ForbiddenElements implemented.
+- Autoclose now operates iteratively, i.e. <span><span><div> now has
+ both span tags closed.
+- Various HTMLPurifier_Config convenience functions now accept another parameter
+ $schema which defines what HTMLPurifier_ConfigSchema to use besides the
+ global default.
+- Fix bug with trusted script handling in libxml versions later than 2.6.28.
+- Fix bug in ExtractStyleBlocks with comments in style tags
+- Fix bug in comment parsing for DirectLex
+- Flush output now displayed when in command line mode for unit tester
+- Fix bug with rgb(0, 1, 2) color syntax with spaces inside shorthand syntax
+- HTMLPurifier_HTMLDefinition->addAttribute can now be called multiple times
+ on the same element without emitting errors.
+- Fixed fatal error in PH5P lexer with invalid tag names
+. Plugins now get their own changelogs according to project conventions.
+. Convert tokens to use instanceof, reducing memory footprint and
+ improving comparison speed.
+. Dry runs now supported in SimpleTest; testing facilities improved
+. Bootstrap class added for handling autoloading functionality
+. Implemented recursive glob at FSTools->globr
+. ConfigSchema now has instance methods for all corresponding define*
+ static methods.
+. A couple of new historical maintenance scripts were added.
+. HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php split into two files
+. tests/index.php can now be run from any directory.
+. HTMLPurifier_Token subclasses split into seperate files
+. HTMLPURIFIER_PREFIX now is defined in Bootstrap.php, NOT HTMLPurifier.php
+. HTMLPURIFIER_PREFIX can now be defined outside of HTML Purifier
+. New --php=php flag added, allows PHP executable to be specified (command
+ line only!)
+. htmlpurifier_add_test() preferred method to translate test files in to
+ classes, because it handles PHPT files too.
+. Debugger class is deprecated and will be removed soon.
+. Command line argument parsing for testing scripts revamped, now --opt value
+ format is supported.
+. Smoketests now cleanup after magic quotes
+. Generator now can output comments (however, comments are still stripped
+ from HTML Purifier output)
+. HTMLPurifier_ConfigSchema->validate() deprecated in favor of
+ HTMLPurifier_VarParser->parse()
+. Integers auto-cast into float type by VarParser.
+. HTMLPURIFIER_STRICT removed; no validation is performed on runtime, only
+ during cache generation
+. Reordered script calls in maintenance/flush.php
+. Command line scripts now honor exit codes
+. When --flush fails in unit testers, abort tests and print message
+. Improved documentation in docs/dev-flush.html about the maintenance scripts
+. copy() methods removed in favor of clone keyword
+
+3.0.0, released 2008-01-06
+# HTML Purifier is PHP 5 only! The 2.1.x branch will be maintained
+ until PHP 4 is completely deprecated, but no new features will be added
+ to it.
+ + Visibility declarations added
+ + Constructor methods renamed to __construct()
+ + PHP4 reference cruft removed (in progress)
+! CSS properties are now case-insensitive
+! DefinitionCacheFactory now can register new implementations
+! New HTMLPurifier_Filter_ExtractStyleBlocks for extracting <style> from
+ documents and cleaning their contents up. Requires the CSSTidy library
+ <http://csstidy.sourceforge.net/>. You can access the blocks with the
+ 'StyleBlocks' Context variable ($purifier->context->get('StyleBlocks')).
+ The output CSS can also be "scoped" for a specific element, use:
+ %Filter.ExtractStyleBlocksScope
+! Experimental support for some proprietary CSS attributes allowed:
+ opacity (and all of the browser-specific equivalents) and scrollbar colors.
+ Enable by setting %CSS.Proprietary to true.
+- Colors missing # but in hex form will be corrected
+- CSS Number algorithm improved
+- Unit testing and multi-testing now on steroids: command lines,
+ XML output, and other goodies now added.
+. Unit tests for Injector improved
+. New classes:
+ + HTMLPurifier_AttrDef_CSS_AlphaValue
+ + HTMLPurifier_AttrDef_CSS_Filter
+. Multitest now has a file docblock
+
+2.1.3, released 2007-11-05
+! tests/multitest.php allows you to test multiple versions by running
+ tests/index.php through multiple interpreters using `phpv` shell
+ script (you must provide this script!)
+- Fixed poor include ordering for Email URI AttrDefs, causes fatal errors
+ on some systems.
+- Injector algorithm further refined: off-by-one error regarding skip
+ counts for dormant injectors fixed
+- Corrective blockquote definition now enabled for HTML 4.01 Strict
+- Fatal error when <img> tag (or any other element with required attributes)
+ has 'id' attribute fixed, thanks NykO18 for reporting
+- Fix warning emitted when a non-supported URI scheme is passed to the
+ MakeAbsolute URIFilter, thanks NykO18 (again)
+- Further refine AutoParagraph injector. Behavior inside of elements
+ allowing paragraph tags clarified: only inline content delimeted by
+ double newlines (not block elements) are paragraphed.
+- Buggy treatment of end tags of elements that have required attributes
+ fixed (does not manifest on default tag-set)
+- Spurious internal content reorganization error suppressed
+- HTMLDefinition->addElement now returns a reference to the created
+ element object, as implied by the documentation
+- Phorum mod's HTML Purifier help message expanded (unreleased elsewhere)
+- Fix a theoretical class of infinite loops from DirectLex reported
+ by Nate Abele
+- Work around unnecessary DOMElement type-cast in PH5P that caused errors
+ in PHP 5.1
+- Work around PHP 4 SimpleTest lack-of-error complaining for one-time-only
+ HTMLDefinition errors, this may indicate problems with error-collecting
+ facilities in PHP 5
+- Make ErrorCollectorEMock work in both PHP 4 and PHP 5
+- Make PH5P work with PHP 5.0 by removing unnecessary array parameter typedef
+. %Core.AcceptFullDocuments renamed to %Core.ConvertDocumentToFragment
+ to better communicate its purpose
+. Error unit tests can now specify the expectation of no errors. Future
+ iterations of the harness will be extremely strict about what errors
+ are allowed
+. Extend Injector hooks to allow for more powerful injector routines
+. HTMLDefinition->addBlankElement created, as according to the HTMLModule
+ method
+. Doxygen configuration file updated, with minor improvements
+. Test runner now checks for similarly named files in conf/ directory too.
+. Minor cosmetic change to flush-definition-cache.php: trailing newline is
+ outputted
+. Maintenance script for generating PH5P patch added, original PH5P source
+ file also added under version control
+. Full unit test runner script title made more descriptive with PHP version
+. Updated INSTALL file to state that 4.3.7 is the earliest version we
+ are actively testing
+
+2.1.2, released 2007-09-03
+! Implemented Object module for trusted users
+! Implemented experimental HTML5 parsing mode using PH5P. To use, add
+ this to your code:
+ require_once 'HTMLPurifier/Lexer/PH5P.php';
+ $config->set('Core', 'LexerImpl', 'PH5P');
+ Note that this Lexer introduces some classes not in the HTMLPurifier
+ namespace. Also, this is PHP5 only.
+! CSS property border-spacing implemented
+- Fix non-visible parsing error in DirectLex with empty tags that have
+ slashes inside attribute values.
+- Fix typo in CSS definition: border-collapse:seperate; was incorrectly
+ accepted as valid CSS. Usually non-visible, because this styling is the
+ default for tables in most browsers. Thanks Brett Zamir for pointing
+ this out.
+- Fix validation errors in configuration form
+- Hammer out a bunch of edge-case bugs in the standalone distribution
+- Inclusion reflection removed from URISchemeRegistry; you must manually
+ include any new schema files you wish to use
+- Numerous typo fixes in documentation thanks to Brett Zamir
+. Unit test refactoring for one logical test per test function
+. Config and context parameters in ComplexHarness deprecated: instead, edit
+ the $config and $context member variables
+. HTML wrapper in DOMLex now takes DTD identifiers into account; doesn't
+ really make a difference, but is good for completeness sake
+. merge-library.php script refactored for greater code reusability and
+ PHP4 compatibility
+
+2.1.1, released 2007-08-04
+- Fix show-stopper bug in %URI.MakeAbsolute functionality
+- Fix PHP4 syntax error in standalone version
+. Add prefix directory to include path for standalone, this prevents
+ other installations from clobbering the standalone's URI schemes
+. Single test methods can be invoked by prefixing with __only
+
+2.1.0, released 2007-08-02
+# flush-htmldefinition-cache.php superseded in favor of a generic
+ flush-definition-cache.php script, you can clear a specific cache
+ by passing its name as a parameter to the script
+! Phorum mod implemented for HTML Purifier
+! With %Core.AggressivelyFixLt, <3 and similar emoticons no longer
+ trigger HTML removal in PHP5 (DOMLex). This directive is not necessary
+ for PHP4 (DirectLex).
+! Standalone file now available, which greatly reduces the amount of
+ includes (although there are still a few files that reside in the
+ standalone folder)
+! Relative URIs can now be transformed into their absolute equivalents
+ using %URI.Base and %URI.MakeAbsolute
+! Ruby implemented for XHTML 1.1
+! You can now define custom URI filtering behavior, see enduser-uri-filter.html
+ for more details
+! UTF-8 font names now supported in CSS
+- AutoFormatters emit friendly error messages if tags or attributes they
+ need are not allowed
+- ConfigForm's compactification of directive names is now configurable
+- AutoParagraph autoformatter algorithm refined after field-testing
+- XHTML 1.1 now applies XHTML 1.0 Strict cleanup routines, namely
+ blockquote wrapping
+- Contents of <style> tags removed by default when tags are removed
+. HTMLPurifier_Config->getSerial() implemented, this is extremely useful
+ for output cache invalidation
+. ConfigForm printer now can retrieve CSS and JS files as strings, in
+ case HTML Purifier's directory is not publically accessible
+. Introduce new text/itext configuration directive values: these represent
+ longer strings that would be more appropriately edited with a textarea
+. Allow newlines to act as separators for lists, hashes, lookups and
+ %HTML.Allowed
+. ConfigForm generates textareas instead of text inputs for lists, hashes,
+ lookups, text and itext fields
+. Hidden element content removal genericized: %Core.HiddenElements can
+ be used to customize this behavior, by default <script> and <style> are
+ hidden
+. Added HTMLPURIFIER_PREFIX constant, should be used instead of dirname(__FILE__)
+. Custom ChildDef added to default include list
+. URIScheme reflection improved: will not attempt to include file if class
+ already exists. May clobber autoload, so I need to keep an eye on it
+. ConfigSchema heavily optimized, will only collect information and validate
+ definitions when HTMLPURIFIER_SCHEMA_STRICT is true.
+. AttrDef_URI unit tests and implementation refactored
+. benchmarks/ directory now protected from public view with .htaccess file;
+ run the tests via command line
+. URI scheme is munged off if there is no authority and the scheme is the
+ default one
+. All unit tests inherit from HTMLPurifier_Harness, not UnitTestCase
+. Interface for URIScheme changed
+. Generic URI object to hold components of URI added, most systems involved
+ in URI validation have been migrated to use it
+. Custom filtering for URIs factored out to URIDefinition interface for
+ maximum extensibility
+
+2.0.1, released 2007-06-27
+! Tag auto-closing now based on a ChildDef heuristic rather than a
+ manually set auto_close array; some behavior may change
+! Experimental AutoFormat functionality added: auto-paragraph and
+ linkify your HTML input by setting %AutoFormat.AutoParagraph and
+ %AutoFormat.Linkify to true
+! Newlines normalized internally, and then converted back to the
+ value of PHP_EOL. If this is not desired, set your newline format
+ using %Output.Newline.
+! Beta error collection, messages are implemented for the most generic
+ cases involving Lexing or Strategies
+- Clean up special case code for <script> tags
+- Reorder includes for DefinitionCache decorators, fixes a possible
+ missing class error
+- Fixed bug where manually modified definitions were not saved via cache
+ (mostly harmless, except for the fact that it would be a little slower)
+- Configuration objects with different serials do not clobber each
+ others when revision numbers are unequal
+- Improve Serializer DefinitionCache directory permissions checks
+- DefinitionCache no longer throws errors when it encounters old
+ serial files that do not conform to the current style
+- Stray xmlns attributes removed from configuration documentation
+- configForm.php smoketest no longer has XSS vulnerability due to
+ unescaped print_r output
+- Printer adheres to configuration's directives on output format
+- Fix improperly named form field in ConfigForm printer
+. Rewire some test-cases to swallow errors rather than expect them
+. HTMLDefinition printer updated with some of the new attributes
+. DefinitionCache keys reordered to reflect precedence: version number,
+ hash, then revision number
+. %Core.DefinitionCache renamed to %Cache.DefinitionImpl
+. Interlinking in configuration documentation added using
+ Injector_PurifierLinkify
+. Directives now keep track of aliases to themselves
+. Error collector now requires a severity to be passed, use PHP's internal
+ error constants for this
+. HTMLPurifier_Config::getAllowedDirectivesForForm implemented, allows
+ much easier selective embedding of configuration values
+. Doctype objects now accept public and system DTD identifiers
+. %HTML.Doctype is now constrained by specific values, to specify a custom
+ doctype use new %HTML.CustomDoctype
+. ConfigForm truncates long directives to keep the form small, and does
+ not re-output namespaces
+
+2.0.0, released 2007-06-20
+# Completely refactored HTMLModuleManager, decentralizing safety
+ information
+# Transform modules changed to Tidy modules, which offer more flexibility
+ and better modularization
+# Configuration object now finalizes itself when a read operation is
+ performed on it, ensuring that its internal state stays consistent.
+ To revert this behavior, you can set the $autoFinalize member variable
+ off, but it's not recommended.
+# New compact syntax for AttrDef objects that can be used to instantiate
+ new objects via make()
+# Definitions (esp. HTMLDefinition) are now cached for a significant
+ performance boost. You can disable caching by setting %Core.DefinitionCache
+ to null. You CANNOT edit raw definitions without setting the corresponding
+ DefinitionID directive (%HTML.DefinitionID for HTMLDefinition).
+# Contents between <script> tags are now completely removed if <script>
+ is not allowed
+# Prototype-declarations for Lexer removed in favor of configuration
+ determination of Lexer implementations.
+! HTML Purifier now works in PHP 4.3.2.
+! Configuration form-editing API makes tweaking HTMLPurifier_Config a
+ breeze!
+! Configuration directives that accept hashes now allow new string
+ format: key1:value1,key2:value2
+! ConfigDoc now factored into OOP design
+! All deprecated elements now natively supported
+! Implement TinyMCE styled whitelist specification format in
+ %HTML.Allowed
+! Config object gives more friendly error messages when things go wrong
+! Advanced API implemented: easy functions for creating elements (addElement)
+ and attributes (addAttribute) on HTMLDefinition
+! Add native support for required attributes
+- Deprecated and removed EnableRedundantUTF8Cleaning. It didn't even work!
+- DOMLex will not emit errors when a custom error handler that does not
+ honor error_reporting is used
+- StrictBlockquote child definition refrains from wrapping whitespace
+ in tags now.
+- Bug resulting from tag transforms to non-allowed elements fixed
+- ChildDef_Custom's regex generation has been improved, removing several
+ false positives
+. Unit test for ElementDef created, ElementDef behavior modified to
+ be more flexible
+. Added convenience functions for HTMLModule constructors
+. AttrTypes now has accessor functions that should be used instead
+ of directly manipulating info
+. TagTransform_Center deprecated in favor of generic TagTransform_Simple
+. Add extra protection in AttrDef_URI against phantom Schemes
+. Doctype object added to HTMLDefinition which describes certain aspects
+ of the operational document type
+. Lexer is now pre-emptively included, with a conditional include for the
+ PHP5 only version.
+. HTMLDefinition and CSSDefinition have a common parent class: Definition.
+. DirectLex can now track line-numbers
+. Preliminary error collector is in place, although no code actually reports
+ errors yet
+. Factor out most of ValidateAttributes to new AttrValidator class
+
+1.6.1, released 2007-05-05
+! Support for more deprecated attributes via transformations:
+ + hspace and vspace in img
+ + size and noshade in hr
+ + nowrap in td
+ + clear in br
+ + align in caption, table, img and hr
+ + type in ul, ol and li
+! DirectLex now preserves text in which a < bracket is followed by
+ a non-alphanumeric character. This means that certain emoticons
+ are now preserved.
+! %Core.RemoveInvalidImg is now operational, when set to false invalid
+ images will hang around with an empty src
+! target attribute in a tag supported, use %Attr.AllowedFrameTargets
+ to enable
+! CSS property white-space now allows nowrap (supported in all modern
+ browsers) but not others (which have spotty browser implementations)
+! XHTML 1.1 mode now sort-of works without any fatal errors, and
+ lang is now moved over to xml:lang.
+! Attribute transformation smoketest available at smoketests/attrTransform.php
+! Transformation of font's size attribute now handles super-large numbers
+- Possibly fatal bug with __autoload() fixed in module manager
+- Invert HTMLModuleManager->addModule() processing order to check
+ prefixes first and then the literal module
+- Empty strings get converted to empty arrays instead of arrays with
+ an empty string in them.
+- Merging in attribute lists now works.
+. Demo script removed: it has been added to the website's repository
+. Basic.php script modified to work out of the box
+. Refactor AttrTransform classes to reduce duplication
+. AttrTransform_TextAlign axed in favor of a more general
+ AttrTransform_EnumToCSS, refer to HTMLModule/TransformToStrict.php to
+ see how the new equivalent is implemented
+. Unit tests now use exclusively assertIdentical
+
+1.6.0, released 2007-04-01
+! Support for most common deprecated attributes via transformations:
+ + bgcolor in td, th, tr and table
+ + border in img
+ + name in a and img
+ + width in td, th and hr
+ + height in td, th
+! Support for CSS attribute 'height' added
+! Support for rel and rev attributes in a tags added, use %Attr.AllowedRel
+ and %Attr.AllowedRev to activate
+- You can define ID blacklists using regular expressions via
+ %Attr.IDBlacklistRegexp
+- Error messages are emitted when you attempt to "allow" elements or
+ attributes that HTML Purifier does not support
+- Fix segfault in unit test. The problem is not very reproduceable and
+ I don't know what causes it, but a six line patch fixed it.
+
+1.5.0, released 2007-03-23
+! Added a rudimentary I18N and L10N system modeled off MediaWiki. It
+ doesn't actually do anything yet, but keep your eyes peeled.
+! docs/enduser-utf8.html explains how to use UTF-8 and HTML Purifier
+! Newly structured HTMLDefinition modeled off of XHTML 1.1 modules.
+ I am loathe to release beta quality APIs, but this is exactly that;
+ don't use the internal interfaces if you're not willing to do migration
+ later on.
+- Allow 'x' subtag in language codes
+- Fixed buggy chameleon-support for ins and del
+. Added support for IDREF attributes (i.e. for)
+. Renamed HTMLPurifier_AttrDef_Class to HTMLPurifier_AttrDef_Nmtokens
+. Removed context variable ParentType, replaced with IsInline, which
+ is false when you're not inline and an integer of the parent that
+ caused you to become inline when you are (so possibly zero)
+. Removed ElementDef->type in favor of ElementDef->descendants_are_inline
+ and HTMLDefinition->content_sets
+. StrictBlockquote now reports what elements its supposed to allow,
+ rather than what it does allow
+. Removed HTMLDefinition->info_flow_elements in favor of
+ HTMLDefinition->content_sets['Flow']
+. Removed redundant "exclusionary" definitions from DTD roster
+. StrictBlockquote now requires a construction parameter as if it
+ were an Required ChildDef, this is the "real" set of allowed elements
+. AttrDef partitioned into HTML, CSS and URI segments
+. Modify Youtube filter regexp to be multiline
+. Require both PHP5 and DOM extension in order to use DOMLex, fixes
+ some edge cases where a DOMDocument class exists in a PHP4 environment
+ due to DOM XML extension.
+
+1.4.1, released 2007-01-21
+! docs/enduser-youtube.html updated according to new functionality
+- YouTube IDs can have underscores and dashes
+
+1.4.0, released 2007-01-21
+! Implemented list-style-image, URIs now allowed in list-style
+! Implemented background-image, background-repeat, background-attachment
+ and background-position CSS properties. Shorthand property background
+ supports all of these properties.
+! Configuration documentation looks nicer
+! Added %Core.EscapeNonASCIICharacters to workaround loss of Unicode
+ characters while %Core.Encoding is set to a non-UTF-8 encoding.
+! Support for configuration directive aliases added
+! Config object can now be instantiated from ini files
+! YouTube preservation code added to the core, with two lines of code
+ you can add it as a filter to your code. See smoketests/preserveYouTube.php
+ for sample code.
+! Moved SLOW to docs/enduser-slow.html and added code examples
+- Replaced version check with functionality check for DOM (thanks Stephen
+ Khoo)
+. Added smoketest 'all.php', which loads all other smoketests via frames
+. Implemented AttrDef_CSSURI for url(http://google.com) style declarations
+. Added convenient single test selector form on test runner
+
+1.3.2, released 2006-12-25
+! HTMLPurifier object now accepts configuration arrays, no need to manually
+ instantiate a configuration object
+! Context object now accessible to outside
+! Added enduser-youtube.html, explains how to embed YouTube videos. See
+ also corresponding smoketest preserveYouTube.php.
+! Added purifyArray(), which takes a list of HTML and purifies it all
+! Added static member variable $version to HTML Purifier with PHP-compatible
+ version number string.
+- Fixed fatal error thrown by upper-cased language attributes
+- printDefinition.php: added labels, added better clarification
+. HTMLPurifier_Config::create() added, takes mixed variable and converts into
+ a HTMLPurifier_Config object.
+
+1.3.1, released 2006-12-06
+! Added HTMLPurifier.func.php stub for a convenient function to call the library
+- Fixed bug in RemoveInvalidImg code that caused all images to be dropped
+ (thanks to .mario for reporting this)
+. Standardized all attribute handling variables to attr, made it plural
+
+1.3.0, released 2006-11-26
+# Invalid images are now removed, rather than replaced with a dud
+ <img src="" alt="Invalid image" />. Previous behavior can be restored
+ with new directive %Core.RemoveInvalidImg set to false.
+! (X)HTML Strict now supported
+ + Transparently handles inline elements in block context (blockquote)
+! Added GET method to demo for easier validation, added 50kb max input size
+! New directive %HTML.BlockWrapper, for block-ifying inline elements
+! New directive %HTML.Parent, allows you to only allow inline content
+! New directives %HTML.AllowedElements and %HTML.AllowedAttributes to let
+ users narrow the set of allowed tags
+! <li value="4"> and <ul start="2"> now allowed in loose mode
+! New directives %URI.DisableExternalResources and %URI.DisableResources
+! New directive %Attr.DisableURI, which eliminates all hyperlinking
+! New directive %URI.Munge, munges URI so you can use some sort of redirector
+ service to avoid PageRank leaks or warn users that they are exiting your site.
+! Added spiffy new smoketest printDefinition.php, which lets you twiddle with
+ the configuration settings and see how the internal rules are affected.
+! New directive %URI.HostBlacklist for blocking links to bad hosts.
+ xssAttacks.php smoketest updated accordingly.
+- Added missing type to ChildDef_Chameleon
+- Remove Tidy option from demo if there is not Tidy available
+. ChildDef_Required guards against empty tags
+. Lookup table HTMLDefinition->info_flow_elements added
+. Added peace-of-mind variable initialization to Strategy_FixNesting
+. Added HTMLPurifier->info_parent_def, parent child processing made special
+. Added internal documents briefly summarizing future progression of HTML
+. HTMLPurifier_Config->getBatch($namespace) added
+. More lenient casting to bool from string in HTMLPurifier_ConfigSchema
+. Refactored ChildDef classes into their own files
+
+1.2.0, released 2006-11-19
+# ID attributes now disabled by default. New directives:
+ + %HTML.EnableAttrID - restores old behavior by allowing IDs
+ + %Attr.IDPrefix - %Attr.IDBlacklist alternative that munges all user IDs
+ so that they don't collide with your IDs
+ + %Attr.IDPrefixLocal - Same as above, but for when there are multiple
+ instances of user content on the page
+ + Profuse documentation on how to use these available in docs/enduser-id.txt
+! Added MODx plugin <http://modxcms.com/forums/index.php/topic,6604.0.html>
+! Added percent encoding normalization
+! XSS attacks smoketest given facelift
+! Configuration documentation now has table of contents
+! Added %URI.DisableExternal, which prevents links to external websites. You
+ can also use %URI.Host to permit absolute linking to subdomains
+! Non-accessible resources (ex. mailto) blocked from embedded URIs (img src)
+- Type variable in HTMLDefinition was not being set properly, fixed
+- Documentation updated
+ + TODO added request Phalanger
+ + TODO added request Native compression
+ + TODO added request Remove redundant tags
+ + TODO added possible plaintext formatter for HTML Purifier documentation
+ + Updated ConfigDoc TODO
+ + Improved inline comments in AttrDef/Class.php, AttrDef/CSS.php
+ and AttrDef/Host.php
+ + Revamped documentation into HTML, along with misc updates
+- HTMLPurifier_Context doesn't throw a variable reference error if you attempt
+ to retrieve a non-existent variable
+. Switched to purify()-wide Context object registry
+. Refactored unit tests to minimize duplication
+. XSS attack sheet updated
+. configdoc.xml now has xml:space attached to default value nodes
+. Allow configuration directives to permit null values
+. Cleaned up test-cases to remove unnecessary swallowErrors()
+
+1.1.2, released 2006-09-30
+! Add HTMLPurifier.auto.php stub file that configures include_path
+- Documentation updated
+ + INSTALL document rewritten
+ + TODO added semi-lossy conversion
+ + API Doxygen docs' file exclusions updated
+ + Added notes on HTML versus XML attribute whitespace handling
+ + Noted that HTMLPurifier_ChildDef_Custom isn't being used
+ + Noted that config object's definitions are cached versions
+- Fixed lack of attribute parsing in HTMLPurifier_Lexer_PEARSax3
+- ftp:// URIs now have their typecodes checked
+- Hooked up HTMLPurifier_ChildDef_Custom's unit tests (they weren't being run)
+. Line endings standardized throughout project (svn:eol-style standardized)
+. Refactored parseData() to general Lexer class
+. Tester named "HTML Purifier" not "HTMLPurifier"
+
+1.1.1, released 2006-09-24
+! Configuration option to optionally Tidy up output for indentation to make up
+ for dropped whitespace by DOMLex (pretty-printing for the entire application
+ should be done by a page-wide Tidy)
+- Various documentation updates
+- Fixed parse error in configuration documentation script
+- Fixed fatal error in benchmark scripts, slightly augmented
+- As far as possible, whitespace is preserved in-between table children
+- Sample test-settings.php file included
+
+1.1.0, released 2006-09-16
+! Directive documentation generation using XSLT
+! XHTML can now be turned off, output becomes <br>
+- Made URI validator more forgiving: will ignore leading and trailing
+ quotes, apostrophes and less than or greater than signs.
+- Enforce alphanumeric namespace and directive names for configuration.
+- Table child definition made more flexible, will fix up poorly ordered elements
+. Renamed ConfigDef to ConfigSchema
+
+1.0.1, released 2006-09-04
+- Fixed slight bug in DOMLex attribute parsing
+- Fixed rejection of case-insensitive configuration values when there is a
+ set of allowed values. This manifested in %Core.Encoding.
+- Fixed rejection of inline style declarations that had lots of extra
+ space in them. This manifested in TinyMCE.
+
+1.0.0, released 2006-09-01
+! Shorthand CSS properties implemented: font, border, background, list-style
+! Basic color keywords translated into hexadecimal values
+! Table CSS properties implemented
+! Support for charsets other than UTF-8 (defined by iconv)
+! Malformed UTF-8 and non-SGML character detection and cleaning implemented
+- Fixed broken numeric entity conversion
+- API documentation completed
+. (HTML|CSS)Definition de-singleton-ized
+
+1.0.0beta, released 2006-08-16
+! First public release, most functionality implemented. Notable omissions are:
+ + Shorthand CSS properties
+ + Table CSS properties
+ + Deprecated attribute transformations
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/README.md b/vendor/ezyang/htmlpurifier/README.md
new file mode 100644
index 0000000..37715c6
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/README.md
@@ -0,0 +1,29 @@
+HTML Purifier [![Build Status](https://secure.travis-ci.org/ezyang/htmlpurifier.svg?branch=master)](http://travis-ci.org/ezyang/htmlpurifier)
+=============
+
+HTML Purifier is an HTML filtering solution that uses a unique combination
+of robust whitelists and aggressive parsing to ensure that not only are
+XSS attacks thwarted, but the resulting HTML is standards compliant.
+
+HTML Purifier is oriented towards richly formatted documents from
+untrusted sources that require CSS and a full tag-set. This library can
+be configured to accept a more restrictive set of tags, but it won't be
+as efficient as more bare-bones parsers. It will, however, do the job
+right, which may be more important.
+
+Places to go:
+
+* See INSTALL for a quick installation guide
+* See docs/ for developer-oriented documentation, code examples and
+ an in-depth installation guide.
+* See WYSIWYG for information on editors like TinyMCE and FCKeditor
+
+HTML Purifier can be found on the web at: [http://htmlpurifier.org/](http://htmlpurifier.org/)
+
+## Installation
+
+Package available on [Composer](https://packagist.org/packages/ezyang/htmlpurifier).
+
+If you're using Composer to manage dependencies, you can use
+
+ $ composer require "ezyang/htmlpurifier":"dev-master"
diff --git a/vendor/ezyang/htmlpurifier/TODO b/vendor/ezyang/htmlpurifier/TODO
new file mode 100644
index 0000000..1afb33c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/TODO
@@ -0,0 +1,150 @@
+
+TODO List
+
+= KEY ====================
+ # Flagship
+ - Regular
+ ? Maybe I'll Do It
+==========================
+
+If no interest is expressed for a feature that may require a considerable
+amount of effort to implement, it may get endlessly delayed. Do not be
+afraid to cast your vote for the next feature to be implemented!
+
+Things to do as soon as possible:
+
+ - http://htmlpurifier.org/phorum/read.php?3,5560,6307#msg-6307
+ - Think about allowing explicit order of operations hooks for transforms
+ - Fix "<.<" bug (trailing < is removed if not EOD)
+ - Build in better internal state dumps and debugging tools for remote
+ debugging
+ - Allowed/Allowed* have strange interactions when both set
+ ? Transform lone embeds into object tags
+ - Deprecated config options that emit warnings when you set them (with'
+ a way of muting the warning if you really want to)
+ - Make HTML.Trusted work with Output.FlashCompat
+ - HTML.Trusted and HTML.SafeObject have funny interaction; general
+ problem is what to do when a module "supersedes" another
+ (see also tables and basic tables.) This is a little dicier
+ because HTML.SafeObject has some extra functionality that
+ trusted might find useful. See http://htmlpurifier.org/phorum/read.php?3,5762,6100
+
+FUTURE VERSIONS
+---------------
+
+4.9 release [OMG CONFIG PONIES]
+ ! Fix Printer. It's from the old days when we didn't have decent XML classes
+ ! Factor demo.php into a set of Printer classes, and then create a stub
+ file for users here (inside the actual HTML Purifier library)
+ - Fix error handling with form construction
+ - Do encoding validation in Printers, or at least, where user data comes in
+ - Config: Add examples to everything (make built-in which also automatically
+ gives output)
+ - Add "register" field to config schemas to eliminate dependence on
+ naming conventions (try to remember why we ultimately decided on tihs)
+
+5.0 release [HTML 5]
+ # Swap out code to use html5lib tokenizer and tree-builder
+ ! Allow turning off of FixNesting and required attribute insertion
+
+5.1 release [It's All About Trust] (floating)
+ # Implement untrusted, dangerous elements/attributes
+ # Implement IDREF support (harder than it seems, since you cannot have
+ IDREFs to non-existent IDs)
+ - Implement <area> (client and server side image maps are blocking
+ on IDREF support)
+ # Frameset XHTML 1.0 and HTML 4.01 doctypes
+ - Figure out how to simultaneously set %CSS.Trusted and %HTML.Trusted (?)
+
+5.2 release [Error'ed]
+ # Error logging for filtering/cleanup procedures
+ # Additional support for poorly written HTML
+ - Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!)
+ - Friendly strict handling of <address> (block -> <br>)
+ - XSS-attempt detection--certain errors are flagged XSS-like
+ - Append something to duplicate IDs so they're still usable (impl. note: the
+ dupe detector would also need to detect the suffix as well)
+
+6.0 release [Beyond HTML]
+ # Legit token based CSS parsing (will require revamping almost every
+ AttrDef class). Probably will use CSSTidy
+ # More control over allowed CSS properties using a modularization
+ # IRI support (this includes IDN)
+ - Standardize token armor for all areas of processing
+
+7.0 release [To XML and Beyond]
+ - Extended HTML capabilities based on namespacing and tag transforms (COMPLEX)
+ - Hooks for adding custom processors to custom namespaced tags and
+ attributes, offer default implementation
+ - Lots of documentation and samples
+
+Ongoing
+ - More refactoring to take advantage of PHP5's facilities
+ - Refactor unit tests into lots of test methods
+ - Plugins for major CMSes (COMPLEX)
+ - phpBB
+ - Also, a FAQ for extension writers with HTML Purifier
+
+AutoFormat
+ - Smileys
+ - Syntax highlighting (with GeSHi) with <pre> and possibly <?php
+ - Look at http://drupal.org/project/Modules/category/63 for ideas
+
+Neat feature related
+ ! Support exporting configuration, so users can easily tweak settings
+ in the demo, and then copy-paste into their own setup
+ - Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
+ - Allow scoped="scoped" attribute in <style> tags; may be troublesome
+ because regular CSS has no way of uniquely identifying nodes, so we'd
+ have to generate IDs
+ - Explain how to use HTML Purifier in non-PHP languages / create
+ a simple command line stub (or complicated?)
+ - Fixes for Firefox's inability to handle COL alignment props (Bug 915)
+ - Automatically add non-breaking spaces to empty table cells when
+ empty-cells:show is applied to have compatibility with Internet Explorer
+ - Table of Contents generation (XHTML Compiler might be reusable). May also
+ be out-of-band information.
+ - Full set of color keywords. Also, a way to add onto them without
+ finalizing the configuration object.
+ - Write a var_export and memcached DefinitionCache - Denis
+ - Built-in support for target="_blank" on all external links
+ - Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand.
+ Also, enable disabling of directionality
+ ? Externalize inline CSS to promote clean HTML, proposed by Sander Tekelenburg
+ ? Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes:
+ 1. Analyzing which tags to remove duplicants
+ 2. Ensure attributes are merged into the parent tag
+ 3. Extend the tag exclusion system to specify whether or not the
+ contents should be dropped or not (currently, there's code that could do
+ something like this if it didn't drop the inner text too.)
+ ? Make AutoParagraph also support paragraph-izing double <br> tags, and not
+ just double newlines. This is kind of tough to do in the current framework,
+ though, and might be reasonably approximated by search replacing double <br>s
+ with newlines before running it through HTML Purifier.
+
+Maintenance related (slightly boring)
+ # CHMOD install script for PEAR installs
+ ! Factor out command line parser into its own class, and unit test it
+ - Reduce size of internal data-structures (esp. HTMLDefinition)
+ - Allow merging configurations. Thus,
+ a -> b -> default
+ c -> d -> default
+ becomes
+ a -> b -> c -> d -> default
+ Maybe allow more fine-grained tuning of this behavior. Alternatively,
+ encourage people to use short plist depths before building them up.
+ - Time PHPT tests
+
+ChildDef related (very boring)
+ - Abstract ChildDef_BlockQuote to work with all elements that only
+ allow blocks in them, required or optional
+ - Implement lenient <ruby> child validation
+
+Wontfix
+ - Non-lossy smart alternate character encoding transformations (unless
+ patch provided)
+ - Pretty-printing HTML: users can use Tidy on the output on entire page
+ - Native content compression, whitespace stripping: use gzip if this is
+ really important
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/VERSION b/vendor/ezyang/htmlpurifier/VERSION
new file mode 100644
index 0000000..1910ba9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/VERSION
@@ -0,0 +1 @@
+4.10.0 \ No newline at end of file
diff --git a/vendor/ezyang/htmlpurifier/WHATSNEW b/vendor/ezyang/htmlpurifier/WHATSNEW
new file mode 100644
index 0000000..810086f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/WHATSNEW
@@ -0,0 +1,13 @@
+HTML Purifier 4.9.x is a maintenance release, collecting a year
+of accumulated bug fixes plus a few new features. New features
+include support for min/max-width/height CSS, and rgba/hsl/hsla
+in color specifications. Major bugfixes include improvements
+in the Serializer cache to avoid chmod'ing directories, better
+entity decoding (we won't accidentally encode entities that occur
+in URLs) and rel="noopener" on links with target attributes,
+to prevent them from overwriting the original frame.
+
+4.9.3 works around an infinite loop bug in PHP 7.1 with the opcode
+cache (and has one other, minor bugfix, avoiding using autoloading
+when testing for DOMDocument presence). If these bugs do not
+affect you, you do not need to upgrade.
diff --git a/vendor/ezyang/htmlpurifier/WYSIWYG b/vendor/ezyang/htmlpurifier/WYSIWYG
new file mode 100644
index 0000000..c518aac
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/WYSIWYG
@@ -0,0 +1,20 @@
+
+WYSIWYG - What You See Is What You Get
+ HTML Purifier: A Pretty Good Fit for TinyMCE and FCKeditor
+
+Javascript-based WYSIWYG editors, simply stated, are quite amazing. But I've
+always been wary about using them due to security issues: they handle the
+client-side magic, but once you've been served a piping hot load of unfiltered
+HTML, what should be done then? In some situations, you can serve it uncleaned,
+since you only offer these facilities to trusted(?) authors.
+
+Unfortunantely, for blog comments and anonymous input, BBCode, Textile and
+other markup languages still reign supreme. Put simply: filtering HTML is
+hard work, and these WYSIWYG authors don't offer anything to alleviate that
+trouble. Therein lies the solution:
+
+HTML Purifier is perfect for filtering pure-HTML input from WYSIWYG editors.
+
+Enough said.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/composer.json b/vendor/ezyang/htmlpurifier/composer.json
new file mode 100644
index 0000000..80fee3d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/composer.json
@@ -0,0 +1,25 @@
+{
+ "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"]
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php b/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php
new file mode 100644
index 0000000..1cfec5d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php
@@ -0,0 +1,91 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/FSTools.php b/vendor/ezyang/htmlpurifier/extras/FSTools.php
new file mode 100644
index 0000000..ce00763
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/FSTools.php
@@ -0,0 +1,164 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/FSTools/File.php b/vendor/ezyang/htmlpurifier/extras/FSTools/File.php
new file mode 100644
index 0000000..6453a7a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/FSTools/File.php
@@ -0,0 +1,141 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php
new file mode 100644
index 0000000..4016d8a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php
@@ -0,0 +1,11 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php
new file mode 100644
index 0000000..d1485bf
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php
@@ -0,0 +1,15 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php
new file mode 100644
index 0000000..69c9095
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php
@@ -0,0 +1,23 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php
new file mode 100644
index 0000000..35c2ca7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php
@@ -0,0 +1,31 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/extras/README b/vendor/ezyang/htmlpurifier/extras/README
new file mode 100644
index 0000000..4bfece7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/extras/README
@@ -0,0 +1,32 @@
+
+HTML Purifier Extras
+ The Method Behind The Madness!
+
+The extras/ folder in HTML Purifier contains--you guessed it--extra things
+for HTML Purifier. Specifically, these are two extra libraries called
+FSTools and ConfigSchema. They're extra for a reason: you don't need them
+if you're using HTML Purifier for normal usage: filtering HTML. However,
+if you're a developer, and would like to test HTML Purifier, or need to
+use one of HTML Purifier's maintenance scripts, chances are they'll need
+these libraries. Who knows: maybe you'll find them useful too!
+
+Here are the libraries:
+
+
+FSTools
+-------
+
+Short for File System Tools, this is a poor-man's object-oriented wrapper for
+the filesystem. It currently consists of two classes:
+
+- FSTools: This is a singleton that contains a manner of useful functions
+ such as recursive glob, directory removal, etc, as well as the ability
+ to call arbitrary native PHP functions through it like $FS->fopen(...).
+ This makes it a lot simpler to mock these filesystem calls for unit testing.
+
+- FSTools_File: This object represents a single file, and has almost any
+ method imaginable one would need.
+
+Check the files themselves for more information.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php
new file mode 100644
index 0000000..1960c39
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php
@@ -0,0 +1,11 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php
new file mode 100644
index 0000000..c271cd1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php
@@ -0,0 +1,15 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php
new file mode 100644
index 0000000..9d8d299
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php
@@ -0,0 +1,24 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php
new file mode 100644
index 0000000..52acc56
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php
@@ -0,0 +1,4 @@
+<?php
+if (!defined('HTMLPURIFIER_PREFIX')) {
+ define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php
new file mode 100644
index 0000000..64b140b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php
@@ -0,0 +1,25 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php
new file mode 100644
index 0000000..321bdc5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php
@@ -0,0 +1,234 @@
+<?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';
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php
new file mode 100644
index 0000000..7522900
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php
@@ -0,0 +1,30 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php
new file mode 100644
index 0000000..39b1b65
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php
@@ -0,0 +1,11 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.php
new file mode 100644
index 0000000..bada518
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.php
@@ -0,0 +1,292 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php
new file mode 100644
index 0000000..a3261f8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php
@@ -0,0 +1,228 @@
+<?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';
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php
new file mode 100644
index 0000000..d2e9d22
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php
@@ -0,0 +1,71 @@
+<?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;
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php
new file mode 100644
index 0000000..c7b17cf
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php
@@ -0,0 +1,148 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php
new file mode 100644
index 0000000..739646f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php
@@ -0,0 +1,144 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php
new file mode 100644
index 0000000..ad2cb90
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php
@@ -0,0 +1,136 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php
new file mode 100644
index 0000000..af2b83d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php
@@ -0,0 +1,34 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php
new file mode 100644
index 0000000..7f1ea3b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php
@@ -0,0 +1,111 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
new file mode 100644
index 0000000..4580ef5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
@@ -0,0 +1,157 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php
new file mode 100644
index 0000000..16243ba
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php
@@ -0,0 +1,56 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php
new file mode 100644
index 0000000..d7287a0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php
@@ -0,0 +1,161 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php
new file mode 100644
index 0000000..9c17505
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php
@@ -0,0 +1,48 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
new file mode 100644
index 0000000..9d77cc9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php
new file mode 100644
index 0000000..bde4c33
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php
@@ -0,0 +1,77 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php
new file mode 100644
index 0000000..579b97e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php
@@ -0,0 +1,176 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php
new file mode 100644
index 0000000..74e24c8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php
@@ -0,0 +1,219 @@
+<?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: "MS 明朝" 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+MS+明朝|ゴシック>
+ // 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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php
new file mode 100644
index 0000000..973002c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php
@@ -0,0 +1,32 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
new file mode 100644
index 0000000..ffc989f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
@@ -0,0 +1,56 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php
new file mode 100644
index 0000000..f12453a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php
@@ -0,0 +1,77 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php
new file mode 100644
index 0000000..e74d426
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php
@@ -0,0 +1,112 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php
new file mode 100644
index 0000000..e707f87
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php
@@ -0,0 +1,71 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php
new file mode 100644
index 0000000..8edc159
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php
@@ -0,0 +1,84 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php
new file mode 100644
index 0000000..f0f25c5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php
@@ -0,0 +1,54 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php
new file mode 100644
index 0000000..5fd4b7f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php
@@ -0,0 +1,46 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php
new file mode 100644
index 0000000..6617aca
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php
@@ -0,0 +1,77 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php
new file mode 100644
index 0000000..6698a00
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php
new file mode 100644
index 0000000..8abda7f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php
@@ -0,0 +1,73 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php
new file mode 100644
index 0000000..dea15d2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php
@@ -0,0 +1,48 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php
new file mode 100644
index 0000000..d501348
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php
@@ -0,0 +1,48 @@
+<?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;
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php
new file mode 100644
index 0000000..946ebb7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php
@@ -0,0 +1,51 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php
new file mode 100644
index 0000000..d79ba12
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php
@@ -0,0 +1,38 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php
new file mode 100644
index 0000000..4ba4561
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php
@@ -0,0 +1,113 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php
new file mode 100644
index 0000000..1c4006f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php
@@ -0,0 +1,56 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php
new file mode 100644
index 0000000..63fa04c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php
@@ -0,0 +1,72 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php
new file mode 100644
index 0000000..bbb20f2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php
@@ -0,0 +1,60 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php
new file mode 100644
index 0000000..f79683b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php
@@ -0,0 +1,70 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php
new file mode 100644
index 0000000..a1d019e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php
@@ -0,0 +1,76 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php
new file mode 100644
index 0000000..400e707
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php
@@ -0,0 +1,91 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php
new file mode 100644
index 0000000..2a55cea
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php
@@ -0,0 +1,86 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php
new file mode 100644
index 0000000..c7eb319
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php
@@ -0,0 +1,53 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php
new file mode 100644
index 0000000..4553a4e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php
@@ -0,0 +1,21 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php
new file mode 100644
index 0000000..c1cd897
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php
@@ -0,0 +1,111 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php
new file mode 100644
index 0000000..daf32b7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php
@@ -0,0 +1,20 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
new file mode 100644
index 0000000..52c0d59
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
@@ -0,0 +1,29 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php
new file mode 100644
index 0000000..e54a334
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php
@@ -0,0 +1,138 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php
new file mode 100644
index 0000000..30ac16c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php
@@ -0,0 +1,45 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php
new file mode 100644
index 0000000..f243793
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php
@@ -0,0 +1,89 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php
new file mode 100644
index 0000000..b428331
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php
@@ -0,0 +1,60 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php
new file mode 100644
index 0000000..2f72869
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php
@@ -0,0 +1,28 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php
new file mode 100644
index 0000000..d66c04a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php
@@ -0,0 +1,27 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php
new file mode 100644
index 0000000..0f51fd2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php
@@ -0,0 +1,28 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php
new file mode 100644
index 0000000..f25cd01
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php
@@ -0,0 +1,47 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php
new file mode 100644
index 0000000..057dc01
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php
@@ -0,0 +1,26 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php
new file mode 100644
index 0000000..7ccd0e3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php
@@ -0,0 +1,68 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php
new file mode 100644
index 0000000..235ebb3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php
@@ -0,0 +1,47 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php
new file mode 100644
index 0000000..350b335
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php
@@ -0,0 +1,61 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php
new file mode 100644
index 0000000..3ab47ed
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php
@@ -0,0 +1,56 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php
new file mode 100644
index 0000000..5b0aff0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php
@@ -0,0 +1,31 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php
new file mode 100644
index 0000000..853f335
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php
@@ -0,0 +1,45 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php
new file mode 100644
index 0000000..63cce68
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php
@@ -0,0 +1,33 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php
new file mode 100644
index 0000000..36079b7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php
@@ -0,0 +1,41 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php
new file mode 100644
index 0000000..1057ebe
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php
@@ -0,0 +1,52 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php
new file mode 100644
index 0000000..231c81a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php
@@ -0,0 +1,25 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php
new file mode 100644
index 0000000..d1f3a4d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php
@@ -0,0 +1,28 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php
new file mode 100644
index 0000000..1143b4b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php
@@ -0,0 +1,79 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php
new file mode 100644
index 0000000..b7057bb
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php
@@ -0,0 +1,23 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php
new file mode 100644
index 0000000..dd63ea8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php
@@ -0,0 +1,45 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php
new file mode 100644
index 0000000..1db3c6c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php
@@ -0,0 +1,37 @@
+<?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;
+ }
+}
+
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php
new file mode 100644
index 0000000..587dc2e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php
@@ -0,0 +1,37 @@
+<?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;
+ }
+}
+
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php
new file mode 100644
index 0000000..6a9f33a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php
@@ -0,0 +1,27 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php
new file mode 100644
index 0000000..3b70520
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php
@@ -0,0 +1,96 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php
new file mode 100644
index 0000000..f97dc93
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php
@@ -0,0 +1,178 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
new file mode 100644
index 0000000..707122b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
@@ -0,0 +1,124 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
new file mode 100644
index 0000000..47dfd1f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
@@ -0,0 +1,491 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php
new file mode 100644
index 0000000..8eb17b8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php
@@ -0,0 +1,52 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php
new file mode 100644
index 0000000..7439be2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php
@@ -0,0 +1,67 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php
new file mode 100644
index 0000000..128132e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php
@@ -0,0 +1,102 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php
new file mode 100644
index 0000000..a8a6cbd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php
@@ -0,0 +1,38 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php
new file mode 100644
index 0000000..4fc70e0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php
@@ -0,0 +1,92 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php
new file mode 100644
index 0000000..b946806
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php
@@ -0,0 +1,45 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php
new file mode 100644
index 0000000..0d1c8f5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php
@@ -0,0 +1,118 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php
new file mode 100644
index 0000000..3270a46
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php
@@ -0,0 +1,110 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php
new file mode 100644
index 0000000..cb6b3e6
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php
@@ -0,0 +1,224 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
new file mode 100644
index 0000000..f37cf37
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
@@ -0,0 +1,920 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php
new file mode 100644
index 0000000..655c0e9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php
@@ -0,0 +1,176 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
new file mode 100644
index 0000000..d5906cd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
@@ -0,0 +1,48 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
new file mode 100644
index 0000000..5fa56f7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
@@ -0,0 +1,144 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php
new file mode 100644
index 0000000..2671516
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Exceptions related to configuration schema
+ */
+class HTMLPurifier_ConfigSchema_Exception extends HTMLPurifier_Exception
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php
new file mode 100644
index 0000000..0e08ae8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php
@@ -0,0 +1,47 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
new file mode 100644
index 0000000..127a39a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
@@ -0,0 +1,89 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
new file mode 100644
index 0000000..126f09d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
@@ -0,0 +1,58 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
new file mode 100644
index 0000000..655e6dd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
@@ -0,0 +1,226 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php
new file mode 100644
index 0000000..fb31277
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php
@@ -0,0 +1,248 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
new file mode 100644
index 0000000..c9aa364
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
@@ -0,0 +1,130 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser
new file mode 100644
index 0000000..371e948
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser
Binary files differ
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
new file mode 100644
index 0000000..0517fed
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
@@ -0,0 +1,8 @@
+Attr.AllowedClasses
+TYPE: lookup/null
+VERSION: 4.0.0
+DEFAULT: null
+--DESCRIPTION--
+List of allowed class values in the class attribute. By default, this is null,
+which means all classes are allowed.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
new file mode 100644
index 0000000..249edd6
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
@@ -0,0 +1,12 @@
+Attr.AllowedFrameTargets
+TYPE: lookup
+DEFAULT: array()
+--DESCRIPTION--
+Lookup table of all allowed link frame targets. Some commonly used link
+targets include _blank, _self, _parent and _top. Values should be
+lowercase, as validation will be done in a case-sensitive manner despite
+W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute
+so this directive will have no effect in that doctype. XHTML 1.1 does not
+enable the Target module by default, you will have to manually enable it
+(see the module documentation for more details.)
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
new file mode 100644
index 0000000..9a8fa6a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRel
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed forward document relationships in the rel attribute. Common
+values may be nofollow or print. By default, this is empty, meaning that no
+document relationships are allowed.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
new file mode 100644
index 0000000..b017883
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRev
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed reverse document relationships in the rev attribute. This
+attribute is a bit of an edge-case; if you don't know what it is for, stay
+away.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
new file mode 100644
index 0000000..e774b82
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
@@ -0,0 +1,19 @@
+Attr.ClassUseCDATA
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.0.0
+--DESCRIPTION--
+If null, class will auto-detect the doctype and, if matching XHTML 1.1 or
+XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise,
+it will use a relaxed CDATA definition. If true, the relaxed CDATA definition
+is forced; if false, the NMTOKENS definition is forced. To get behavior
+of HTML Purifier prior to 4.0.0, set this directive to false.
+
+Some rational behind the auto-detection:
+in previous versions of HTML Purifier, it was assumed that the form of
+class was NMTOKENS, as specified by the XHTML Modularization (representing
+XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however
+specify class as CDATA. HTML 5 effectively defines it as CDATA, but
+with the additional constraint that each name should be unique (this is not
+explicitly outlined in previous specifications).
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
new file mode 100644
index 0000000..533165e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
@@ -0,0 +1,11 @@
+Attr.DefaultImageAlt
+TYPE: string/null
+DEFAULT: null
+VERSION: 3.2.0
+--DESCRIPTION--
+This is the content of the alt tag of an image if the user had not
+previously specified an alt attribute. This applies to all images without
+a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which
+only applies to invalid images, and overrides in the case of an invalid image.
+Default behavior with null is to use the basename of the src tag for the alt.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
new file mode 100644
index 0000000..9eb7e38
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
@@ -0,0 +1,9 @@
+Attr.DefaultInvalidImage
+TYPE: string
+DEFAULT: ''
+--DESCRIPTION--
+This is the default image an img tag will be pointed to if it does not have
+a valid src attribute. In future versions, we may allow the image tag to
+be removed completely, but due to design issues, this is not possible right
+now.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
new file mode 100644
index 0000000..2f17bf4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
@@ -0,0 +1,8 @@
+Attr.DefaultInvalidImageAlt
+TYPE: string
+DEFAULT: 'Invalid image'
+--DESCRIPTION--
+This is the content of the alt tag of an invalid image if the user had not
+previously specified an alt attribute. It has no effect when the image is
+valid but there was no alt attribute present.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
new file mode 100644
index 0000000..52654b5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
@@ -0,0 +1,10 @@
+Attr.DefaultTextDir
+TYPE: string
+DEFAULT: 'ltr'
+--DESCRIPTION--
+Defines the default text direction (ltr or rtl) of the document being
+parsed. This generally is the same as the value of the dir attribute in
+HTML, or ltr if that is not specified.
+--ALLOWED--
+'ltr', 'rtl'
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
new file mode 100644
index 0000000..6440d21
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
@@ -0,0 +1,16 @@
+Attr.EnableID
+TYPE: bool
+DEFAULT: false
+VERSION: 1.2.0
+--DESCRIPTION--
+Allows the ID attribute in HTML. This is disabled by default due to the
+fact that without proper configuration user input can easily break the
+validation of a webpage by specifying an ID that is already on the
+surrounding HTML. If you don't mind throwing caution to the wind, enable
+this directive, but I strongly recommend you also consider blacklisting IDs
+you use (%Attr.IDBlacklist) or prefixing all user supplied IDs
+(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of
+pre-1.2.0 versions.
+--ALIASES--
+HTML.EnableAttrID
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
new file mode 100644
index 0000000..f31d226
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
@@ -0,0 +1,8 @@
+Attr.ForbiddenClasses
+TYPE: lookup
+VERSION: 4.0.0
+DEFAULT: array()
+--DESCRIPTION--
+List of forbidden class values in the class attribute. By default, this is
+empty, which means that no classes are forbidden. See also %Attr.AllowedClasses.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
new file mode 100644
index 0000000..735d4b7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
@@ -0,0 +1,10 @@
+Attr.ID.HTML5
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.8.0
+--DESCRIPTION--
+In HTML5, restrictions on the format of the id attribute have been significantly
+relaxed, such that any string is valid so long as it contains no spaces and
+is at least one character. In lieu of a general HTML5 compatibility flag,
+set this configuration directive to true to use the relaxed rules.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
new file mode 100644
index 0000000..5f2b5e3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
@@ -0,0 +1,5 @@
+Attr.IDBlacklist
+TYPE: list
+DEFAULT: array()
+DESCRIPTION: Array of IDs not allowed in the document.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
new file mode 100644
index 0000000..6f58245
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
@@ -0,0 +1,9 @@
+Attr.IDBlacklistRegexp
+TYPE: string/null
+VERSION: 1.6.0
+DEFAULT: NULL
+--DESCRIPTION--
+PCRE regular expression to be matched against all IDs. If the expression is
+matches, the ID is rejected. Use this with care: may cause significant
+degradation. ID matching is done after all other validation.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
new file mode 100644
index 0000000..cc49d43
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
@@ -0,0 +1,12 @@
+Attr.IDPrefix
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+String to prefix to IDs. If you have no idea what IDs your pages may use,
+you may opt to simply add a prefix to all user-submitted ID attributes so
+that they are still usable, but will not conflict with core page IDs.
+Example: setting the directive to 'user_' will result in a user submitted
+'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true
+before using this.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
new file mode 100644
index 0000000..2c5924a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
@@ -0,0 +1,14 @@
+Attr.IDPrefixLocal
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you
+need to allow multiple sets of user content on web page, you may need to
+have a seperate prefix that changes with each iteration. This way,
+seperately submitted user content displayed on the same page doesn't
+clobber each other. Ideal values are unique identifiers for the content it
+represents (i.e. the id of the row in the database). Be sure to add a
+seperator (like an underscore) at the end. Warning: this directive will
+not work unless %Attr.IDPrefix is set to a non-empty value!
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
new file mode 100644
index 0000000..d5caa1b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
@@ -0,0 +1,31 @@
+AutoFormat.AutoParagraph
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ This directive turns on auto-paragraphing, where double newlines are
+ converted in to paragraphs whenever possible. Auto-paragraphing:
+</p>
+<ul>
+ <li>Always applies to inline elements or text in the root node,</li>
+ <li>Applies to inline elements or text with double newlines in nodes
+ that allow paragraph tags,</li>
+ <li>Applies to double newlines in paragraph tags</li>
+</ul>
+<p>
+ <code>p</code> tags must be allowed for this directive to take effect.
+ We do not use <code>br</code> tags for paragraphing, as that is
+ semantically incorrect.
+</p>
+<p>
+ To prevent auto-paragraphing as a content-producer, refrain from using
+ double-newlines except to specify a new paragraph or in contexts where
+ it has special meaning (whitespace usually has no meaning except in
+ tags like <code>pre</code>, so this should not be difficult.) To prevent
+ the paragraphing of inline text adjacent to block elements, wrap them
+ in <code>div</code> tags (the behavior is slightly different outside of
+ the root node.)
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
new file mode 100644
index 0000000..2a47648
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
@@ -0,0 +1,12 @@
+AutoFormat.Custom
+TYPE: list
+VERSION: 2.0.1
+DEFAULT: array()
+--DESCRIPTION--
+
+<p>
+ This directive can be used to add custom auto-format injectors.
+ Specify an array of injector names (class name minus the prefix)
+ or concrete implementations. Injector class must exist.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
new file mode 100644
index 0000000..663064a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
@@ -0,0 +1,11 @@
+AutoFormat.DisplayLinkURI
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ This directive turns on the in-text display of URIs in &lt;a&gt; tags, and disables
+ those links. For example, <a href="http://example.com">example</a> becomes
+ example (<a>http://example.com</a>).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
new file mode 100644
index 0000000..3a48ba9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
@@ -0,0 +1,12 @@
+AutoFormat.Linkify
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ This directive turns on linkification, auto-linking http, ftp and
+ https URLs. <code>a</code> tags with the <code>href</code> attribute
+ must be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
new file mode 100644
index 0000000..db58b13
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
@@ -0,0 +1,12 @@
+AutoFormat.PurifierLinkify.DocURL
+TYPE: string
+VERSION: 2.0.1
+DEFAULT: '#%s'
+ALIASES: AutoFormatParam.PurifierLinkifyDocURL
+--DESCRIPTION--
+<p>
+ Location of configuration documentation to link to, let %s substitute
+ into the configuration's namespace and directive names sans the percent
+ sign.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
new file mode 100644
index 0000000..7996488
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
@@ -0,0 +1,12 @@
+AutoFormat.PurifierLinkify
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ Internal auto-formatter that converts configuration directives in
+ syntax <a>%Namespace.Directive</a> to links. <code>a</code> tags
+ with the <code>href</code> attribute must be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt
new file mode 100644
index 0000000..6367fe2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt
@@ -0,0 +1,14 @@
+AutoFormat.RemoveEmpty.Predicate
+TYPE: hash
+VERSION: 4.7.0
+DEFAULT: array('colgroup' => array(), 'th' => array(), 'td' => array(), 'iframe' => array('src'))
+--DESCRIPTION--
+<p>
+ Given that an element has no contents, it will be removed by default, unless
+ this predicate dictates otherwise. The predicate can either be an associative
+ map from tag name to list of attributes that must be present for the element
+ to be considered preserved: thus, the default always preserves <code>colgroup</code>,
+ <code>th</code> and <code>td</code>, and also <code>iframe</code> if it
+ has a <code>src</code>.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
new file mode 100644
index 0000000..35c393b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
@@ -0,0 +1,11 @@
+AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions
+TYPE: lookup
+VERSION: 4.0.0
+DEFAULT: array('td' => true, 'th' => true)
+--DESCRIPTION--
+<p>
+ When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp
+ are enabled, this directive defines what HTML elements should not be
+ removede if they have only a non-breaking space in them.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
new file mode 100644
index 0000000..ca17eb1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
@@ -0,0 +1,15 @@
+AutoFormat.RemoveEmpty.RemoveNbsp
+TYPE: bool
+VERSION: 4.0.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ When enabled, HTML Purifier will treat any elements that contain only
+ non-breaking spaces as well as regular whitespace as empty, and remove
+ them when %AutoForamt.RemoveEmpty is enabled.
+</p>
+<p>
+ See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements
+ that don't have this behavior applied to them.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
new file mode 100644
index 0000000..34657ba
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
@@ -0,0 +1,46 @@
+AutoFormat.RemoveEmpty
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ When enabled, HTML Purifier will attempt to remove empty elements that
+ contribute no semantic information to the document. The following types
+ of nodes will be removed:
+</p>
+<ul><li>
+ Tags with no attributes and no content, and that are not empty
+ elements (remove <code>&lt;a&gt;&lt;/a&gt;</code> but not
+ <code>&lt;br /&gt;</code>), and
+ </li>
+ <li>
+ Tags with no content, except for:<ul>
+ <li>The <code>colgroup</code> element, or</li>
+ <li>
+ Elements with the <code>id</code> or <code>name</code> attribute,
+ when those attributes are permitted on those elements.
+ </li>
+ </ul></li>
+</ul>
+<p>
+ Please be very careful when using this functionality; while it may not
+ seem that empty elements contain useful information, they can alter the
+ layout of a document given appropriate styling. This directive is most
+ useful when you are processing machine-generated HTML, please avoid using
+ it on regular user HTML.
+</p>
+<p>
+ Elements that contain only whitespace will be treated as empty. Non-breaking
+ spaces, however, do not count as whitespace. See
+ %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior.
+</p>
+<p>
+ This algorithm is not perfect; you may still notice some empty tags,
+ particularly if a node had elements, but those elements were later removed
+ because they were not permitted in that context, or tags that, after
+ being auto-closed by another tag, where empty. This is for safety reasons
+ to prevent clever code from breaking validation. The general rule of thumb:
+ if a tag looked empty on the way in, it will get removed; if HTML Purifier
+ made it empty, it will stay.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
new file mode 100644
index 0000000..dde990a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
@@ -0,0 +1,11 @@
+AutoFormat.RemoveSpansWithoutAttributes
+TYPE: bool
+VERSION: 4.0.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ This directive causes <code>span</code> tags without any attributes
+ to be removed. It will also remove spans that had all attributes
+ removed during processing.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt
new file mode 100644
index 0000000..4d054b1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt
@@ -0,0 +1,11 @@
+CSS.AllowDuplicates
+TYPE: bool
+DEFAULT: false
+VERSION: 4.8.0
+--DESCRIPTION--
+<p>
+ By default, HTML Purifier removes duplicate CSS properties,
+ like <code>color:red; color:blue</code>. If this is set to
+ true, duplicate properties are allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
new file mode 100644
index 0000000..b324608
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
@@ -0,0 +1,8 @@
+CSS.AllowImportant
+TYPE: bool
+DEFAULT: false
+VERSION: 3.1.0
+--DESCRIPTION--
+This parameter determines whether or not !important cascade modifiers should
+be allowed in user CSS. If false, !important will stripped.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
new file mode 100644
index 0000000..748be0e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
@@ -0,0 +1,11 @@
+CSS.AllowTricky
+TYPE: bool
+DEFAULT: false
+VERSION: 3.1.0
+--DESCRIPTION--
+This parameter determines whether or not to allow "tricky" CSS properties and
+values. Tricky CSS properties/values can drastically modify page layout or
+be used for deceptive practices but do not directly constitute a security risk.
+For example, <code>display:none;</code> is considered a tricky property that
+will only be allowed if this directive is set to true.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
new file mode 100644
index 0000000..3fd4654
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
@@ -0,0 +1,12 @@
+CSS.AllowedFonts
+TYPE: lookup/null
+VERSION: 4.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+ Allows you to manually specify a set of allowed fonts. If
+ <code>NULL</code>, all fonts are allowed. This directive
+ affects generic names (serif, sans-serif, monospace, cursive,
+ fantasy) as well as specific font families.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
new file mode 100644
index 0000000..460112e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
@@ -0,0 +1,18 @@
+CSS.AllowedProperties
+TYPE: lookup/null
+VERSION: 3.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ If HTML Purifier's style attributes set is unsatisfactory for your needs,
+ you can overload it with your own list of tags to allow. Note that this
+ method is subtractive: it does its job by taking away from HTML Purifier
+ usual feature set, so you cannot add an attribute that HTML Purifier never
+ supported in the first place.
+</p>
+<p>
+ <strong>Warning:</strong> If another directive conflicts with the
+ elements here, <em>that</em> directive will win and override.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
new file mode 100644
index 0000000..5cb7dda
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
@@ -0,0 +1,11 @@
+CSS.DefinitionRev
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+ Revision identifier for your custom definition. See
+ %HTML.DefinitionRev for details.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
new file mode 100644
index 0000000..f1f5c5f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
@@ -0,0 +1,13 @@
+CSS.ForbiddenProperties
+TYPE: lookup
+VERSION: 4.2.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+ This is the logical inverse of %CSS.AllowedProperties, and it will
+ override that directive or any other directive. If possible,
+ %CSS.AllowedProperties is recommended over this directive,
+ because it can sometimes be difficult to tell whether or not you've
+ forbidden all of the CSS properties you truly would like to disallow.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
new file mode 100644
index 0000000..7a32914
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
@@ -0,0 +1,16 @@
+CSS.MaxImgLength
+TYPE: string/null
+DEFAULT: '1200px'
+VERSION: 3.1.1
+--DESCRIPTION--
+<p>
+ This parameter sets the maximum allowed length on <code>img</code> tags,
+ effectively the <code>width</code> and <code>height</code> properties.
+ Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %HTML.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the CSS max is a number with
+ a unit).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
new file mode 100644
index 0000000..148eedb
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
@@ -0,0 +1,10 @@
+CSS.Proprietary
+TYPE: bool
+VERSION: 3.0.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ Whether or not to allow safe, proprietary CSS values.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
new file mode 100644
index 0000000..e733a61
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
@@ -0,0 +1,9 @@
+CSS.Trusted
+TYPE: bool
+VERSION: 4.2.1
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user's CSS input is trusted or not. If the
+input is trusted, a more expansive set of allowed properties. See
+also %HTML.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
new file mode 100644
index 0000000..c486724
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
@@ -0,0 +1,14 @@
+Cache.DefinitionImpl
+TYPE: string/null
+VERSION: 2.0.0
+DEFAULT: 'Serializer'
+--DESCRIPTION--
+
+This directive defines which method to use when caching definitions,
+the complex data-type that makes HTML Purifier tick. Set to null
+to disable caching (not recommended, as you will see a definite
+performance degradation).
+
+--ALIASES--
+Core.DefinitionCache
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
new file mode 100644
index 0000000..5403650
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
@@ -0,0 +1,13 @@
+Cache.SerializerPath
+TYPE: string/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ Absolute path with no trailing slash to store serialized definitions in.
+ Default is within the
+ HTML Purifier library inside DefinitionCache/Serializer. This
+ path must be writable by the webserver.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
new file mode 100644
index 0000000..2e0cc81
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
@@ -0,0 +1,16 @@
+Cache.SerializerPermissions
+TYPE: int/null
+VERSION: 4.3.0
+DEFAULT: 0755
+--DESCRIPTION--
+
+<p>
+ Directory permissions of the files and directories created inside
+ the DefinitionCache/Serializer or other custom serializer path.
+</p>
+<p>
+ In HTML Purifier 4.8.0, this also supports <code>NULL</code>,
+ which means that no chmod'ing or directory creation shall
+ occur.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
new file mode 100644
index 0000000..568cbf3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
@@ -0,0 +1,18 @@
+Core.AggressivelyFixLt
+TYPE: bool
+VERSION: 2.1.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+ This directive enables aggressive pre-filter fixes HTML Purifier can
+ perform in order to ensure that open angled-brackets do not get killed
+ during parsing stage. Enabling this will result in two preg_replace_callback
+ calls and at least two preg_replace calls for every HTML document parsed;
+ if your users make very well-formed HTML, you can set this directive false.
+ This has no effect when DirectLex is used.
+</p>
+<p>
+ <strong>Notice:</strong> This directive's default turned from false to true
+ in HTML Purifier 3.2.0.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt
new file mode 100644
index 0000000..b2b6ab1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt
@@ -0,0 +1,16 @@
+Core.AggressivelyRemoveScript
+TYPE: bool
+VERSION: 4.9.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+ This directive enables aggressive pre-filter removal of
+ script tags. This is not necessary for security,
+ but it can help work around a bug in libxml where embedded
+ HTML elements inside script sections cause the parser to
+ choke. To revert to pre-4.9.0 behavior, set this to false.
+ This directive has no effect if %Core.Trusted is true,
+ %Core.RemoveScriptContents is false, or %Core.HiddenElements
+ does not contain script.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
new file mode 100644
index 0000000..2c910cc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
@@ -0,0 +1,16 @@
+Core.AllowHostnameUnderscore
+TYPE: bool
+VERSION: 4.6.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ By RFC 1123, underscores are not permitted in host names.
+ (This is in contrast to the specification for DNS, RFC
+ 2181, which allows underscores.)
+ However, most browsers do the right thing when faced with
+ an underscore in the host name, and so some poorly written
+ websites are written with the expectation this should work.
+ Setting this parameter to true relaxes our allowed character
+ check so that underscores are permitted.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
new file mode 100644
index 0000000..d731791
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
@@ -0,0 +1,12 @@
+Core.CollectErrors
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+
+Whether or not to collect errors found while filtering the document. This
+is a useful way to give feedback to your users. <strong>Warning:</strong>
+Currently this feature is very patchy and experimental, with lots of
+possible error messages not yet implemented. It will not cause any
+problems, but it may not help your users either.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
new file mode 100644
index 0000000..c572c14
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
@@ -0,0 +1,29 @@
+Core.ColorKeywords
+TYPE: hash
+VERSION: 2.0.0
+--DEFAULT--
+array (
+ 'maroon' => '#800000',
+ 'red' => '#FF0000',
+ 'orange' => '#FFA500',
+ 'yellow' => '#FFFF00',
+ 'olive' => '#808000',
+ 'purple' => '#800080',
+ 'fuchsia' => '#FF00FF',
+ 'white' => '#FFFFFF',
+ 'lime' => '#00FF00',
+ 'green' => '#008000',
+ 'navy' => '#000080',
+ 'blue' => '#0000FF',
+ 'aqua' => '#00FFFF',
+ 'teal' => '#008080',
+ 'black' => '#000000',
+ 'silver' => '#C0C0C0',
+ 'gray' => '#808080',
+)
+--DESCRIPTION--
+
+Lookup array of color names to six digit hexadecimal number corresponding
+to color, with preceding hash mark. Used when parsing colors. The lookup
+is done in a case-insensitive manner.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
new file mode 100644
index 0000000..64b114f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
@@ -0,0 +1,14 @@
+Core.ConvertDocumentToFragment
+TYPE: bool
+DEFAULT: true
+--DESCRIPTION--
+
+This parameter determines whether or not the filter should convert
+input that is a full document with html and body tags to a fragment
+of just the contents of a body tag. This parameter is simply something
+HTML Purifier can do during an edge-case: for most inputs, this
+processing is not necessary.
+
+--ALIASES--
+Core.AcceptFullDocuments
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
new file mode 100644
index 0000000..36f16e0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
@@ -0,0 +1,17 @@
+Core.DirectLexLineNumberSyncInterval
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 0
+--DESCRIPTION--
+
+<p>
+ Specifies the number of tokens the DirectLex line number tracking
+ implementations should process before attempting to resyncronize the
+ current line count by manually counting all previous new-lines. When
+ at 0, this functionality is disabled. Lower values will decrease
+ performance, and this is only strictly necessary if the counting
+ algorithm is buggy (in which case you should report it as a bug).
+ This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is
+ not being used.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
new file mode 100644
index 0000000..1cd4c2c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
@@ -0,0 +1,14 @@
+Core.DisableExcludes
+TYPE: bool
+DEFAULT: false
+VERSION: 4.5.0
+--DESCRIPTION--
+<p>
+ This directive disables SGML-style exclusions, e.g. the exclusion of
+ <code>&lt;object&gt;</code> in any descendant of a
+ <code>&lt;pre&gt;</code> tag. Disabling excludes will allow some
+ invalid documents to pass through HTML Purifier, but HTML Purifier
+ will also be less likely to accidentally remove large documents during
+ processing.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
new file mode 100644
index 0000000..ce243c3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
@@ -0,0 +1,9 @@
+Core.EnableIDNA
+TYPE: bool
+DEFAULT: false
+VERSION: 4.4.0
+--DESCRIPTION--
+Allows international domain names in URLs. This configuration option
+requires the PEAR Net_IDNA2 module to be installed. It operates by
+punycoding any internationalized host names for maximum portability.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
new file mode 100644
index 0000000..8bfb47c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
@@ -0,0 +1,15 @@
+Core.Encoding
+TYPE: istring
+DEFAULT: 'utf-8'
+--DESCRIPTION--
+If for some reason you are unable to convert all webpages to UTF-8, you can
+use this directive as a stop-gap compatibility change to let HTML Purifier
+deal with non UTF-8 input. This technique has notable deficiencies:
+absolutely no characters outside of the selected character encoding will be
+preserved, not even the ones that have been ampersand escaped (this is due
+to a UTF-8 specific <em>feature</em> that automatically resolves all
+entities), making it pretty useless for anything except the most I18N-blind
+applications, although %Core.EscapeNonASCIICharacters offers fixes this
+trouble with another tradeoff. This directive only accepts ISO-8859-1 if
+iconv is not enabled.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
new file mode 100644
index 0000000..a3881be
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
@@ -0,0 +1,12 @@
+Core.EscapeInvalidChildren
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+<p><strong>Warning:</strong> this configuration option is no longer does anything as of 4.6.0.</p>
+
+<p>When true, a child is found that is not allowed in the context of the
+parent element will be transformed into text as if it were ASCII. When
+false, that element and all internal tags will be dropped, though text will
+be preserved. There is no option for dropping the element but preserving
+child nodes.</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
new file mode 100644
index 0000000..a7a5b24
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
@@ -0,0 +1,7 @@
+Core.EscapeInvalidTags
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When true, invalid tags will be written back to the document as plain text.
+Otherwise, they are silently dropped.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
new file mode 100644
index 0000000..abb4999
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
@@ -0,0 +1,13 @@
+Core.EscapeNonASCIICharacters
+TYPE: bool
+VERSION: 1.4.0
+DEFAULT: false
+--DESCRIPTION--
+This directive overcomes a deficiency in %Core.Encoding by blindly
+converting all non-ASCII characters into decimal numeric entities before
+converting it to its native encoding. This means that even characters that
+can be expressed in the non-UTF-8 encoding will be entity-ized, which can
+be a real downer for encodings like Big5. It also assumes that the ASCII
+repetoire is available, although this is the case for almost all encodings.
+Anyway, use UTF-8!
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
new file mode 100644
index 0000000..915391e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
@@ -0,0 +1,19 @@
+Core.HiddenElements
+TYPE: lookup
+--DEFAULT--
+array (
+ 'script' => true,
+ 'style' => true,
+)
+--DESCRIPTION--
+
+<p>
+ This directive is a lookup array of elements which should have their
+ contents removed when they are not allowed by the HTML definition.
+ For example, the contents of a <code>script</code> tag are not
+ normally shown in a document, so if script tags are to be removed,
+ their contents should be removed to. This is opposed to a <code>b</code>
+ tag, which defines some presentational changes but does not hide its
+ contents.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
new file mode 100644
index 0000000..233fca1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
@@ -0,0 +1,10 @@
+Core.Language
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'en'
+--DESCRIPTION--
+
+ISO 639 language code for localizable things in HTML Purifier to use,
+which is mainly error reporting. There is currently only an English (en)
+translation, so this directive is currently useless.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt
new file mode 100644
index 0000000..392b436
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt
@@ -0,0 +1,36 @@
+Core.LegacyEntityDecoder
+TYPE: bool
+VERSION: 4.9.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Prior to HTML Purifier 4.9.0, entities were decoded by performing
+ a global search replace for all entities whose decoded versions
+ did not have special meanings under HTML, and replaced them with
+ their decoded versions. We would match all entities, even if they did
+ not have a trailing semicolon, but only if there weren't any trailing
+ alphanumeric characters.
+</p>
+<table>
+<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
+<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
+<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
+<tr><td>&amp;yena</td><td>&amp;yena</td><td>&amp;yena</td></tr>
+<tr><td>&amp;yen=</td><td>&yen;=</td><td>&yen;=</td></tr>
+</table>
+<p>
+ In HTML Purifier 4.9.0, we changed the behavior of entity parsing
+ to match entities that had missing trailing semicolons in less
+ cases, to more closely match HTML5 parsing behavior:
+</p>
+<table>
+<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
+<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
+<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
+<tr><td>&amp;yena</td><td>&yen;a</td><td>&amp;yena</td></tr>
+<tr><td>&amp;yen=</td><td>&yen;=</td><td>&amp;yen=</td></tr>
+</table>
+<p>
+ This flag reverts back to pre-HTML Purifier 4.9.0 behavior.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
new file mode 100644
index 0000000..8983e2c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
@@ -0,0 +1,34 @@
+Core.LexerImpl
+TYPE: mixed/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ This parameter determines what lexer implementation can be used. The
+ valid values are:
+</p>
+<dl>
+ <dt><em>null</em></dt>
+ <dd>
+ Recommended, the lexer implementation will be auto-detected based on
+ your PHP-version and configuration.
+ </dd>
+ <dt><em>string</em> lexer identifier</dt>
+ <dd>
+ This is a slim way of manually overridding the implementation.
+ Currently recognized values are: DOMLex (the default PHP5
+implementation)
+ and DirectLex (the default PHP4 implementation). Only use this if
+ you know what you are doing: usually, the auto-detection will
+ manage things for cases you aren't even aware of.
+ </dd>
+ <dt><em>object</em> lexer instance</dt>
+ <dd>
+ Super-advanced: you can specify your own, custom, implementation that
+ implements the interface defined by <code>HTMLPurifier_Lexer</code>.
+ I may remove this option simply because I don't expect anyone
+ to use it.
+ </dd>
+</dl>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
new file mode 100644
index 0000000..eb841a7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
@@ -0,0 +1,16 @@
+Core.MaintainLineNumbers
+TYPE: bool/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ If true, HTML Purifier will add line number information to all tokens.
+ This is useful when error reporting is turned on, but can result in
+ significant performance degradation and should not be used when
+ unnecessary. This directive must be used with the DirectLex lexer,
+ as the DOMLex lexer does not (yet) support this functionality.
+ If the value is null, an appropriate value will be selected based
+ on other configuration.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
new file mode 100644
index 0000000..d77f536
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
@@ -0,0 +1,11 @@
+Core.NormalizeNewlines
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+ Whether or not to normalize newlines to the operating
+ system default. When <code>false</code>, HTML Purifier
+ will attempt to preserve mixed newline files.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
new file mode 100644
index 0000000..4070c2a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
@@ -0,0 +1,12 @@
+Core.RemoveInvalidImg
+TYPE: bool
+DEFAULT: true
+VERSION: 1.3.0
+--DESCRIPTION--
+
+<p>
+ This directive enables pre-emptive URI checking in <code>img</code>
+ tags, as the attribute validation strategy is not authorized to
+ remove elements from the document. Revert to pre-1.3.0 behavior by setting to false.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
new file mode 100644
index 0000000..3397d9f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
@@ -0,0 +1,11 @@
+Core.RemoveProcessingInstructions
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+Instead of escaping processing instructions in the form <code>&lt;? ...
+?&gt;</code>, remove it out-right. This may be useful if the HTML
+you are validating contains XML processing instruction gunk, however,
+it can also be user-unfriendly for people attempting to post PHP
+snippets.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
new file mode 100644
index 0000000..a4cd966
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
@@ -0,0 +1,12 @@
+Core.RemoveScriptContents
+TYPE: bool/null
+DEFAULT: NULL
+VERSION: 2.0.0
+DEPRECATED-VERSION: 2.1.0
+DEPRECATED-USE: Core.HiddenElements
+--DESCRIPTION--
+<p>
+ This directive enables HTML Purifier to remove not only script tags
+ but all of their contents.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
new file mode 100644
index 0000000..3db50ef
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
@@ -0,0 +1,11 @@
+Filter.Custom
+TYPE: list
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+ This directive can be used to add custom filters; it is nearly the
+ equivalent of the now deprecated <code>HTMLPurifier-&gt;addFilter()</code>
+ method. Specify an array of concrete implementations.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
new file mode 100644
index 0000000..16829bc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
@@ -0,0 +1,14 @@
+Filter.ExtractStyleBlocks.Escaping
+TYPE: bool
+VERSION: 3.0.0
+DEFAULT: true
+ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping
+--DESCRIPTION--
+
+<p>
+ Whether or not to escape the dangerous characters &lt;, &gt; and &amp;
+ as \3C, \3E and \26, respectively. This is can be safely set to false
+ if the contents of StyleBlocks will be placed in an external stylesheet,
+ where there is no risk of it being interpreted as HTML.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
new file mode 100644
index 0000000..7f95f54
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
@@ -0,0 +1,29 @@
+Filter.ExtractStyleBlocks.Scope
+TYPE: string/null
+VERSION: 3.0.0
+DEFAULT: NULL
+ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope
+--DESCRIPTION--
+
+<p>
+ If you would like users to be able to define external stylesheets, but
+ only allow them to specify CSS declarations for a specific node and
+ prevent them from fiddling with other elements, use this directive.
+ It accepts any valid CSS selector, and will prepend this to any
+ CSS declaration extracted from the document. For example, if this
+ directive is set to <code>#user-content</code> and a user uses the
+ selector <code>a:hover</code>, the final selector will be
+ <code>#user-content a:hover</code>.
+</p>
+<p>
+ The comma shorthand may be used; consider the above example, with
+ <code>#user-content, #user-content2</code>, the final selector will
+ be <code>#user-content a:hover, #user-content2 a:hover</code>.
+</p>
+<p>
+ <strong>Warning:</strong> It is possible for users to bypass this measure
+ using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML
+ Purifier, and I am working to get it fixed. Until then, HTML Purifier
+ performs a basic check to prevent this.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
new file mode 100644
index 0000000..6c231b2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
@@ -0,0 +1,16 @@
+Filter.ExtractStyleBlocks.TidyImpl
+TYPE: mixed/null
+VERSION: 3.1.0
+DEFAULT: NULL
+ALIASES: FilterParam.ExtractStyleBlocksTidyImpl
+--DESCRIPTION--
+<p>
+ If left NULL, HTML Purifier will attempt to instantiate a <code>csstidy</code>
+ class to use for internal cleaning. This will usually be good enough.
+</p>
+<p>
+ However, for trusted user input, you can set this to <code>false</code> to
+ disable cleaning. In addition, you can supply your own concrete implementation
+ of Tidy's interface to use, although I don't know why you'd want to do that.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
new file mode 100644
index 0000000..078d087
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
@@ -0,0 +1,74 @@
+Filter.ExtractStyleBlocks
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+EXTERNAL: CSSTidy
+--DESCRIPTION--
+<p>
+ This directive turns on the style block extraction filter, which removes
+ <code>style</code> blocks from input HTML, cleans them up with CSSTidy,
+ and places them in the <code>StyleBlocks</code> context variable, for further
+ use by you, usually to be placed in an external stylesheet, or a
+ <code>style</code> block in the <code>head</code> of your document.
+</p>
+<p>
+ Sample usage:
+</p>
+<pre><![CDATA[
+<?php
+ header('Content-type: text/html; charset=utf-8');
+ echo '<?xml version="1.0" encoding="UTF-8"?>';
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+ <title>Filter.ExtractStyleBlocks</title>
+<?php
+ require_once '/path/to/library/HTMLPurifier.auto.php';
+ require_once '/path/to/csstidy.class.php';
+
+ $dirty = '<style>body {color:#F00;}</style> Some text';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Filter', 'ExtractStyleBlocks', true);
+ $purifier = new HTMLPurifier($config);
+
+ $html = $purifier->purify($dirty);
+
+ // This implementation writes the stylesheets to the styles/ directory.
+ // You can also echo the styles inside the document, but it's a bit
+ // more difficult to make sure they get interpreted properly by
+ // browsers; try the usual CSS armoring techniques.
+ $styles = $purifier->context->get('StyleBlocks');
+ $dir = 'styles/';
+ if (!is_dir($dir)) mkdir($dir);
+ $hash = sha1($_GET['html']);
+ foreach ($styles as $i => $style) {
+ file_put_contents($name = $dir . $hash . "_$i");
+ echo '<link rel="stylesheet" type="text/css" href="'.$name.'" />';
+ }
+?>
+</head>
+<body>
+ <div>
+ <?php echo $html; ?>
+ </div>
+</b]]><![CDATA[ody>
+</html>
+]]></pre>
+<p>
+ <strong>Warning:</strong> It is possible for a user to mount an
+ imagecrash attack using this CSS. Counter-measures are difficult;
+ it is not simply enough to limit the range of CSS lengths (using
+ relative lengths with many nesting levels allows for large values
+ to be attained without actually specifying them in the stylesheet),
+ and the flexible nature of selectors makes it difficult to selectively
+ disable lengths on image tags (HTML Purifier, however, does disable
+ CSS width and height in inline styling). There are probably two effective
+ counter measures: an explicit width and height set to auto in all
+ images in your document (unlikely) or the disabling of width and
+ height (somewhat reasonable). Whether or not these measures should be
+ used is left to the reader.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
new file mode 100644
index 0000000..321eaa2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
@@ -0,0 +1,16 @@
+Filter.YouTube
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ <strong>Warning:</strong> Deprecated in favor of %HTML.SafeObject and
+ %Output.FlashCompat (turn both on to allow YouTube videos and other
+ Flash content).
+</p>
+<p>
+ This directive enables YouTube video embedding in HTML Purifier. Check
+ <a href="http://htmlpurifier.org/docs/enduser-youtube.html">this document
+ on embedding videos</a> for more information on what this filter does.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
new file mode 100644
index 0000000..0b2c106
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
@@ -0,0 +1,25 @@
+HTML.Allowed
+TYPE: itext/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ This is a preferred convenience directive that combines
+ %HTML.AllowedElements and %HTML.AllowedAttributes.
+ Specify elements and attributes that are allowed using:
+ <code>element1[attr1|attr2],element2...</code>. For example,
+ if you would like to only allow paragraphs and links, specify
+ <code>a[href],p</code>. You can specify attributes that apply
+ to all elements using an asterisk, e.g. <code>*[lang]</code>.
+ You can also use newlines instead of commas to separate elements.
+</p>
+<p>
+ <strong>Warning</strong>:
+ All of the constraints on the component directives are still enforced.
+ The syntax is a <em>subset</em> of TinyMCE's <code>valid_elements</code>
+ whitelist: directly copy-pasting it here will probably result in
+ broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes
+ are set, this directive has no effect.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
new file mode 100644
index 0000000..fcf093f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
@@ -0,0 +1,19 @@
+HTML.AllowedAttributes
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ If HTML Purifier's attribute set is unsatisfactory, overload it!
+ The syntax is "tag.attr" or "*.attr" for the global attributes
+ (style, id, class, dir, lang, xml:lang).
+</p>
+<p>
+ <strong>Warning:</strong> If another directive conflicts with the
+ elements here, <em>that</em> directive will win and override. For
+ example, %HTML.EnableAttrID will take precedence over *.id in this
+ directive. You must set that directive to true before you can use
+ IDs at all.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
new file mode 100644
index 0000000..140e214
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
@@ -0,0 +1,10 @@
+HTML.AllowedComments
+TYPE: lookup
+VERSION: 4.4.0
+DEFAULT: array()
+--DESCRIPTION--
+A whitelist which indicates what explicit comment bodies should be
+allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp
+(these directives are union'ed together, so a comment is considered
+valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
new file mode 100644
index 0000000..f22e977
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
@@ -0,0 +1,15 @@
+HTML.AllowedCommentsRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+A regexp, which if it matches the body of a comment, indicates that
+it should be allowed. Trailing and leading spaces are removed prior
+to running this regular expression.
+<strong>Warning:</strong> Make sure you specify
+correct anchor metacharacters <code>^regex$</code>, otherwise you may accept
+comments that you did not mean to! In particular, the regex <code>/foo|bar/</code>
+is probably not sufficiently strict, since it also allows <code>foobar</code>.
+See also %HTML.AllowedComments (these directives are union'ed together,
+so a comment is considered valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644
index 0000000..1d3fa79
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
@@ -0,0 +1,23 @@
+HTML.AllowedElements
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+ If HTML Purifier's tag set is unsatisfactory for your needs, you can
+ overload it with your own list of tags to allow. If you change
+ this, you probably also want to change %HTML.AllowedAttributes; see
+ also %HTML.Allowed which lets you set allowed elements and
+ attributes at the same time.
+</p>
+<p>
+ If you attempt to allow an element that HTML Purifier does not know
+ about, HTML Purifier will raise an error. You will need to manually
+ tell HTML Purifier about this element by using the
+ <a href="http://htmlpurifier.org/docs/enduser-customize.html">advanced customization features.</a>
+</p>
+<p>
+ <strong>Warning:</strong> If another directive conflicts with the
+ elements here, <em>that</em> directive will win and override.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
new file mode 100644
index 0000000..5a59a55
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
@@ -0,0 +1,20 @@
+HTML.AllowedModules
+TYPE: lookup/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ A doctype comes with a set of usual modules to use. Without having
+ to mucking about with the doctypes, you can quickly activate or
+ disable these modules by specifying which modules you wish to allow
+ with this directive. This is most useful for unit testing specific
+ modules, although end users may find it useful for their own ends.
+</p>
+<p>
+ If you specify a module that does not exist, the manager will silently
+ fail to use it, so be careful! User-defined modules are not affected
+ by this directive. Modules defined in %HTML.CoreModules are not
+ affected by this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
new file mode 100644
index 0000000..151fb7b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
@@ -0,0 +1,11 @@
+HTML.Attr.Name.UseCDATA
+TYPE: bool
+DEFAULT: false
+VERSION: 4.0.0
+--DESCRIPTION--
+The W3C specification DTD defines the name attribute to be CDATA, not ID, due
+to limitations of DTD. In certain documents, this relaxed behavior is desired,
+whether it is to specify duplicate names, or to specify names that would be
+illegal IDs (for example, names that begin with a digit.) Set this configuration
+directive to true to use the relaxed parsing rules.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
new file mode 100644
index 0000000..45ae469
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
@@ -0,0 +1,18 @@
+HTML.BlockWrapper
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'p'
+--DESCRIPTION--
+
+<p>
+ String name of element to wrap inline elements that are inside a block
+ context. This only occurs in the children of blockquote in strict mode.
+</p>
+<p>
+ Example: by default value,
+ <code>&lt;blockquote&gt;Foo&lt;/blockquote&gt;</code> would become
+ <code>&lt;blockquote&gt;&lt;p&gt;Foo&lt;/p&gt;&lt;/blockquote&gt;</code>.
+ The <code>&lt;p&gt;</code> tags can be replaced with whatever you desire,
+ as long as it is a block level element.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
new file mode 100644
index 0000000..5246188
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
@@ -0,0 +1,23 @@
+HTML.CoreModules
+TYPE: lookup
+VERSION: 2.0.0
+--DEFAULT--
+array (
+ 'Structure' => true,
+ 'Text' => true,
+ 'Hypertext' => true,
+ 'List' => true,
+ 'NonXMLCommonAttributes' => true,
+ 'XMLCommonAttributes' => true,
+ 'CommonAttributes' => true,
+)
+--DESCRIPTION--
+
+<p>
+ Certain modularized doctypes (XHTML, namely), have certain modules
+ that must be included for the doctype to be an conforming document
+ type: put those modules here. By default, XHTML's core modules
+ are used. You can set this to a blank array to disable core module
+ protection, but this is not recommended.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
new file mode 100644
index 0000000..6ed70b5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
@@ -0,0 +1,9 @@
+HTML.CustomDoctype
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+A custom doctype for power-users who defined their own document
+type. This directive only applies when %HTML.Doctype is blank.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
new file mode 100644
index 0000000..103db75
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
@@ -0,0 +1,33 @@
+HTML.DefinitionID
+TYPE: string/null
+DEFAULT: NULL
+VERSION: 2.0.0
+--DESCRIPTION--
+
+<p>
+ Unique identifier for a custom-built HTML definition. If you edit
+ the raw version of the HTMLDefinition, introducing changes that the
+ configuration object does not reflect, you must specify this variable.
+ If you change your custom edits, you should change this directive, or
+ clear your cache. Example:
+</p>
+<pre>
+$config = HTMLPurifier_Config::createDefault();
+$config->set('HTML', 'DefinitionID', '1');
+$def = $config->getHTMLDefinition();
+$def->addAttribute('a', 'tabindex', 'Number');
+</pre>
+<p>
+ In the above example, the configuration is still at the defaults, but
+ using the advanced API, an extra attribute has been added. The
+ configuration object normally has no way of knowing that this change
+ has taken place, so it needs an extra directive: %HTML.DefinitionID.
+ If someone else attempts to use the default configuration, these two
+ pieces of code will not clobber each other in the cache, since one has
+ an extra directive attached to it.
+</p>
+<p>
+ You <em>must</em> specify a value to this directive to use the
+ advanced API features.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
new file mode 100644
index 0000000..229ae02
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
@@ -0,0 +1,16 @@
+HTML.DefinitionRev
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+ Revision identifier for your custom definition specified in
+ %HTML.DefinitionID. This serves the same purpose: uniquely identifying
+ your custom definition, but this one does so in a chronological
+ context: revision 3 is more up-to-date then revision 2. Thus, when
+ this gets incremented, the cache handling is smart enough to clean
+ up any older revisions of your definition as well as flush the
+ cache.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
new file mode 100644
index 0000000..9dab497
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
@@ -0,0 +1,11 @@
+HTML.Doctype
+TYPE: string/null
+DEFAULT: NULL
+--DESCRIPTION--
+Doctype to use during filtering. Technically speaking this is not actually
+a doctype (as it does not identify a corresponding DTD), but we are using
+this name for sake of simplicity. When non-blank, this will override any
+older directives like %HTML.XHTML or %HTML.Strict.
+--ALLOWED--
+'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1'
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
new file mode 100644
index 0000000..7878dc0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
@@ -0,0 +1,11 @@
+HTML.FlashAllowFullScreen
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to permit embedded Flash content from
+ %HTML.SafeObject to expand to the full screen. Corresponds to
+ the <code>allowFullScreen</code> parameter.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
new file mode 100644
index 0000000..57358f9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
@@ -0,0 +1,21 @@
+HTML.ForbiddenAttributes
+TYPE: lookup
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+ While this directive is similar to %HTML.AllowedAttributes, for
+ forwards-compatibility with XML, this attribute has a different syntax. Instead of
+ <code>tag.attr</code>, use <code>tag@attr</code>. To disallow <code>href</code>
+ attributes in <code>a</code> tags, set this directive to
+ <code>a@href</code>. You can also disallow an attribute globally with
+ <code>attr</code> or <code>*@attr</code> (either syntax is fine; the latter
+ is provided for consistency with %HTML.AllowedAttributes).
+</p>
+<p>
+ <strong>Warning:</strong> This directive complements %HTML.ForbiddenElements,
+ accordingly, check
+ out that directive for a discussion of why you
+ should think twice before using this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
new file mode 100644
index 0000000..93a53e1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
@@ -0,0 +1,20 @@
+HTML.ForbiddenElements
+TYPE: lookup
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+ This was, perhaps, the most requested feature ever in HTML
+ Purifier. Please don't abuse it! This is the logical inverse of
+ %HTML.AllowedElements, and it will override that directive, or any
+ other directive.
+</p>
+<p>
+ If possible, %HTML.Allowed is recommended over this directive, because it
+ can sometimes be difficult to tell whether or not you've forbidden all of
+ the behavior you would like to disallow. If you forbid <code>img</code>
+ with the expectation of preventing images on your site, you'll be in for
+ a nasty surprise when people start using the <code>background-image</code>
+ CSS property.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
new file mode 100644
index 0000000..e424c38
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
@@ -0,0 +1,14 @@
+HTML.MaxImgLength
+TYPE: int/null
+DEFAULT: 1200
+VERSION: 3.1.1
+--DESCRIPTION--
+<p>
+ This directive controls the maximum number of pixels in the width and
+ height attributes in <code>img</code> tags. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %CSS.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the HTML max is an integer).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
new file mode 100644
index 0000000..700b309
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
@@ -0,0 +1,7 @@
+HTML.Nofollow
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, nofollow rel attributes are added to all outgoing links.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
new file mode 100644
index 0000000..62e8e16
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
@@ -0,0 +1,12 @@
+HTML.Parent
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'div'
+--DESCRIPTION--
+
+<p>
+ String name of element that HTML fragment passed to library will be
+ inserted in. An interesting variation would be using span as the
+ parent element, meaning that only inline tags would be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
new file mode 100644
index 0000000..dfb7204
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
@@ -0,0 +1,12 @@
+HTML.Proprietary
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to allow proprietary elements and attributes in your
+ documents, as per <code>HTMLPurifier_HTMLModule_Proprietary</code>.
+ <strong>Warning:</strong> This can cause your documents to stop
+ validating!
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
new file mode 100644
index 0000000..cdda09a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
@@ -0,0 +1,13 @@
+HTML.SafeEmbed
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to permit embed tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to embed tags. Embed is a proprietary
+ element and will cause your website to stop validating; you should
+ see if you can use %Output.FlashCompat with %HTML.SafeObject instead
+ first.</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
new file mode 100644
index 0000000..5eb6ec2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
@@ -0,0 +1,13 @@
+HTML.SafeIframe
+TYPE: bool
+VERSION: 4.4.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to permit iframe tags in untrusted documents. This
+ directive must be accompanied by a whitelist of permitted iframes,
+ such as %URI.SafeIframeRegexp, otherwise it will fatally error.
+ This directive has no effect on strict doctypes, as iframes are not
+ valid.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
new file mode 100644
index 0000000..ceb342e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
@@ -0,0 +1,13 @@
+HTML.SafeObject
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to permit object tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to object tags. You should also enable
+ %Output.FlashCompat in order to generate Internet Explorer
+ compatibility code for your object tags.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
new file mode 100644
index 0000000..5ebc7a1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
@@ -0,0 +1,10 @@
+HTML.SafeScripting
+TYPE: lookup
+VERSION: 4.5.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+ Whether or not to permit script tags to external scripts in documents.
+ Inline scripting is not allowed, and the script must match an explicit whitelist.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
new file mode 100644
index 0000000..a8b1de5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
@@ -0,0 +1,9 @@
+HTML.Strict
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not to use Transitional (loose) or Strict rulesets.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
new file mode 100644
index 0000000..587a167
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
@@ -0,0 +1,8 @@
+HTML.TargetBlank
+TYPE: bool
+VERSION: 4.4.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, <code>target=blank</code> attributes are added to all outgoing links.
+(This includes links from an HTTPS version of a page to an HTTP version.)
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
new file mode 100644
index 0000000..dd514c0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
@@ -0,0 +1,10 @@
+--# vim: et sw=4 sts=4
+HTML.TargetNoopener
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noopener rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
new file mode 100644
index 0000000..cb5a0b0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
@@ -0,0 +1,9 @@
+HTML.TargetNoreferrer
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noreferrer rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644
index 0000000..b4c271b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
@@ -0,0 +1,8 @@
+HTML.TidyAdd
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to add to the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644
index 0000000..4186ccd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
@@ -0,0 +1,24 @@
+HTML.TidyLevel
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'medium'
+--DESCRIPTION--
+
+<p>General level of cleanliness the Tidy module should enforce.
+There are four allowed values:</p>
+<dl>
+ <dt>none</dt>
+ <dd>No extra tidying should be done</dd>
+ <dt>light</dt>
+ <dd>Only fix elements that would be discarded otherwise due to
+ lack of support in doctype</dd>
+ <dt>medium</dt>
+ <dd>Enforce best practices</dd>
+ <dt>heavy</dt>
+ <dd>Transform all deprecated elements and attributes to standards
+ compliant equivalents</dd>
+</dl>
+
+--ALLOWED--
+'none', 'light', 'medium', 'heavy'
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644
index 0000000..996762b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
@@ -0,0 +1,8 @@
+HTML.TidyRemove
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to remove from the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644
index 0000000..1db9237
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
@@ -0,0 +1,9 @@
+HTML.Trusted
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user input is trusted or not. If the input is
+trusted, a more expansive set of allowed tags and attributes will be used.
+See also %CSS.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644
index 0000000..2a47e38
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
@@ -0,0 +1,11 @@
+HTML.XHTML
+TYPE: bool
+DEFAULT: true
+VERSION: 1.1.0
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor.
+--ALIASES--
+Core.XHTML
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644
index 0000000..08921fd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
@@ -0,0 +1,10 @@
+Output.CommentScriptContents
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: true
+--DESCRIPTION--
+Determines whether or not HTML Purifier should attempt to fix up the
+contents of script tags for legacy browsers with comments.
+--ALIASES--
+Core.CommentScriptContents
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644
index 0000000..d6f0d9f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
@@ -0,0 +1,15 @@
+Output.FixInnerHTML
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+ If true, HTML Purifier will protect against Internet Explorer's
+ mishandling of the <code>innerHTML</code> attribute by appending
+ a space to any attribute that does not contain angled brackets, spaces
+ or quotes, but contains a backtick. This slightly changes the
+ semantics of any given attribute, so if this is unacceptable and
+ you do not use <code>innerHTML</code> on any of your pages, you can
+ turn this directive off.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
new file mode 100644
index 0000000..93398e8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
@@ -0,0 +1,11 @@
+Output.FlashCompat
+TYPE: bool
+VERSION: 4.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ If true, HTML Purifier will generate Internet Explorer compatibility
+ code for all object code. This is highly recommended if you enable
+ %HTML.SafeObject.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
new file mode 100644
index 0000000..79f8ad8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
@@ -0,0 +1,13 @@
+Output.Newline
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ Newline string to format final output with. If left null, HTML Purifier
+ will auto-detect the default newline type of the system and use that;
+ you can manually override it here. Remember, \r\n is Windows, \r
+ is Mac, and \n is Unix.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
new file mode 100644
index 0000000..232b023
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
@@ -0,0 +1,14 @@
+Output.SortAttr
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ If true, HTML Purifier will sort attributes by name before writing them back
+ to the document, converting a tag like: <code>&lt;el b="" a="" c="" /&gt;</code>
+ to <code>&lt;el a="" b="" c="" /&gt;</code>. This is a workaround for
+ a bug in FCKeditor which causes it to swap attributes order, adding noise
+ to text diffs. If you're not seeing this bug, chances are, you don't need
+ this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
new file mode 100644
index 0000000..06bab00
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
@@ -0,0 +1,25 @@
+Output.TidyFormat
+TYPE: bool
+VERSION: 1.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Determines whether or not to run Tidy on the final output for pretty
+ formatting reasons, such as indentation and wrap.
+</p>
+<p>
+ This can greatly improve readability for editors who are hand-editing
+ the HTML, but is by no means necessary as HTML Purifier has already
+ fixed all major errors the HTML may have had. Tidy is a non-default
+ extension, and this directive will silently fail if Tidy is not
+ available.
+</p>
+<p>
+ If you are looking to make the overall look of your page's source
+ better, I recommend running Tidy on the entire page rather than just
+ user-content (after all, the indentation relative to the containing
+ blocks will be incorrect).
+</p>
+--ALIASES--
+Core.TidyFormat
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
new file mode 100644
index 0000000..071bc02
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
@@ -0,0 +1,7 @@
+Test.ForceNoIconv
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When set to true, HTMLPurifier_Encoder will act as if iconv does not exist
+and use only pure PHP implementations.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
new file mode 100644
index 0000000..eb97307
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
@@ -0,0 +1,18 @@
+URI.AllowedSchemes
+TYPE: lookup
+--DEFAULT--
+array (
+ 'http' => true,
+ 'https' => true,
+ 'mailto' => true,
+ 'ftp' => true,
+ 'nntp' => true,
+ 'news' => true,
+ 'tel' => true,
+)
+--DESCRIPTION--
+Whitelist that defines the schemes that a URI is allowed to have. This
+prevents XSS attacks from using pseudo-schemes like javascript or mocha.
+There is also support for the <code>data</code> and <code>file</code>
+URI schemes, but they are not enabled by default.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644
index 0000000..876f068
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
@@ -0,0 +1,17 @@
+URI.Base
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ The base URI is the URI of the document this purified HTML will be
+ inserted into. This information is important if HTML Purifier needs
+ to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute
+ is on. You may use a non-absolute URI for this value, but behavior
+ may vary (%URI.MakeAbsolute deals nicely with both absolute and
+ relative paths, but forwards-compatibility is not guaranteed).
+ <strong>Warning:</strong> If set, the scheme on this URI
+ overrides the one specified by %URI.DefaultScheme.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
new file mode 100644
index 0000000..834bc08
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
@@ -0,0 +1,15 @@
+URI.DefaultScheme
+TYPE: string/null
+DEFAULT: 'http'
+--DESCRIPTION--
+
+<p>
+ Defines through what scheme the output will be served, in order to
+ select the proper object validator when no scheme information is present.
+</p>
+
+<p>
+ Starting with HTML Purifier 4.9.0, the default scheme can be null, in
+ which case we reject all URIs which do not have explicit schemes.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
new file mode 100644
index 0000000..f05312b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
@@ -0,0 +1,11 @@
+URI.DefinitionID
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ Unique identifier for a custom-built URI definition. If you want
+ to add custom URIFilters, you must specify this value.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
new file mode 100644
index 0000000..80cfea9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
@@ -0,0 +1,11 @@
+URI.DefinitionRev
+TYPE: int
+VERSION: 2.1.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+ Revision identifier for your custom definition. See
+ %HTML.DefinitionRev for details.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
new file mode 100644
index 0000000..71ce025
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
@@ -0,0 +1,14 @@
+URI.Disable
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ Disables all URIs in all forms. Not sure why you'd want to do that
+ (after all, the Internet's founded on the notion of a hyperlink).
+</p>
+
+--ALIASES--
+Attr.DisableURI
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
new file mode 100644
index 0000000..13c122c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
@@ -0,0 +1,11 @@
+URI.DisableExternal
+TYPE: bool
+VERSION: 1.2.0
+DEFAULT: false
+--DESCRIPTION--
+Disables links to external websites. This is a highly effective anti-spam
+and anti-pagerank-leech measure, but comes at a hefty price: nolinks or
+images outside of your domain will be allowed. Non-linkified URIs will
+still be preserved. If you want to be able to link to subdomains or use
+absolute URIs, specify %URI.Host for your website.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
new file mode 100644
index 0000000..abcc1ef
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
@@ -0,0 +1,13 @@
+URI.DisableExternalResources
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+--DESCRIPTION--
+Disables the embedding of external resources, preventing users from
+embedding things like images from other hosts. This prevents access
+tracking (good for email viewers), bandwidth leeching, cross-site request
+forging, goatse.cx posting, and other nasties, but also results in a loss
+of end-user functionality (they can't directly post a pic they posted from
+Flickr anymore). Use it if you don't have a robust user-content moderation
+team.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
new file mode 100644
index 0000000..f891de4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
@@ -0,0 +1,15 @@
+URI.DisableResources
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Disables embedding resources, essentially meaning no pictures. You can
+ still link to them though. See %URI.DisableExternalResources for why
+ this might be a good idea.
+</p>
+<p>
+ <em>Note:</em> While this directive has been available since 1.3.0,
+ it didn't actually start doing anything until 4.2.0.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
new file mode 100644
index 0000000..ee83b12
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
@@ -0,0 +1,19 @@
+URI.Host
+TYPE: string/null
+VERSION: 1.2.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ Defines the domain name of the server, so we can determine whether or
+ an absolute URI is from your website or not. Not strictly necessary,
+ as users should be using relative URIs to reference resources on your
+ website. It will, however, let you use absolute URIs to link to
+ subdomains of the domain you post here: i.e. example.com will allow
+ sub.example.com. However, higher up domains will still be excluded:
+ if you set %URI.Host to sub.example.com, example.com will be blocked.
+ <strong>Note:</strong> This directive overrides %URI.Base because
+ a given page may be on a sub-domain, but you wish HTML Purifier to be
+ more relaxed and allow some of the parent domains too.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
new file mode 100644
index 0000000..0b6df76
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
@@ -0,0 +1,9 @@
+URI.HostBlacklist
+TYPE: list
+VERSION: 1.3.0
+DEFAULT: array()
+--DESCRIPTION--
+List of strings that are forbidden in the host of any URI. Use it to kill
+domain names of spam, etc. Note that it will catch anything in the domain,
+so <tt>moo.com</tt> will catch <tt>moo.com.example.com</tt>.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
new file mode 100644
index 0000000..4214900
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
@@ -0,0 +1,13 @@
+URI.MakeAbsolute
+TYPE: bool
+VERSION: 2.1.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+ Converts all URIs into absolute forms. This is useful when the HTML
+ being filtered assumes a specific base path, but will actually be
+ viewed in a different context (and setting an alternate base URI is
+ not possible). %URI.Base must be set for this directive to work.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
new file mode 100644
index 0000000..58c81dc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
@@ -0,0 +1,83 @@
+URI.Munge
+TYPE: string/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+ Munges all browsable (usually http, https and ftp)
+ absolute URIs into another URI, usually a URI redirection service.
+ This directive accepts a URI, formatted with a <code>%s</code> where
+ the url-encoded original URI should be inserted (sample:
+ <code>http://www.google.com/url?q=%s</code>).
+</p>
+<p>
+ Uses for this directive:
+</p>
+<ul>
+ <li>
+ Prevent PageRank leaks, while being fairly transparent
+ to users (you may also want to add some client side JavaScript to
+ override the text in the statusbar). <strong>Notice</strong>:
+ Many security experts believe that this form of protection does not deter spam-bots.
+ </li>
+ <li>
+ Redirect users to a splash page telling them they are leaving your
+ website. While this is poor usability practice, it is often mandated
+ in corporate environments.
+ </li>
+</ul>
+<p>
+ Prior to HTML Purifier 3.1.1, this directive also enabled the munging
+ of browsable external resources, which could break things if your redirection
+ script was a splash page or used <code>meta</code> tags. To revert to
+ previous behavior, please use %URI.MungeResources.
+</p>
+<p>
+ You may want to also use %URI.MungeSecretKey along with this directive
+ in order to enforce what URIs your redirector script allows. Open
+ redirector scripts can be a security risk and negatively affect the
+ reputation of your domain name.
+</p>
+<p>
+ Starting with HTML Purifier 3.1.1, there is also these substitutions:
+</p>
+<table>
+ <thead>
+ <tr>
+ <th>Key</th>
+ <th>Description</th>
+ <th>Example <code>&lt;a href=""&gt;</code></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>%r</td>
+ <td>1 - The URI embeds a resource<br />(blank) - The URI is merely a link</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>%n</td>
+ <td>The name of the tag this URI came from</td>
+ <td>a</td>
+ </tr>
+ <tr>
+ <td>%m</td>
+ <td>The name of the attribute this URI came from</td>
+ <td>href</td>
+ </tr>
+ <tr>
+ <td>%p</td>
+ <td>The name of the CSS property this URI came from, or blank if irrelevant</td>
+ <td></td>
+ </tr>
+ </tbody>
+</table>
+<p>
+ Admittedly, these letters are somewhat arbitrary; the only stipulation
+ was that they couldn't be a through f. r is for resource (I would have preferred
+ e, but you take what you can get), n is for name, m
+ was picked because it came after n (and I couldn't use a), p is for
+ property.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
new file mode 100644
index 0000000..6fce0fd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
@@ -0,0 +1,17 @@
+URI.MungeResources
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ If true, any URI munging directives like %URI.Munge
+ will also apply to embedded resources, such as <code>&lt;img src=""&gt;</code>.
+ Be careful enabling this directive if you have a redirector script
+ that does not use the <code>Location</code> HTTP header; all of your images
+ and other embedded resources will break.
+</p>
+<p>
+ <strong>Warning:</strong> It is strongly advised you use this in conjunction
+ %URI.MungeSecretKey to mitigate the security risk of an open redirector.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
new file mode 100644
index 0000000..1e17c1d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
@@ -0,0 +1,30 @@
+URI.MungeSecretKey
+TYPE: string/null
+VERSION: 3.1.1
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+ This directive enables secure checksum generation along with %URI.Munge.
+ It should be set to a secure key that is not shared with anyone else.
+ The checksum can be placed in the URI using %t. Use of this checksum
+ affords an additional level of protection by allowing a redirector
+ to check if a URI has passed through HTML Purifier with this line:
+</p>
+
+<pre>$checksum === hash_hmac("sha256", $url, $secret_key)</pre>
+
+<p>
+ If the output is TRUE, the redirector script should accept the URI.
+</p>
+
+<p>
+ Please note that it would still be possible for an attacker to procure
+ secure hashes en-mass by abusing your website's Preview feature or the
+ like, but this service affords an additional level of protection
+ that should be combined with website blacklisting.
+</p>
+
+<p>
+ Remember this has no effect if %URI.Munge is not on.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
new file mode 100644
index 0000000..23331a4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
@@ -0,0 +1,9 @@
+URI.OverrideAllowedSchemes
+TYPE: bool
+DEFAULT: true
+--DESCRIPTION--
+If this is set to true (which it is by default), you can override
+%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the
+registry. If false, you will also have to update that directive in order
+to add more schemes.
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
new file mode 100644
index 0000000..7908483
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
@@ -0,0 +1,22 @@
+URI.SafeIframeRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+ A PCRE regular expression that will be matched against an iframe URI. This is
+ a relatively inflexible scheme, but works well enough for the most common
+ use-case of iframes: embedded video. This directive only has an effect if
+ %HTML.SafeIframe is enabled. Here are some example values:
+</p>
+<ul>
+ <li><code>%^http://www.youtube.com/embed/%</code> - Allow YouTube videos</li>
+ <li><code>%^http://player.vimeo.com/video/%</code> - Allow Vimeo videos</li>
+ <li><code>%^http://(www.youtube.com/embed/|player.vimeo.com/video/)%</code> - Allow both</li>
+</ul>
+<p>
+ Note that this directive does not give you enough granularity to, say, disable
+ all <code>autoplay</code> videos. Pipe up on the HTML Purifier forums if this
+ is a capability you want.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini
new file mode 100644
index 0000000..5de4505
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini
@@ -0,0 +1,3 @@
+name = "HTML Purifier"
+
+; vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php
new file mode 100644
index 0000000..543e3f8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php
@@ -0,0 +1,170 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php
new file mode 100644
index 0000000..00e509c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php
@@ -0,0 +1,95 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php
new file mode 100644
index 0000000..bc6d433
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php
@@ -0,0 +1,55 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php
new file mode 100644
index 0000000..9aa8ff3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php
@@ -0,0 +1,129 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php
new file mode 100644
index 0000000..b57a51b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php
@@ -0,0 +1,112 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
new file mode 100644
index 0000000..4991777
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
@@ -0,0 +1,78 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php
new file mode 100644
index 0000000..d529dce
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php
@@ -0,0 +1,85 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
new file mode 100644
index 0000000..b1fec8d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
@@ -0,0 +1,82 @@
+<?php
+
+require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
+
+/**
+ * Definition cache decorator template.
+ */
+class HTMLPurifier_DefinitionCache_Decorator_Template extends HTMLPurifier_DefinitionCache_Decorator
+{
+
+ /**
+ * @type string
+ */
+ public $name = 'Template'; // replace this
+
+ public function copy()
+ {
+ // replace class name with yours
+ return new HTMLPurifier_DefinitionCache_Decorator_Template();
+ }
+
+ // remove methods you don't need
+
+ /**
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function add($def, $config)
+ {
+ return parent::add($def, $config);
+ }
+
+ /**
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function set($def, $config)
+ {
+ return parent::set($def, $config);
+ }
+
+ /**
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function replace($def, $config)
+ {
+ return parent::replace($def, $config);
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function get($config)
+ {
+ return parent::get($config);
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function flush($config)
+ {
+ return parent::flush($config);
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return mixed
+ */
+ public function cleanup($config)
+ {
+ return parent::cleanup($config);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php
new file mode 100644
index 0000000..d9a75ce
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php
@@ -0,0 +1,76 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php
new file mode 100644
index 0000000..b82c6bb
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php
@@ -0,0 +1,311 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README
new file mode 100755
index 0000000..2e35c1c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README
@@ -0,0 +1,3 @@
+This is a dummy file to prevent Git from ignoring this empty directory.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php
new file mode 100644
index 0000000..fd1cc9b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php
@@ -0,0 +1,106 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php
new file mode 100644
index 0000000..4acd06e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php
@@ -0,0 +1,73 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php
new file mode 100644
index 0000000..acc1d64
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php
@@ -0,0 +1,142 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php
new file mode 100644
index 0000000..d5311ce
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php
@@ -0,0 +1,216 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php
new file mode 100644
index 0000000..b94f175
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php
@@ -0,0 +1,617 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php
new file mode 100644
index 0000000..f12ff13
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php
@@ -0,0 +1,48 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser
new file mode 100644
index 0000000..e8b0812
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser
@@ -0,0 +1 @@
+a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";} \ No newline at end of file
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php
new file mode 100644
index 0000000..c372b5a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php
@@ -0,0 +1,285 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php
new file mode 100644
index 0000000..d47e3f2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php
@@ -0,0 +1,244 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php
new file mode 100644
index 0000000..cf869d3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php
@@ -0,0 +1,74 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php
new file mode 100644
index 0000000..be85b4c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php
@@ -0,0 +1,12 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php
new file mode 100644
index 0000000..c1f41ee
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php
@@ -0,0 +1,56 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
new file mode 100644
index 0000000..66f70b0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
@@ -0,0 +1,341 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php
new file mode 100644
index 0000000..276d836
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php
@@ -0,0 +1,65 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php
new file mode 100644
index 0000000..eb56e2d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php
@@ -0,0 +1,286 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
new file mode 100644
index 0000000..9b7b334
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
@@ -0,0 +1,493 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php
new file mode 100644
index 0000000..bb3a923
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php
@@ -0,0 +1,284 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php
new file mode 100644
index 0000000..1e67c79
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php
new file mode 100644
index 0000000..a96ab1b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php
@@ -0,0 +1,31 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php
new file mode 100644
index 0000000..a9042a3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php
@@ -0,0 +1,55 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php
new file mode 100644
index 0000000..6f7ddbc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php
@@ -0,0 +1,190 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php
new file mode 100644
index 0000000..72d7a31
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php
new file mode 100644
index 0000000..f7e7c91
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php
@@ -0,0 +1,51 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php
new file mode 100644
index 0000000..0f5fdb3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php
@@ -0,0 +1,49 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php
new file mode 100644
index 0000000..86b5299
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php
@@ -0,0 +1,186 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php
new file mode 100644
index 0000000..7a20ff7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php
@@ -0,0 +1,51 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php
new file mode 100644
index 0000000..60c0545
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php
@@ -0,0 +1,26 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php
new file mode 100644
index 0000000..dc9410a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php
@@ -0,0 +1,25 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
new file mode 100644
index 0000000..da72225
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
@@ -0,0 +1,20 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php
new file mode 100644
index 0000000..2f9efc5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php
@@ -0,0 +1,62 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php
new file mode 100644
index 0000000..6458ce9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php
@@ -0,0 +1,42 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php
new file mode 100644
index 0000000..5ee3c8e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php
new file mode 100644
index 0000000..a0d4892
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php
@@ -0,0 +1,36 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php
new file mode 100644
index 0000000..04e6689
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php
new file mode 100644
index 0000000..1297f80
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php
@@ -0,0 +1,62 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php
new file mode 100644
index 0000000..0330cd9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php
new file mode 100644
index 0000000..8b28a7b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php
@@ -0,0 +1,73 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php
new file mode 100644
index 0000000..497b832
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php
@@ -0,0 +1,33 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php
new file mode 100644
index 0000000..8a0b3b4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php
@@ -0,0 +1,75 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php
new file mode 100644
index 0000000..b188ac9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php
@@ -0,0 +1,28 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php
new file mode 100644
index 0000000..58ccc68
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php
@@ -0,0 +1,24 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php
new file mode 100644
index 0000000..b967ff5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php
@@ -0,0 +1,21 @@
+<?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();
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php
new file mode 100644
index 0000000..32484d6
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php
@@ -0,0 +1,21 @@
+<?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();
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php
new file mode 100644
index 0000000..7a65e00
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php
@@ -0,0 +1,87 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php
new file mode 100644
index 0000000..08aa232
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php
@@ -0,0 +1,230 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php
new file mode 100644
index 0000000..a995161
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php
@@ -0,0 +1,33 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
new file mode 100644
index 0000000..3326438
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
@@ -0,0 +1,34 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php
new file mode 100644
index 0000000..803c44f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php
@@ -0,0 +1,43 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php
new file mode 100644
index 0000000..c095ad9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php
@@ -0,0 +1,16 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php
new file mode 100644
index 0000000..3ecddc4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php
@@ -0,0 +1,26 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
new file mode 100644
index 0000000..c4f16a4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
@@ -0,0 +1,179 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
new file mode 100644
index 0000000..01dbe9d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
@@ -0,0 +1,20 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php
new file mode 100644
index 0000000..38c058f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php
@@ -0,0 +1,467 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php
new file mode 100644
index 0000000..65c902c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php
@@ -0,0 +1,57 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php
new file mode 100644
index 0000000..116b470
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php
@@ -0,0 +1,283 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php
new file mode 100644
index 0000000..4afdd12
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php
@@ -0,0 +1,356 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php
new file mode 100644
index 0000000..c19b1bc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php
new file mode 100644
index 0000000..74f83ea
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php
@@ -0,0 +1,64 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php
new file mode 100644
index 0000000..cb9046f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php
@@ -0,0 +1,71 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php
new file mode 100644
index 0000000..0ebc477
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php
@@ -0,0 +1,112 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
new file mode 100644
index 0000000..9ee7aa8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
@@ -0,0 +1,84 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php
new file mode 100644
index 0000000..317f786
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php
@@ -0,0 +1,124 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php
new file mode 100644
index 0000000..65277dd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php
@@ -0,0 +1,204 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php
new file mode 100644
index 0000000..8828f5c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php
@@ -0,0 +1,9 @@
+<?php
+
+// private class for unit testing
+
+class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language
+{
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php
new file mode 100644
index 0000000..1c046f3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php
@@ -0,0 +1,11 @@
+<?php
+
+// private language message file for unit testing purposes
+
+$fallback = 'en';
+
+$messages = array(
+ 'HTMLPurifier' => 'HTML Purifier X'
+);
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php
new file mode 100644
index 0000000..806c83f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php
@@ -0,0 +1,12 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php
new file mode 100644
index 0000000..c7f197e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php
@@ -0,0 +1,55 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php
new file mode 100644
index 0000000..4e35272
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php
@@ -0,0 +1,209 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php
new file mode 100644
index 0000000..e70da55
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php
@@ -0,0 +1,162 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php
new file mode 100644
index 0000000..e9da3ed
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php
@@ -0,0 +1,382 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php
new file mode 100644
index 0000000..6238a99
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php
@@ -0,0 +1,328 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php
new file mode 100644
index 0000000..6f13089
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php
@@ -0,0 +1,539 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php
new file mode 100644
index 0000000..72476dd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php
@@ -0,0 +1,4788 @@
+<?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;
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php
new file mode 100644
index 0000000..3995fec
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php
@@ -0,0 +1,49 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php
new file mode 100644
index 0000000..38ba193
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php
@@ -0,0 +1,36 @@
+<?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);
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php
new file mode 100644
index 0000000..6cbf56d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php
@@ -0,0 +1,59 @@
+<?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);
+ }
+ }
+}
+
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php
new file mode 100644
index 0000000..aec9166
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php
@@ -0,0 +1,54 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php
new file mode 100644
index 0000000..18c8bbb
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php
@@ -0,0 +1,111 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php
new file mode 100644
index 0000000..549e4ce
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php
@@ -0,0 +1,218 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php
new file mode 100644
index 0000000..29505fe
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css
new file mode 100644
index 0000000..3ff1a88
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css
@@ -0,0 +1,10 @@
+
+.hp-config {}
+
+.hp-config tbody th {text-align:right; padding-right:0.5em;}
+.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;}
+.hp-config .namespace th {text-align:center;}
+.hp-config .verbose {display:none;}
+.hp-config .controls {text-align:center;}
+
+/* vim: et sw=4 sts=4 */
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js
new file mode 100644
index 0000000..cba00c9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js
@@ -0,0 +1,5 @@
+function toggleWriteability(id_of_patient, checked) {
+ document.getElementById(id_of_patient).disabled = checked;
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php
new file mode 100644
index 0000000..65a7779
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php
@@ -0,0 +1,451 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php
new file mode 100644
index 0000000..5f2f2f8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php
@@ -0,0 +1,324 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php
new file mode 100644
index 0000000..189348f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php
@@ -0,0 +1,122 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php
new file mode 100644
index 0000000..15b330e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php
@@ -0,0 +1,42 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php
new file mode 100644
index 0000000..f58db90
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php
@@ -0,0 +1,56 @@
+<?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);
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php
new file mode 100644
index 0000000..e1ff3b7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php
@@ -0,0 +1,26 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php
new file mode 100644
index 0000000..d7d35ce
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php
@@ -0,0 +1,30 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php
new file mode 100644
index 0000000..4414c17
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php
@@ -0,0 +1,17 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php
new file mode 100644
index 0000000..6fa673d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php
@@ -0,0 +1,181 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php
new file mode 100644
index 0000000..a6eb09e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php
@@ -0,0 +1,659 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php
new file mode 100644
index 0000000..1a8149e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php
@@ -0,0 +1,207 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
new file mode 100644
index 0000000..fbb3d27
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
@@ -0,0 +1,45 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php
new file mode 100644
index 0000000..c073701
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php
@@ -0,0 +1,47 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php
new file mode 100644
index 0000000..7c73f80
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php
@@ -0,0 +1,136 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php
new file mode 100644
index 0000000..7b8d833
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php
@@ -0,0 +1,37 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php
new file mode 100644
index 0000000..7853d90
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php
@@ -0,0 +1,114 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php
new file mode 100644
index 0000000..71bf10b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php
new file mode 100644
index 0000000..84d3619
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php
@@ -0,0 +1,100 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php
new file mode 100644
index 0000000..23453c7
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php
@@ -0,0 +1,38 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php
new file mode 100644
index 0000000..78a95f5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php
@@ -0,0 +1,15 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php
new file mode 100644
index 0000000..59b38fd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php
@@ -0,0 +1,24 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php
new file mode 100644
index 0000000..019f317
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * Concrete start token class.
+ */
+class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
+{
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php
new file mode 100644
index 0000000..d643fa6
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php
@@ -0,0 +1,68 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php
new file mode 100644
index 0000000..f26a1c2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php
@@ -0,0 +1,53 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php
new file mode 100644
index 0000000..dea2446
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php
@@ -0,0 +1,118 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
new file mode 100644
index 0000000..9c5be39
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
@@ -0,0 +1,316 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php
new file mode 100644
index 0000000..e0bd8bc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php
@@ -0,0 +1,112 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php
new file mode 100644
index 0000000..09724e9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php
@@ -0,0 +1,74 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php
new file mode 100644
index 0000000..ced1b13
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php
@@ -0,0 +1,54 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php
new file mode 100644
index 0000000..c656216
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php
@@ -0,0 +1,25 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php
new file mode 100644
index 0000000..d5c412c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php
@@ -0,0 +1,22 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php
new file mode 100644
index 0000000..a6645c1
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php
@@ -0,0 +1,46 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php
new file mode 100644
index 0000000..c507bbf
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php
@@ -0,0 +1,158 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php
new file mode 100644
index 0000000..6e03315
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php
@@ -0,0 +1,115 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644
index 0000000..f609c47
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php
@@ -0,0 +1,68 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php
new file mode 100644
index 0000000..0e7381a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php
@@ -0,0 +1,71 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php
new file mode 100644
index 0000000..fe9e82c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php
@@ -0,0 +1,102 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php
new file mode 100644
index 0000000..41c49d5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php
@@ -0,0 +1,136 @@
+<?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)
+ {
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php
new file mode 100644
index 0000000..215be4b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php
@@ -0,0 +1,44 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php
new file mode 100644
index 0000000..1eb43ee
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php
@@ -0,0 +1,58 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php
new file mode 100644
index 0000000..ce69ec4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php
@@ -0,0 +1,36 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php
new file mode 100644
index 0000000..0e96882
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php
@@ -0,0 +1,18 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php
new file mode 100644
index 0000000..c3a6b60
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php
@@ -0,0 +1,40 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php
new file mode 100644
index 0000000..7490927
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php
@@ -0,0 +1,35 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php
new file mode 100644
index 0000000..f211d71
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php
@@ -0,0 +1,32 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php
new file mode 100644
index 0000000..8cd1933
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php
@@ -0,0 +1,46 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php
new file mode 100644
index 0000000..4ac8a0b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php
@@ -0,0 +1,81 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
new file mode 100644
index 0000000..166f3bf
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
@@ -0,0 +1,307 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php
new file mode 100644
index 0000000..50cba69
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php
@@ -0,0 +1,198 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php
new file mode 100644
index 0000000..b15016c
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php
@@ -0,0 +1,130 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php
new file mode 100644
index 0000000..f11c318
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php
@@ -0,0 +1,38 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php
new file mode 100644
index 0000000..5df3414
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Exception type for HTMLPurifier_VarParser
+ */
+class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php
new file mode 100644
index 0000000..6e21ea0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php
@@ -0,0 +1,157 @@
+<?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);
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/maintenance/.htaccess b/vendor/ezyang/htmlpurifier/maintenance/.htaccess
new file mode 100644
index 0000000..3a42882
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/.htaccess
@@ -0,0 +1 @@
+Deny from all
diff --git a/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch b/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch
new file mode 100644
index 0000000..7637095
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch
@@ -0,0 +1,102 @@
+--- C:\Users\Edward\Webs\htmlpurifier\maintenance\PH5P.php 2008-07-07 09:12:12.000000000 -0400
++++ C:\Users\Edward\Webs\htmlpurifier\maintenance/PH5P.new.php 2008-12-06 02:29:34.988800000 -0500
+@@ -65,7 +65,7 @@
+
+ public function __construct($data) {
+ $data = str_replace("\r\n", "\n", $data);
+- $date = str_replace("\r", null, $data);
++ $data = str_replace("\r", null, $data);
+
+ $this->data = $data;
+ $this->char = -1;
+@@ -211,7 +211,10 @@
+ // 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);
++ $this->emitToken(array(
++ 'type' => self::CHARACTR,
++ 'data' => $char
++ ));
+
+ // Finally, switch to the data state.
+ $this->state = 'data';
+@@ -708,7 +711,7 @@
+ } elseif($char === '&') {
+ /* U+0026 AMPERSAND (&)
+ Switch to the entity in attribute value state. */
+- $this->entityInAttributeValueState('non');
++ $this->entityInAttributeValueState();
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+@@ -738,7 +741,8 @@
+ ? '&'
+ : $entity;
+
+- $this->emitToken($char);
++ $last = count($this->token['attr']) - 1;
++ $this->token['attr'][$last]['value'] .= $char;
+ }
+
+ private function bogusCommentState() {
+@@ -1066,6 +1070,11 @@
+ $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;
+ }
+@@ -2084,7 +2093,7 @@
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+- $this->insertElement($token);
++ $this->insertElement($token, true, true);
+ break;
+ }
+ break;
+@@ -3465,7 +3474,18 @@
+ }
+ }
+
+- private function insertElement($token, $append = true) {
++ 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) {
+@@ -3659,7 +3679,7 @@
+ }
+ }
+
+- private function generateImpliedEndTags(array $exclude = array()) {
++ 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
+@@ -3673,7 +3693,8 @@
+ }
+ }
+
+- private function getElementCategory($name) {
++ private function getElementCategory($node) {
++ $name = $node->tagName;
+ if(in_array($name, $this->special))
+ return self::SPECIAL;
+
diff --git a/vendor/ezyang/htmlpurifier/maintenance/PH5P.php b/vendor/ezyang/htmlpurifier/maintenance/PH5P.php
new file mode 100644
index 0000000..a04273e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/PH5P.php
@@ -0,0 +1,3889 @@
+<?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;
+ }
+}
diff --git a/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php b/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php
new file mode 100644
index 0000000..d6a8eb2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php
@@ -0,0 +1,130 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/common.php b/vendor/ezyang/htmlpurifier/maintenance/common.php
new file mode 100644
index 0000000..342bc20
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/common.php
@@ -0,0 +1,25 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/compile-doxygen.sh b/vendor/ezyang/htmlpurifier/maintenance/compile-doxygen.sh
new file mode 100755
index 0000000..ecd1127
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/compile-doxygen.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+cd ..
+mkdir docs/doxygen
+rm -Rf docs/doxygen/*
+doxygen 1>docs/doxygen/info.log 2>docs/doxygen/errors.log
+if [ "$?" != 0 ]; then
+ cat docs/doxygen/errors.log
+ exit
+fi
+cd docs
+tar czf doxygen.tgz doxygen
diff --git a/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php b/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php
new file mode 100644
index 0000000..c614d1f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php
@@ -0,0 +1,155 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php b/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php
new file mode 100755
index 0000000..138badb
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php
@@ -0,0 +1,42 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/flush.php b/vendor/ezyang/htmlpurifier/maintenance/flush.php
new file mode 100644
index 0000000..c0853d2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/flush.php
@@ -0,0 +1,30 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php b/vendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php
new file mode 100755
index 0000000..ff1713e
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php
@@ -0,0 +1,75 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php b/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php
new file mode 100644
index 0000000..01e1c2a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php
@@ -0,0 +1,192 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.php b/vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.php
new file mode 100644
index 0000000..c92a7d2
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.php
@@ -0,0 +1,22 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php b/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php
new file mode 100644
index 0000000..339ff12
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php
@@ -0,0 +1,45 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php b/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php
new file mode 100755
index 0000000..254d4d8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php
@@ -0,0 +1,159 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/merge-library.php b/vendor/ezyang/htmlpurifier/maintenance/merge-library.php
new file mode 100755
index 0000000..de2eecd
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/merge-library.php
@@ -0,0 +1,11 @@
+#!/usr/bin/php
+<?php
+
+/**
+ * @file
+ * Deprecated in favor of generate-standalone.php.
+ */
+
+require dirname(__FILE__) . '/generate-standalone.php';
+
+// vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.php b/vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.php
new file mode 100644
index 0000000..514a08d
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.php
@@ -0,0 +1,71 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php b/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php
new file mode 100644
index 0000000..f47c7d0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php
@@ -0,0 +1,32 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php b/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php
new file mode 100644
index 0000000..5ae0319
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php
@@ -0,0 +1,32 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh b/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh
new file mode 100755
index 0000000..6f4d720
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh
@@ -0,0 +1,5 @@
+#!/bin/bash -e
+./compile-doxygen.sh
+cd ../docs
+scp doxygen.tgz htmlpurifier.org:/home/ezyang/htmlpurifier.org
+ssh htmlpurifier.org "cd /home/ezyang/htmlpurifier.org && ./reload-docs.sh"
diff --git a/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php b/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php
new file mode 100644
index 0000000..8578705
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php
@@ -0,0 +1,37 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/maintenance/rename-config.php b/vendor/ezyang/htmlpurifier/maintenance/rename-config.php
new file mode 100644
index 0000000..6e59e2a
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/rename-config.php
@@ -0,0 +1,84 @@
+#!/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);
diff --git a/vendor/ezyang/htmlpurifier/maintenance/update-config.php b/vendor/ezyang/htmlpurifier/maintenance/update-config.php
new file mode 100644
index 0000000..2d8a7a9
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/maintenance/update-config.php
@@ -0,0 +1,34 @@
+#!/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
diff --git a/vendor/ezyang/htmlpurifier/package.php b/vendor/ezyang/htmlpurifier/package.php
new file mode 100644
index 0000000..bfef936
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/package.php
@@ -0,0 +1,61 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/phpdoc.ini b/vendor/ezyang/htmlpurifier/phpdoc.ini
new file mode 100644
index 0000000..c4c3723
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/phpdoc.ini
@@ -0,0 +1,102 @@
+;; phpDocumentor parse configuration file
+;;
+;; This file is designed to cut down on repetitive typing on the command-line or web interface
+;; You can copy this file to create a number of configuration files that can be used with the
+;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini. The web
+;; interface will automatically generate a list of .ini files that can be used.
+;;
+;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs
+;;
+;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini
+;;
+;; Copyright 2002, Greg Beaver <cellog@users.sourceforge.net>
+;;
+;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them
+
+[Parse Data]
+;; title of all the documentation
+;; legal values: any string
+title = HTML Purifier API Documentation
+
+;; parse files that start with a . like .bash_profile
+;; legal values: true, false
+hidden = false
+
+;; show elements marked @access private in documentation by setting this to on
+;; legal values: on, off
+parseprivate = off
+
+;; parse with javadoc-like description (first sentence is always the short description)
+;; legal values: on, off
+javadocdesc = on
+
+;; add any custom @tags separated by commas here
+;; legal values: any legal tagname separated by commas.
+;customtags = mytag1,mytag2
+
+;; This is only used by the XML:DocBook/peardoc2 converter
+defaultcategoryname = Documentation
+
+;; what is the main package?
+;; legal values: alphanumeric string plus - and _
+defaultpackagename = HTMLPurifier
+
+;; output any parsing information? set to on for cron jobs
+;; legal values: on
+;quiet = on
+
+;; parse a PEAR-style repository. Do not turn this on if your project does
+;; not have a parent directory named "pear"
+;; legal values: on/off
+;pear = on
+
+;; where should the documentation be written?
+;; legal values: a legal path
+target = docs/phpdoc
+
+;; Which files should be parsed out as special documentation files, such as README,
+;; INSTALL and CHANGELOG? This overrides the default files found in
+;; phpDocumentor.ini (this file is not a user .ini file, but the global file)
+readmeinstallchangelog = README, INSTALL, NEWS, WYSIWYG, SLOW, LICENSE, CREDITS
+
+;; limit output to the specified packages, even if others are parsed
+;; legal values: package names separated by commas
+;packageoutput = package1,package2
+
+;; comma-separated list of files to parse
+;; legal values: paths separated by commas
+;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory
+
+;; comma-separated list of directories to parse
+;; legal values: directory paths separated by commas
+;directory = /path1,/path2,.,..,subdirectory
+;directory = /home/jeichorn/cvs/pear
+directory = .
+
+;; template base directory (the equivalent directory of <installdir>/phpDocumentor)
+;templatebase = /path/to/my/templates
+
+;; directory to find any example files in through @example and {@example} tags
+;examplesdir = /path/to/my/templates
+
+;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore
+;; legal values: any wildcard strings separated by commas
+;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/
+ignore = *tests*,*benchmarks*,*docs*,*test-settings.php,*configdoc*,*maintenance*,*smoketests*,*standalone*,*.svn*,*conf*
+
+sourcecode = on
+
+;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format
+;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib,
+;; HTML:frames:earthli,
+;; HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de,
+;; HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli
+;; HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS
+;; PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default
+output=HTML:frames:default
+
+;; turn this option on if you want highlighted source code for every file
+;; legal values: on/off
+sourcecode = on
+
+; vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/modx.txt b/vendor/ezyang/htmlpurifier/plugins/modx.txt
new file mode 100644
index 0000000..0763821
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/modx.txt
@@ -0,0 +1,112 @@
+
+MODx Plugin
+
+MODx <http://www.modxcms.com/> is an open source PHP application framework.
+I first came across them in my referrer logs when tillda asked if anyone
+could implement an HTML Purifier plugin. This forum thread
+<http://modxcms.com/forums/index.php/topic,6604.0.html> eventually resulted
+in the fruition of this plugin that davidm says, "is on top of my favorite
+list." HTML Purifier goes great with WYSIWYG editors!
+
+
+
+1. Credits
+
+PaulGregory wrote the overall structure of the code. I added the
+slashes hack.
+
+
+
+2. Install
+
+First, you need to place HTML Purifier library somewhere. The code here
+assumes that you've placed in MODx's assets/plugins/htmlpurifier (no version
+number).
+
+Log into the manager, and navigate:
+
+Resources > Manage Resources > Plugins tab > New Plugin
+
+Type in a name (probably HTML Purifier), and copy paste this code into the
+textarea:
+
+--------------------------------------------------------------------------------
+$e = &$modx->Event;
+if ($e->name == 'OnBeforeDocFormSave') {
+ global $content;
+
+ include_once '../assets/plugins/htmlpurifier/library/HTMLPurifier.auto.php';
+ $purifier = new HTMLPurifier();
+
+ static $magic_quotes = null;
+ if ($magic_quotes === null) {
+ // this is an ugly hack because this hook hasn't
+ // had the backslashes removed yet when magic_quotes_gpc is on,
+ // but HTMLPurifier must not have the quotes slashed.
+ $magic_quotes = get_magic_quotes_gpc();
+ }
+
+ if ($magic_quotes) $content = stripslashes($content);
+ $content = $purifier->purify($content);
+ if ($magic_quotes) $content = addslashes($content);
+}
+--------------------------------------------------------------------------------
+
+Then navigate to the System Events tab and check "OnBeforeDocFormSave".
+Save the plugin. HTML Purifier now is integrated!
+
+
+
+3. Making sure it works
+
+You can test HTML Purifier by deliberately putting in crappy HTML and seeing
+whether or not it gets fixed. A better way is to put in something like this:
+
+<p lang="fr">Il est bon</p>
+
+...and seeing whether or not the content comes out as:
+
+<p lang="fr" xml:lang="fr">Il est bon</p>
+
+(lang to xml:lang synchronization is one of the many features HTML Purifier
+has).
+
+
+
+4. Caveat Emptor
+
+This code does not intercept save requests from the QuickEdit plugin, this may
+be added in a later version. It also modifies things on save, so there's a
+slight chance that HTML Purifier may make a boo-boo and accidently mess things
+up (the original version is not saved).
+
+Finally, make sure that MODx is using UTF-8. If you are using, say, a French
+localisation, you may be using Latin-1, if that's the case, configure
+HTML Purifier properly like this:
+
+$config = HTMLPurifier_Config::createDefault();
+$config->set('Core', 'Encoding', 'ISO-8859-1'); // or whatever encoding
+$purifier = new HTMLPurifier($config);
+
+
+
+5. Known Bugs
+
+'rn' characters sometimes mysteriously appear after purification. We are
+currently investigating this issue. See: <http://htmlpurifier.org/phorum/read.php?3,1866>
+
+
+
+6. See Also
+
+A modified version of Jot 1.1.3 is available, which integrates with HTML
+Purifier. You can check it out here: <http://modxcms.com/forums/index.php/topic,25621.msg161970.html>
+
+
+X. Changelog
+
+2008-06-16
+- Updated code to work with 3.1.0 and later
+- Add Known Bugs and See Also section
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore b/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore
new file mode 100644
index 0000000..8325e09
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore
@@ -0,0 +1,2 @@
+migrate.php
+htmlpurifier/*
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog b/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog
new file mode 100644
index 0000000..9f939e5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog
@@ -0,0 +1,27 @@
+Changelog HTMLPurifier : Phorum Mod
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+= KEY ====================
+ # Breaks back-compat
+ ! Feature
+ - Bugfix
+ + Sub-comment
+ . Internal change
+==========================
+
+Version 4.0.0 for Phorum 5.2, released July 9, 2009
+# Works only with HTML Purifier 4.0.0
+! Better installation documentation
+- Fixed double encoded quotes
+- Fixed fatal error when migrate.php is blank
+
+Version 3.0.0 for Phorum 5.2, released January 12, 2008
+# WYSIWYG and suppress_message options are now configurable via web
+ interface.
+- Module now compatible with Phorum 5.2, primary bugs were in migration
+ code as well as signature and edit message handling. This module is NOT
+ compatible with Phorum 5.1.
+- Buggy WYSIWYG mode refined
+. AutoFormatParam added to list of default configuration namespaces
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL b/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL
new file mode 100644
index 0000000..23c76fc
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL
@@ -0,0 +1,84 @@
+
+Install
+ How to install the Phorum HTML Purifier plugin
+
+0. PREREQUISITES
+----------------
+This Phorum module only works on PHP5 and with HTML Purifier 4.0.0
+or later.
+
+1. UNZIP
+--------
+Unzip phorum-htmlpurifier-x.y.z, producing an htmlpurifier folder.
+You've already done this step if you're reading this!
+
+2. MOVE
+-------
+Move the htmlpurifier folder to the mods/ folder of your Phorum
+installation, so the directory structure looks like:
+
+phorum/
+ mods/
+ htmlpurifier/
+ INSTALL - this install file
+ info.txt, ... - the module files
+ htmlpurifier/
+
+3. INSTALL HTML PURIFIER
+------------------------
+Download and unzip HTML Purifier <htmlpurifier.org>. Place the contents of
+the library/ folder in the htmlpurifier/htmlpurifier folder. Your directory
+structure will look like:
+
+phorum/
+ mods/
+ htmlpurifier/
+ htmlpurifier/
+ HTMLPurifier.auto.php
+ ... - other files
+ HTMLPurifier/
+
+Advanced users:
+ If you have HTML Purifier installed elsewhere on your server,
+ all you need is an HTMLPurifier.auto.php file in the library folder which
+ includes the HTMLPurifier.auto.php file in your install.
+
+4. MIGRATE
+----------
+If you're setting up a new Phorum installation, all you need to do is create
+a blank migrate.php file in the htmlpurifier module folder (NOT the library
+folder.
+
+If you have an old Phorum installation and was using BBCode,
+copy migrate.bbcode.php to migrate.php. If you were using a different input
+format, follow the instructions in migrate.bbcode.php to create your own custom
+migrate.php file.
+
+Your directory structure should now look like this:
+
+phorum/
+ mods/
+ htmlpurifier/
+ migrate.php
+
+5. ENABLE
+---------
+Navigate to your Phorum admin panel at http://example.com/phorum/admin.php,
+click on Global Settings > Modules, scroll to "HTML Purifier Phorum Mod" and
+turn it On.
+
+6. MIGRATE SIGNATURES
+---------------------
+If you're setting up a new Phorum installation, skip this step.
+
+If you allowed your users to make signatures, navigate to the module settings
+page of HTML Purifier (Global Settings > Modules > HTML Purifier Phorum Mod >
+Configure), type in "yes" in the "Confirm" box, and press "Migrate."
+
+ONLY DO THIS ONCE! BE SURE TO BACK UP YOUR DATABASE!
+
+7. CONFIGURE
+------------
+Configure using Edit settings. See that page for more information.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/README b/vendor/ezyang/htmlpurifier/plugins/phorum/README
new file mode 100644
index 0000000..0524ed3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/README
@@ -0,0 +1,45 @@
+
+HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way!
+
+This Phorum mod enables HTML posting on Phorum. Under normal circumstances,
+this would cause a huge security risk, but because we are running
+HTML through HTML Purifier, output is guaranteed to be XSS free and
+standards-compliant.
+
+This mod requires HTML input, and previous markup languages need to be
+converted accordingly. Thus, it is vital that you create a 'migrate.php'
+file that works with your installation. If you're using the built-in
+BBCode formatting, simply move migrate.bbcode.php to that place; for
+other markup languages, consult said file for instructions on how
+to adapt it to your needs.
+
+ -- NOTE -------------------------------------------------
+ You can also run this module in parallel with another
+ formatting module; this module attempts to place itself
+ at the end of the filtering chain. However, if any
+ previous modules produce insecure HTML (for instance,
+ a JavaScript email obfuscator) they will get cleaned.
+
+This module will not work if 'migrate.php' is not created, and an improperly
+made migration file may *CORRUPT* Phorum, so please take your time to
+do this correctly. It should go without saying to *BACKUP YOUR DATABASE*
+before attempting anything here. If no migration is necessary, you can
+simply create a blank migrate.php file. HTML Purifier is smart and will
+not re-migrate already processed messages. However, the original code
+is irretrievably lost (we may change this in the future.)
+
+This module will not automatically migrate user signatures, because this
+process may take a long time. After installing the HTML Purifier module and
+then configuring 'migrate.php', navigate to Settings and click 'Migrate
+Signatures' to migrate all user signatures to HTML.
+
+All of HTML Purifier's usual functions are configurable via the mod settings
+page. If you require custom configuration, create config.php file in
+the mod directory that edits a $config variable. Be sure, also, to
+set $PHORUM['mod_htmlpurifier']['wysiwyg'] to TRUE if you are using a
+WYSIWYG editor (you can do this through a common hook or the web
+configuration form).
+
+Visit HTML Purifier at <http://htmlpurifier.org/>.
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php b/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php
new file mode 100644
index 0000000..e047c0b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php
@@ -0,0 +1,57 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php b/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php
new file mode 100644
index 0000000..f66d8c3
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php
@@ -0,0 +1,316 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt b/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt
new file mode 100644
index 0000000..7234654
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt
@@ -0,0 +1,18 @@
+title: HTML Purifier Phorum Mod
+desc: This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod.
+author: Edward Z. Yang
+url: http://htmlpurifier.org/
+version: 4.0.0
+
+hook: format|phorum_htmlpurifier_format
+hook: quote|phorum_htmlpurifier_quote
+hook: posting_custom_action|phorum_htmlpurifier_posting
+hook: common|phorum_htmlpurifier_common
+hook: before_editor|phorum_htmlpurifier_before_editor
+hook: tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject
+
+# This module is meant to be a drop-in for bbcode, so make it run last.
+priority: run module after *
+priority: run hook format after *
+
+ vim: et sw=4 sts=4
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php b/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php
new file mode 100644
index 0000000..e19787b
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php
@@ -0,0 +1,30 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php b/vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php
new file mode 100644
index 0000000..0d09194
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php
@@ -0,0 +1,31 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/settings.php b/vendor/ezyang/htmlpurifier/plugins/phorum/settings.php
new file mode 100644
index 0000000..8158f02
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/settings.php
@@ -0,0 +1,64 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php
new file mode 100644
index 0000000..9b6ad5f
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php
@@ -0,0 +1,95 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php
new file mode 100644
index 0000000..abea3b5
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php
@@ -0,0 +1,22 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php
new file mode 100644
index 0000000..5ea9cd0
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php
@@ -0,0 +1,79 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php
new file mode 100644
index 0000000..2aefaf8
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php
@@ -0,0 +1,29 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/release1-update.php b/vendor/ezyang/htmlpurifier/release1-update.php
new file mode 100644
index 0000000..834d385
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/release1-update.php
@@ -0,0 +1,110 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/release2-tag.php b/vendor/ezyang/htmlpurifier/release2-tag.php
new file mode 100644
index 0000000..25e5300
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/release2-tag.php
@@ -0,0 +1,22 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/test-settings.sample.php b/vendor/ezyang/htmlpurifier/test-settings.sample.php
new file mode 100644
index 0000000..480b662
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/test-settings.sample.php
@@ -0,0 +1,74 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/test-settings.travis.php b/vendor/ezyang/htmlpurifier/test-settings.travis.php
new file mode 100644
index 0000000..b1edce4
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/test-settings.travis.php
@@ -0,0 +1,72 @@
+<?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
diff --git a/vendor/ezyang/htmlpurifier/tests/path2class.func.php b/vendor/ezyang/htmlpurifier/tests/path2class.func.php
new file mode 100644
index 0000000..bf3aa73
--- /dev/null
+++ b/vendor/ezyang/htmlpurifier/tests/path2class.func.php
@@ -0,0 +1,15 @@
+<?php
+
+function path2class($path)
+{
+ $temp = $path;
+ $temp = str_replace('./', '', $temp); // remove leading './'
+ $temp = str_replace('.\\', '', $temp); // remove leading '.\'
+ $temp = str_replace('\\', '_', $temp); // normalize \ to _
+ $temp = str_replace('/', '_', $temp); // normalize / to _
+ while(strpos($temp, '__') !== false) $temp = str_replace('__', '_', $temp);
+ $temp = str_replace('.php', '', $temp);
+ return $temp;
+}
+
+// vim: et sw=4 sts=4
diff --git a/vendor/guzzle/guzzle/.gitignore b/vendor/guzzle/guzzle/.gitignore
new file mode 100644
index 0000000..893035d
--- /dev/null
+++ b/vendor/guzzle/guzzle/.gitignore
@@ -0,0 +1,27 @@
+# Ingore common cruft
+.DS_STORE
+coverage
+.idea
+
+# Ignore binary files
+guzzle.phar
+guzzle-min.phar
+
+# Ignore potentially sensitive phpunit file
+phpunit.xml
+
+# Ignore composer generated files
+composer.phar
+composer.lock
+composer-test.lock
+vendor/
+
+# Ignore build files
+build/
+phing/build.properties
+
+# Ignore subsplit working directory
+.subsplit
+
+docs/_build
+docs/*.pyc
diff --git a/vendor/guzzle/guzzle/.travis.yml b/vendor/guzzle/guzzle/.travis.yml
new file mode 100644
index 0000000..209e05c
--- /dev/null
+++ b/vendor/guzzle/guzzle/.travis.yml
@@ -0,0 +1,17 @@
+language: php
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm
+
+before_script:
+ - curl --version
+ - pecl install uri_template-beta || echo "pecl uri_template not available"
+ - composer self-update
+ - composer install --no-interaction --prefer-source --dev
+ - ~/.nvm/nvm.sh install v0.6.14
+
+script: composer test
diff --git a/vendor/guzzle/guzzle/CHANGELOG.md b/vendor/guzzle/guzzle/CHANGELOG.md
new file mode 100644
index 0000000..f0dc544
--- /dev/null
+++ b/vendor/guzzle/guzzle/CHANGELOG.md
@@ -0,0 +1,751 @@
+# CHANGELOG
+
+## 3.9.3 - 2015-03-18
+
+* Ensuring Content-Length is not stripped from a request when it is `0`.
+* Added more information to stream wrapper exceptions.
+* Message parser will no longer throw warnings for malformed messages.
+* Giving a valid cache TTL when max-age is 0.
+
+## 3.9.2 - 2014-09-10
+
+* Retrying "Connection died, retrying a fresh connect" curl errors.
+* Automatically extracting the cacert from the phar in client constructor.
+* Added EntityBody support for OPTIONS requests.
+
+## 3.9.1 - 2014-05-07
+
+* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop.
+* Added a fix to the stream checksum function so that when the first read
+ returns a falsey value, it still continues to consume the stream until EOF.
+
+## 3.9.0 - 2014-04-23
+
+* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value
+ with no trailing "=". See dc1d824277.
+* No longer performing an MD5 check on the cacert each time the phar is used,
+ but rather copying the cacert to the temp directory.
+* `"0"` can now be added as a URL path
+* Deleting cookies that are set to empty
+* If-Modified-Since is no longer unnecessarily added to the CachePlugin
+* Cookie path matching now follows RFC 6265 s5.1.4
+* Updated service descriptions are now added to a service client's composite
+ factory.
+* MockPlugin now throws an exception if the queue is empty.
+* Properly parsing URLs that start with "http" but are not absolute
+* Added the ability to configure the curl_multi_select timeout setting
+* OAuth parameters are now sorted using lexicographical byte value ordering
+* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin
+
+## 3.8.1 -2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()``
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
+* Bug: ``+`` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/vendor/guzzle/guzzle/LICENSE b/vendor/guzzle/guzzle/LICENSE
new file mode 100644
index 0000000..d51aa69
--- /dev/null
+++ b/vendor/guzzle/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
+
+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.
diff --git a/vendor/guzzle/guzzle/README.md b/vendor/guzzle/guzzle/README.md
new file mode 100644
index 0000000..6be06bf
--- /dev/null
+++ b/vendor/guzzle/guzzle/README.md
@@ -0,0 +1,57 @@
+Guzzle, PHP HTTP client and webservice framework
+================================================
+
+# This is an old version of Guzzle
+
+This repository is for Guzzle 3.x. Guzzle 5.x, the new version of Guzzle, has
+been released and is available at
+[https://github.com/guzzle/guzzle](https://github.com/guzzle/guzzle). The
+documentation for Guzzle version 5+ can be found at
+[http://guzzlephp.org](http://guzzlephp.org).
+
+Guzzle 3 is only maintained for bug and security fixes. Guzzle 3 will be EOL
+at some point in late 2015.
+
+### About Guzzle 3
+
+[![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle)
+ [![Build Status](https://secure.travis-ci.org/guzzle/guzzle3.png?branch=master)](http://travis-ci.org/guzzle/guzzle3)
+
+- Extremely powerful API provides all the power of cURL with a simple interface.
+- Truly take advantage of HTTP/1.1 with persistent connections, connection pooling, and parallel requests.
+- Service description DSL allows you build awesome web service clients faster.
+- Symfony2 event-based plugin system allows you to completely modify the behavior of a request.
+
+Get answers with: [Documentation](http://guzzle3.readthedocs.org/en/latest/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), IRC ([#guzzlephp](irc://irc.freenode.net/#guzzlephp) @ irc.freenode.net)
+
+### Installing via Composer
+
+The recommended way to install Guzzle is through [Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+
+# Add Guzzle as a dependency
+php composer.phar require guzzle/guzzle:~3.9
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+## Known Issues
+
+1. Problem following a specific redirect: https://github.com/guzzle/guzzle/issues/385.
+ This has been fixed in Guzzle 4/5.
+2. Root XML attributes not serialized in a service description: https://github.com/guzzle/guzzle3/issues/5.
+ This has been fixed in Guzzle 4/5.
+3. Accept-Encoding not preserved when following redirect: https://github.com/guzzle/guzzle3/issues/9
+ Fixed in Guzzle 4/5.
+4. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
+ Fixed in Guzzle 4/5.
+5. Recursive model references with array items: https://github.com/guzzle/guzzle3/issues/13
+ Fixed in Guzzle 4/5
+6. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
+ Fixed in Guzzle 4/5.
diff --git a/vendor/guzzle/guzzle/UPGRADING.md b/vendor/guzzle/guzzle/UPGRADING.md
new file mode 100644
index 0000000..f58bf11
--- /dev/null
+++ b/vendor/guzzle/guzzle/UPGRADING.md
@@ -0,0 +1,537 @@
+Guzzle Upgrade Guide
+====================
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<client>
+ <commands>
+ <!-- Groups -->
+ <command name="list_groups" method="GET" uri="groups.json">
+ <doc>Get a list of groups</doc>
+ </command>
+ <command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
+ <doc>Uses a search query to get a list of groups</doc>
+ <param name="query" type="string" required="true" />
+ </command>
+ <command name="create_group" method="POST" uri="groups.json">
+ <doc>Create a group</doc>
+ <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
+ <param name="Content-Type" location="header" static="application/json"/>
+ </command>
+ <command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
+ <doc>Delete a group by ID</doc>
+ <param name="id" type="integer" required="true"/>
+ </command>
+ <command name="get_group" method="GET" uri="groups/{{id}}.json">
+ <param name="id" type="integer" required="true"/>
+ </command>
+ <command name="update_group" method="PUT" uri="groups/{{id}}.json">
+ <doc>Update a group</doc>
+ <param name="id" type="integer" required="true"/>
+ <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
+ <param name="Content-Type" location="header" static="application/json"/>
+ </command>
+ </commands>
+</client>
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/vendor/guzzle/guzzle/build.xml b/vendor/guzzle/guzzle/build.xml
new file mode 100644
index 0000000..2aa62ba
--- /dev/null
+++ b/vendor/guzzle/guzzle/build.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="guzzle" default="test">
+ <!-- set local values, like git location -->
+ <property file="phing/build.properties.dist" override="true" />
+ <property file="phing/build.properties" override="true" />
+
+ <property name="dir.output" value="${project.basedir}/build/artifacts" />
+ <property name="dir.imports" value="${project.basedir}/phing/imports" />
+ <property name="dir.bin" value="${project.basedir}/bin" />
+ <property name="repo.dir" value="${project.basedir}" />
+
+ <import file="${dir.imports}/dependencies.xml"/>
+ <import file="${dir.imports}/deploy.xml"/>
+
+ <target name="composer-lint" description="lint-check composer.json only">
+ <composerlint dir="${project.basedir}/src" file="{$project.basedir}/composer.json" />
+ </target>
+
+ <target name="test" description="Run unit tests">
+ <exec passthru="true" command="vendor/bin/phpunit" checkReturn="true" />
+ </target>
+
+ <target name="build-init" description="Initialize local phing properties">
+ <copy file="phing/build.properties.dist" tofile="phing/build.properties" overwrite="false" />
+ </target>
+
+ <target name="clean">
+ <delete dir="${dir.output}"/>
+ <delete dir="${project.basedir}/build/pearwork"/>
+ </target>
+
+ <target name="prepare" depends="clean,build-init">
+ <mkdir dir="${dir.output}"/>
+ <mkdir dir="${dir.output}/logs" />
+ </target>
+
+ <target name="coverage" depends="prepare">
+ <exec passthru="true" command="vendor/bin/phpunit --coverage-html=${dir.output}/coverage" />
+ </target>
+
+ <target name="view-coverage">
+ <exec passthru="true" command="open ${dir.output}/coverage/index.html" />
+ </target>
+
+</project>
diff --git a/vendor/guzzle/guzzle/composer.json b/vendor/guzzle/guzzle/composer.json
new file mode 100644
index 0000000..59424b3
--- /dev/null
+++ b/vendor/guzzle/guzzle/composer.json
@@ -0,0 +1,82 @@
+{
+ "name": "guzzle/guzzle",
+ "type": "library",
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+
+ "require": {
+ "php": ">=5.3.3",
+ "ext-curl": "*",
+ "symfony/event-dispatcher": "~2.1"
+ },
+
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+
+ "scripts": {
+ "test": "phpunit"
+ },
+
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "symfony/class-loader": "~2.1",
+ "monolog/monolog": "~1.0",
+ "psr/log": "~1.0",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3",
+ "phpunit/phpunit": "3.7.*"
+ },
+
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/docs/Makefile b/vendor/guzzle/guzzle/docs/Makefile
new file mode 100644
index 0000000..d92e03f
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json b/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json
new file mode 100644
index 0000000..8168302
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json
@@ -0,0 +1,176 @@
+{
+ "additionalProperties": true,
+ "name": {
+ "type": "string",
+ "description": "Name of the web service"
+ },
+ "apiVersion": {
+ "type": ["string", "number"],
+ "description": "Version identifier that the service description is compatible with"
+ },
+ "baseUrl": {
+ "type": "string",
+ "description": "Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the process defined in RFC 2396"
+ },
+ "basePath": {
+ "type": "string",
+ "description": "Alias of baseUrl"
+ },
+ "_description": {
+ "type": "string",
+ "description": "Short summary of the web service. This is actually called 'description' but this JSON schema wont validate using just description."
+ },
+ "operations": {
+ "description": "Operations of the web service",
+ "type": "object",
+ "properties": {
+ "extends": {
+ "type": "string",
+ "description": "Extend from another operation by name. The parent operation must be defined before the child."
+ },
+ "httpMethod": {
+ "type": "string",
+ "description": "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)"
+ },
+ "uri": {
+ "type": "string",
+ "description": "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri"
+ },
+ "summary": {
+ "type": "string",
+ "description": "Short summary of what the operation does"
+ },
+ "class": {
+ "type": "string",
+ "description": "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand"
+ },
+ "responseClass": {
+ "type": "string",
+ "description": "This is what is returned from the method. Can be a primitive, class name, or model name."
+ },
+ "responseNotes": {
+ "type": "string",
+ "description": "A description of the response returned by the operation"
+ },
+ "responseType": {
+ "type": "string",
+ "description": "The type of response that the operation creates. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default.",
+ "enum": [ "primitive", "class", "model", "documentation" ]
+ },
+ "deprecated": {
+ "type": "boolean",
+ "description": "Whether or not the operation is deprecated"
+ },
+ "errorResponses": {
+ "description": "Errors that could occur while executing the operation",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "number",
+ "description": "HTTP response status code of the error"
+ },
+ "reason": {
+ "type": "string",
+ "description": "Response reason phrase or description of the error"
+ },
+ "class": {
+ "type": "string",
+ "description": "A custom exception class that would be thrown if the error is encountered"
+ }
+ }
+ }
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": "true"
+ },
+ "parameters": {
+ "$ref": "parameters",
+ "description": "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request."
+ },
+ "additionalParameters": {
+ "$ref": "parameters",
+ "description": "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined."
+ }
+ }
+ },
+ "models": {
+ "description": "Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP response is parsed into a Guzzle\\Service\\Resource\\Model object.",
+ "type": "object",
+ "properties": {
+ "$ref": "parameters",
+ "description": "Parameters of the model. When a model is referenced in a responseClass attribute of an operation, parameters define how a HTTP response message is parsed into a Guzzle\\Service\\Resource\\Model."
+ }
+ },
+ "includes": {
+ "description": "Service description files to include and extend from (can be a .json, .js, or .php file)",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": ".+\\.(js|json|php)$"
+ }
+ },
+ "definitions": {
+ "parameters": {
+ "extends": "http://json-schema.org/schema",
+ "id": "parameters",
+ "name": {
+ "type": "string",
+ "description": "Unique name of the parameter"
+ },
+ "type": {
+ "type": ["string", "array"],
+ "description": "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid."
+ },
+ "instanceOf": {
+ "type": "string",
+ "description": "When the type is an object, you can specify the class that the object must implement"
+ },
+ "required": {
+ "type": "boolean",
+ "description": "Whether or not the parameter is required"
+ },
+ "default": {
+ "description": "Default value to use if no value is supplied"
+ },
+ "static": {
+ "type": "bool",
+ "description": "Set to true to specify that the parameter value cannot be changed from the default setting"
+ },
+ "description": {
+ "type": "string",
+ "description": "Documentation of the parameter"
+ },
+ "location": {
+ "type": "string",
+ "description": "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody"
+ },
+ "sentAs": {
+ "type": "string",
+ "description": "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar."
+ },
+ "filters": {
+ "type": "array",
+ "description": "Array of static method names to to run a parameter value through. Each value in the array must be a string containing the full class path to a static method or an array of complex filter information. You can specify static methods of classes using the full namespace class name followed by ‘::’ (e.g. FooBar::baz()). Some filters require arguments in order to properly filter a value. For complex filters, use a hash containing a ‘method’ key pointing to a static method, and an ‘args’ key containing an array of positional arguments to pass to the method. Arguments can contain keywords that are replaced when filtering a value: '@value‘ is replaced with the value being validated, '@api‘ is replaced with the Parameter object.",
+ "items": {
+ "type": ["string", {
+ "object": {
+ "properties": {
+ "method": {
+ "type": "string",
+ "description": "PHP function to call",
+ "required": true
+ },
+ "args": {
+ "type": "array"
+ }
+ }
+ }
+ }]
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png b/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png
new file mode 100644
index 0000000..f1017f7
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png
Binary files differ
diff --git a/vendor/guzzle/guzzle/docs/_static/homepage.css b/vendor/guzzle/guzzle/docs/_static/homepage.css
new file mode 100644
index 0000000..70c46d8
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_static/homepage.css
@@ -0,0 +1,122 @@
+/* Hero unit on homepage */
+
+.hero-unit h1 {
+ font-size: 49px;
+ margin-bottom: 12px;
+}
+
+.hero-unit {
+ padding: 40px;
+}
+
+.hero-unit p {
+ font-size: 17px;
+}
+
+.masthead img {
+ float: left;
+ margin-right: 17px;
+}
+
+.hero-unit ul li {
+ margin-left: 220px;
+}
+
+.hero-unit .buttons {
+ text-align: center;
+}
+
+.jumbotron {
+ position: relative;
+ padding: 40px 0;
+ color: #fff;
+ text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075);
+ background: #00312F;
+ background: -moz-linear-gradient(45deg, #002F31 0%, #335A6D 100%);
+ background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#00312D), color-stop(100%,#33566D));
+ background: -webkit-linear-gradient(45deg, #020031 0%,#334F6D 100%);
+ background: -o-linear-gradient(45deg, #002D31 0%,#334D6D 100%);
+ background: -ms-linear-gradient(45deg, #002F31 0%,#33516D 100%);
+ background: linear-gradient(45deg, #020031 0%,#33516D 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 );
+ -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2);
+ -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
+ box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2);
+}
+
+.jumbotron h1 {
+ font-size: 80px;
+ font-weight: bold;
+ letter-spacing: -1px;
+ line-height: 1;
+}
+
+.jumbotron p {
+ font-size: 24px;
+ font-weight: 300;
+ line-height: 1.25;
+ margin-bottom: 30px;
+}
+
+.masthead {
+ padding: 40px 0 30px;
+ margin-bottom: 0;
+ color: #fff;
+ margin-top: -19px;
+}
+
+.masthead h1 {
+ display: none;
+}
+
+.masthead p {
+ font-size: 40px;
+ font-weight: 200;
+ line-height: 1.25;
+ margin: 12px 0 0 0;
+}
+
+.masthead .btn {
+ padding: 19px 24px;
+ font-size: 24px;
+ font-weight: 200;
+ border: 0;
+}
+
+/* Social bar on homepage */
+
+.social {
+ padding: 2px 0;
+ text-align: center;
+ background-color: #f5f5f5;
+ border-top: 1px solid #fff;
+ border-bottom: 1px solid #ddd;
+ margin: 0 0 20px 0;
+}
+
+.social ul {
+ margin-top: 0;
+}
+
+.social-buttons {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+}
+
+.social-buttons li {
+ display: inline-block;
+ padding: 5px 8px;
+ line-height: 1;
+ *display: inline;
+ *zoom: 1;
+}
+
+.center-announcement {
+ padding: 10px;
+ background-color: rgb(238, 243, 255);
+ border-radius: 8px;
+ text-align: center;
+ margin: 24px 0;
+}
diff --git a/vendor/guzzle/guzzle/docs/_static/logo.png b/vendor/guzzle/guzzle/docs/_static/logo.png
new file mode 100644
index 0000000..965a4ef
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_static/logo.png
Binary files differ
diff --git a/vendor/guzzle/guzzle/docs/_static/prettify.css b/vendor/guzzle/guzzle/docs/_static/prettify.css
new file mode 100644
index 0000000..4d410b1
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_static/prettify.css
@@ -0,0 +1,41 @@
+.com {
+ color: #93A1A1;
+}
+.lit {
+ color: #195F91;
+}
+.pun, .opn, .clo {
+ color: #93A1A1;
+}
+.fun {
+ color: #DC322F;
+}
+.str, .atv {
+ color: #DD1144;
+}
+.kwd, .linenums .tag {
+ color: #1E347B;
+}
+.typ, .atn, .dec, .var {
+ color: teal;
+}
+.pln {
+ color: #48484C;
+}
+.prettyprint {
+ background-color: #F7F7F9;
+ border: 1px solid #E1E1E8;
+ padding: 8px;
+}
+.prettyprint.linenums {
+ box-shadow: 40px 0 0 #FBFBFC inset, 41px 0 0 #ECECF0 inset;
+}
+ol.linenums {
+ margin: 0 0 0 33px;
+}
+ol.linenums li {
+ color: #BEBEC5;
+ line-height: 18px;
+ padding-left: 12px;
+ text-shadow: 0 1px 0 #FFFFFF;
+}
diff --git a/vendor/guzzle/guzzle/docs/_static/prettify.js b/vendor/guzzle/guzzle/docs/_static/prettify.js
new file mode 100644
index 0000000..eef5ad7
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_static/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/vendor/guzzle/guzzle/docs/_templates/index.html b/vendor/guzzle/guzzle/docs/_templates/index.html
new file mode 100644
index 0000000..2bbfd6f
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_templates/index.html
@@ -0,0 +1,106 @@
+<script type="text/javascript" src="{{ pathto('_static/prettify.js', 1) }}"></script>
+<link rel="stylesheet" type="text/css" href="{{ pathto('_static/prettify.css', 1) }}" />
+<link rel="stylesheet" type="text/css" href="{{ pathto('_static/homepage.css', 1) }}" />
+
+<div class="jumbotron masthead">
+ <div class="container">
+ <img src="{{ pathto('_static/logo.png', 1) }}" alt="guzzle" width="199" height="260" />
+ <h1>Guzzle</h1>
+ <p>Guzzle is a PHP HTTP client<br />&amp; framework for building RESTful web service clients.</p>
+ <p>
+ <a class="btn btn-primary btn-lg" href="https://github.com/guzzle/guzzle">View Guzzle on GitHub</a>
+ <a class="btn btn-default btn-lg" href="{{ pathto('docs') }}">Read the docs</a>
+ </p>
+ </div>
+</div>
+
+<div class="social">
+ <ul class="social-buttons">
+ <li>
+ <iframe src="http://ghbtns.com/github-btn.html?user=guzzle&repo=guzzle&type=watch&count=true"
+ allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
+ </li>
+ <li>
+ <a href="https://twitter.com/share" class="twitter-share-button" data-url="http://guzzlephp.org" data-text="Guzzle, PHP HTTP client &amp; framework for building RESTful web service clients" data-via="mtdowling">Tweet</a>
+ <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="http://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
+ </li>
+ <li>
+ <a href="https://twitter.com/mtdowling" class="twitter-follow-button" data-show-count="false">Follow @mtdowling</a>
+ <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="http://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
+ </li>
+ </ul>
+</div>
+
+<div class="container">
+
+ <h1>Introducing Guzzle</h1>
+
+ <p>Guzzle takes the pain out of sending HTTP requests and the redundancy out of creating web service clients. It's
+ a framework that includes the tools needed to create a robust web service client, including:
+ Service descriptions for defining the inputs and outputs of an API, resource iterators for traversing
+ paginated resources, batching for sending a large number of requests as efficiently as possible.</p>
+
+ <ul>
+ <li>All the power of cURL with a simple interface.</li>
+ <li>Persistent connections and parallel requests.</li>
+ <li>Streams request and response bodies</li>
+ <li><a href="{{ pathto('webservice-client/guzzle-service-descriptions') }}">Service descriptions</a> for quickly building clients.</li>
+ <li>Powered by the Symfony2 EventDispatcher.</li>
+ <li>Use all of the code or only <a href="https://packagist.org/packages/guzzle/">specific components</a>.</li>
+ <li><a href="{{ pathto('plugins/plugins-overview') }}">Plugins</a> for caching, logging, OAuth, mocks, and more</li>
+ <li>Includes a custom node.js webserver to <a href="{{ pathto('testing/unit-testing') }}">test your clients</a>.</li>
+ </ul>
+
+ <div class="center-announcement">
+ Guzzle is now part of Drupal 8 core and powers the official <a href="https://github.com/aws/aws-sdk-php">AWS SDK for PHP</a>
+ </div>
+
+ <h2>GitHub Example</h2>
+
+ <pre class="prettyprint">&lt;?php
+require_once 'vendor/autoload.php';
+use Guzzle\Http\Client;
+
+// Create a client and provide a base URL
+$client = new Client('https://api.github.com');
+// Create a request with basic Auth
+$request = $client->get('/user')->setAuth('user', 'pass');
+// Send the request and get the response
+$response = $request->send();
+echo $response->getBody();
+// >>> {"type":"User", ...
+echo $response->getHeader('Content-Length');
+// >>> 792
+</pre>
+
+ <h2>Twitter Example</h2>
+ <pre class="prettyprint">&lt;?php
+// Create a client to work with the Twitter API
+$client = new Client('https://api.twitter.com/{version}', array(
+ 'version' => '1.1'
+));
+
+// Sign all requests with the OauthPlugin
+$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
+ 'consumer_key' => '***',
+ 'consumer_secret' => '***',
+ 'token' => '***',
+ 'token_secret' => '***'
+)));
+
+echo $client->get('statuses/user_timeline.json')->send()->getBody();
+// >>> {"public_gists":6,"type":"User" ...
+
+// Create a tweet using POST
+$request = $client->post('statuses/update.json', null, array(
+ 'status' => 'Tweeted with Guzzle, http://guzzlephp.org'
+));
+
+// Send the request and parse the JSON response into an array
+$data = $request->send()->json();
+echo $data['text'];
+// >>> Tweeted with Guzzle, http://t.co/kngJMfRk
+</pre>
+</div>
+
+<script type="text/javascript">prettyPrint();</script>
diff --git a/vendor/guzzle/guzzle/docs/_templates/leftbar.html b/vendor/guzzle/guzzle/docs/_templates/leftbar.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_templates/leftbar.html
diff --git a/vendor/guzzle/guzzle/docs/_templates/nav_links.html b/vendor/guzzle/guzzle/docs/_templates/nav_links.html
new file mode 100644
index 0000000..d4f2165
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/_templates/nav_links.html
@@ -0,0 +1,5 @@
+<li><a href="{{ pathto('docs') }}">Docs</a></li>
+<li><a href="http://guzzlephp.org/api/index.html">API</a></li>
+<li><a href="https://github.com/guzzle/guzzle">GitHub</a></li>
+<li><a href="https://groups.google.com/forum/?hl=en#!forum/guzzle">Forum</a></li>
+<li><a href="irc:irc.freenode.com/#guzzlephp">IRC</a></li>
diff --git a/vendor/guzzle/guzzle/docs/batching/batching.rst b/vendor/guzzle/guzzle/docs/batching/batching.rst
new file mode 100644
index 0000000..57f04d8
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/batching/batching.rst
@@ -0,0 +1,183 @@
+========
+Batching
+========
+
+Guzzle provides a fairly generic and very customizable batching framework that allows developers to efficiently
+transfer requests in parallel.
+
+Sending requests and commands in parallel
+-----------------------------------------
+
+You can send HTTP requests in parallel by passing an array of ``Guzzle\Http\Message\RequestInterface`` objects to
+``Guzzle\Http\Client::send()``:
+
+.. code-block:: php
+
+ $responses = $client->send(array(
+ $client->get('http://www.example.com/foo'),
+ $client->get('http://www.example.com/baz')
+ $client->get('http://www.example.com/bar')
+ ));
+
+You can send commands in parallel by passing an array of ``Guzzle\Service\Command\CommandInterface`` objects
+``Guzzle\Service\Client::execute()``:
+
+.. code-block:: php
+
+ $commands = $client->execute(array(
+ $client->getCommand('foo'),
+ $client->getCommand('baz'),
+ $client->getCommand('bar')
+ ));
+
+These approaches work well for most use-cases. When you need more control over the requests that are sent in
+parallel or you need to send a large number of requests, you need to use the functionality provided in the
+``Guzzle\Batch`` namespace.
+
+Batching overview
+-----------------
+
+The batch object, ``Guzzle\Batch\Batch``, is a queue. You add requests to the queue until you are ready to transfer
+all of the requests. In order to efficiently transfer the items in the queue, the batch object delegates the
+responsibility of dividing the queue into manageable parts to a divisor (``Guzzle\Batch\BatchDivisorInterface``).
+The batch object then iterates over each array of items created by the divisor and sends them to the batch object's
+``Guzzle\Batch\BatchTransferInterface``.
+
+.. code-block:: php
+
+ use Guzzle\Batch\Batch;
+ use Guzzle\Http\BatchRequestTransfer;
+
+ // BatchRequestTransfer acts as both the divisor and transfer strategy
+ $transferStrategy = new BatchRequestTransfer(10);
+ $divisorStrategy = $transferStrategy;
+
+ $batch = new Batch($transferStrategy, $divisorStrategy);
+
+ // Add some requests to the batch queue
+ $batch->add($request1)
+ ->add($request2)
+ ->add($request3);
+
+ // Flush the queue and retrieve the flushed items
+ $arrayOfTransferredRequests = $batch->flush();
+
+.. note::
+
+ You might find that your transfer strategy will need to act as both the divisor and transfer strategy.
+
+Using the BatchBuilder
+----------------------
+
+The ``Guzzle\Batch\BatchBuilder`` makes it easier to create batch objects. The batch builder also provides an easier
+way to add additional behaviors to your batch object.
+
+Transferring requests
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``Guzzle\Http\BatchRequestTransfer`` class efficiently transfers HTTP requests in parallel by grouping batches of
+requests by the curl_multi handle that is used to transfer the requests.
+
+.. code-block:: php
+
+ use Guzzle\Batch\BatchBuilder;
+
+ $batch = BatchBuilder::factory()
+ ->transferRequests(10)
+ ->build();
+
+Transferring commands
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``Guzzle\Service\Command\BatchCommandTransfer`` class efficiently transfers service commands by grouping commands
+by the client that is used to transfer them. You can add commands to a batch object that are transferred by different
+clients, and the batch will handle the rest.
+
+.. code-block:: php
+
+ use Guzzle\Batch\BatchBuilder;
+
+ $batch = BatchBuilder::factory()
+ ->transferCommands(10)
+ ->build();
+
+ $batch->add($client->getCommand('foo'))
+ ->add($client->getCommand('baz'))
+ ->add($client->getCommand('bar'));
+
+ $commands = $batch->flush();
+
+Batch behaviors
+---------------
+
+You can add various behaviors to your batch that allow for more customizable transfers.
+
+Automatically flushing a queue
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``Guzzle\Batch\FlushingBatch`` decorator when you want to pump a large number of items into a batch queue and
+have the queue automatically flush when the size of the queue reaches a certain threshold.
+
+.. code-block:: php
+
+ use Guzzle\Batch\BatchBuilder;
+
+ $batch = BatchBuilder::factory()
+ ->transferRequests(10)
+ ->autoFlushAt(10)
+ ->build();
+
+Batch builder method: ``autoFlushAt($threshold)``
+
+Notifying on flush
+~~~~~~~~~~~~~~~~~~
+
+Use the ``Guzzle\Batch\NotifyingBatch`` decorator if you want a function to be notified each time the batch queue is
+flushed. This is useful when paired with the flushing batch decorator. Pass a callable to the ``notify()`` method of
+a batch builder to use this decorator with the builder.
+
+.. code-block:: php
+
+ use Guzzle\Batch\BatchBuilder;
+
+ $batch = BatchBuilder::factory()
+ ->transferRequests(10)
+ ->autoFlushAt(10)
+ ->notify(function (array $transferredItems) {
+ echo 'Transferred ' . count($transferredItems) . "items\n";
+ })
+ ->build();
+
+Batch builder method:: ``notify(callable $callback)``
+
+Keeping a history
+~~~~~~~~~~~~~~~~~
+
+Use the ``Guzzle\Batch\HistoryBatch`` decorator if you want to maintain a history of all the items transferred with
+the batch queue.
+
+.. code-block:: php
+
+ use Guzzle\Batch\BatchBuilder;
+
+ $batch = BatchBuilder::factory()
+ ->transferRequests(10)
+ ->keepHistory()
+ ->build();
+
+After transferring items, you can use the ``getHistory()`` of a batch to retrieve an array of transferred items. Be
+sure to periodically clear the history using ``clearHistory()``.
+
+Batch builder method: ``keepHistory()``
+
+Exception buffering
+~~~~~~~~~~~~~~~~~~~
+
+Use the ``Guzzle\Batch\ExceptionBufferingBatch`` decorator to buffer exceptions during a transfer so that you can
+transfer as many items as possible then deal with the errored batches after the transfer completes. After transfer,
+use the ``getExceptions()`` method of a batch to retrieve an array of
+``Guzzle\Batch\Exception\BatchTransferException`` objects. You can use these exceptions to attempt to retry the
+failed batches. Be sure to clear the buffered exceptions when you are done with them by using the
+``clearExceptions()`` method.
+
+Batch builder method: ``bufferExceptions()``
diff --git a/vendor/guzzle/guzzle/docs/docs.rst b/vendor/guzzle/guzzle/docs/docs.rst
new file mode 100644
index 0000000..cf87908
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/docs.rst
@@ -0,0 +1,73 @@
+.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services
+
+====================
+Guzzle Documentation
+====================
+
+Getting started
+---------------
+
+.. toctree::
+ :maxdepth: 1
+
+ getting-started/overview
+ getting-started/installation
+ getting-started/faq
+
+The HTTP client
+---------------
+
+.. toctree::
+ :maxdepth: 2
+
+ http-client/client
+ http-client/request
+ http-client/response
+ http-client/entity-bodies
+ http-client/http-redirects
+ http-client/uri-templates
+
+Plugins
+-------
+
+.. toctree::
+ :maxdepth: 1
+
+ plugins/plugins-overview
+ plugins/creating-plugins
+ plugins/async-plugin
+ plugins/backoff-plugin
+ plugins/cache-plugin
+ plugins/cookie-plugin
+ plugins/curl-auth-plugin
+ plugins/history-plugin
+ plugins/log-plugin
+ plugins/md5-validator-plugin
+ plugins/mock-plugin
+ plugins/oauth-plugin
+
+The web service client
+----------------------
+
+.. toctree::
+ :maxdepth: 1
+
+ webservice-client/webservice-client
+ webservice-client/using-the-service-builder
+ webservice-client/guzzle-service-descriptions
+ batching/batching
+ iterators/resource-iterators
+ iterators/guzzle-iterators
+
+Testing
+-------
+
+.. toctree::
+ :maxdepth: 2
+
+ testing/unit-testing
+
+API Docs
+--------
+
+`Read the API docs <http://guzzlephp.org/api/index.html>`_
diff --git a/vendor/guzzle/guzzle/docs/getting-started/faq.rst b/vendor/guzzle/guzzle/docs/getting-started/faq.rst
new file mode 100644
index 0000000..a0a3fdb
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/getting-started/faq.rst
@@ -0,0 +1,29 @@
+===
+FAQ
+===
+
+What should I do if I get this error: Fatal error: Maximum function nesting level of '100' reached, aborting!
+-------------------------------------------------------------------------------------------------------------
+
+You could run into this error if you have the XDebug extension installed and you execute a lot of requests in
+callbacks. This error message comes specifically from the XDebug extension. PHP itself does not have a function
+nesting limit. Change this setting in your php.ini to increase the limit::
+
+ xdebug.max_nesting_level = 1000
+
+[`source <http://stackoverflow.com/a/4293870/151504>`_]
+
+How can I speed up my client?
+-----------------------------
+
+There are several things you can do to speed up your client:
+
+1. Utilize a C based HTTP message parser (e.g. ``Guzzle\Parser\Message\PeclHttpMessageParser``)
+2. Disable operation validation by setting the ``command.disable_validation`` option to true on a command
+
+Why am I getting a 417 error response?
+--------------------------------------
+
+This can occur for a number of reasons, but if you are sending PUT, POST, or PATCH requests with an
+``Expect: 100-Continue`` header, a server that does not support this header will return a 417 response. You can work
+around this by calling ``$request->removeHeader('Expect');`` after setting the entity body of a request.
diff --git a/vendor/guzzle/guzzle/docs/getting-started/installation.rst b/vendor/guzzle/guzzle/docs/getting-started/installation.rst
new file mode 100644
index 0000000..77d4001
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/getting-started/installation.rst
@@ -0,0 +1,154 @@
+============
+Installation
+============
+
+Requirements
+------------
+
+#. PHP 5.3.3+ compiled with the cURL extension
+#. A recent version of cURL 7.16.2+ compiled with OpenSSL and zlib
+
+Installing Guzzle
+-----------------
+
+Composer
+~~~~~~~~
+
+The recommended way to install Guzzle is with `Composer <http://getcomposer.org>`_. Composer is a dependency
+management tool for PHP that allows you to declare the dependencies your project needs and installs them into your
+project.
+
+.. code-block:: bash
+
+ # Install Composer
+ curl -sS https://getcomposer.org/installer | php
+
+ # Add Guzzle as a dependency
+ php composer.phar require guzzle/guzzle:~3.9
+
+After installing, you need to require Composer's autoloader:
+
+.. code-block:: php
+
+ require 'vendor/autoload.php';
+
+You can find out more on how to install Composer, configure autoloading, and other best-practices for defining
+dependencies at `getcomposer.org <http://getcomposer.org>`_.
+
+Using only specific parts of Guzzle
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+While you can always just rely on ``guzzle/guzzle``, Guzzle provides several smaller parts of Guzzle as individual
+packages available through Composer.
+
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| Package name | Description |
++===============================================================================================+==========================================+
+| `guzzle/common <https://packagist.org/packages/guzzle/common>`_ | Provides ``Guzzle\Common`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/http <https://packagist.org/packages/guzzle/http>`_ | Provides ``Guzzle\Http`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/parser <https://packagist.org/packages/guzzle/parser>`_ | Provides ``Guzzle\Parser`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/batch <https://packagist.org/packages/guzzle/batch>`_ | Provides ``Guzzle\Batch`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/cache <https://packagist.org/packages/guzzle/cache>`_ | Provides ``Guzzle\Cache`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/inflection <https://packagist.org/packages/guzzle/inflection>`_ | Provides ``Guzzle\Inflection`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/iterator <https://packagist.org/packages/guzzle/iterator>`_ | Provides ``Guzzle\Iterator`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/log <https://packagist.org/packages/guzzle/log>`_ | Provides ``Guzzle\Log`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin <https://packagist.org/packages/guzzle/plugin>`_ | Provides ``Guzzle\Plugin`` (all plugins) |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-async <https://packagist.org/packages/guzzle/plugin-async>`_ | Provides ``Guzzle\Plugin\Async`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-backoff <https://packagist.org/packages/guzzle/plugin-backoff>`_ | Provides ``Guzzle\Plugin\BackoffPlugin`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-cache <https://packagist.org/packages/guzzle/plugin-cache>`_ | Provides ``Guzzle\Plugin\Cache`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-cookie <https://packagist.org/packages/guzzle/plugin-cookie>`_ | Provides ``Guzzle\Plugin\Cookie`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-error-response <https://packagist.org/packages/guzzle/plugin-error-response>`_ | Provides ``Guzzle\Plugin\ErrorResponse`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-history <https://packagist.org/packages/guzzle/plugin-history>`_ | Provides ``Guzzle\Plugin\History`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-log <https://packagist.org/packages/guzzle/plugin-log>`_ | Provides ``Guzzle\Plugin\Log`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-md5 <https://packagist.org/packages/guzzle/plugin-md5>`_ | Provides ``Guzzle\Plugin\Md5`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-mock <https://packagist.org/packages/guzzle/plugin-mock>`_ | Provides ``Guzzle\Plugin\Mock`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/plugin-oauth <https://packagist.org/packages/guzzle/plugin-oauth>`_ | Provides ``Guzzle\Plugin\Oauth`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/service <https://packagist.org/packages/guzzle/service>`_ | Provides ``Guzzle\Service`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+| `guzzle/stream <https://packagist.org/packages/guzzle/stream>`_ | Provides ``Guzzle\Stream`` |
++-----------------------------------------------------------------------------------------------+------------------------------------------+
+
+Bleeding edge
+^^^^^^^^^^^^^
+
+During your development, you can keep up with the latest changes on the master branch by setting the version
+requirement for Guzzle to ``dev-master``.
+
+.. code-block:: js
+
+ {
+ "require": {
+ "guzzle/guzzle": "dev-master"
+ }
+ }
+
+PEAR
+~~~~
+
+Guzzle can be installed through PEAR:
+
+.. code-block:: bash
+
+ pear channel-discover guzzlephp.org/pear
+ pear install guzzle/guzzle
+
+You can install a specific version of Guzzle by providing a version number suffix:
+
+.. code-block:: bash
+
+ pear install guzzle/guzzle-3.9.0
+
+Contributing to Guzzle
+----------------------
+
+In order to contribute, you'll need to checkout the source from GitHub and install Guzzle's dependencies using
+Composer:
+
+.. code-block:: bash
+
+ git clone https://github.com/guzzle/guzzle.git
+ cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev
+
+Guzzle is unit tested with PHPUnit. You will need to create your own phpunit.xml file in order to run the unit tests
+(or just copy phpunit.xml.dist to phpunit.xml). Run the tests using the vendored PHPUnit binary:
+
+.. code-block:: bash
+
+ vendor/bin/phpunit
+
+You'll need to install node.js v0.5.0 or newer in order to test the cURL implementation.
+
+Framework integrations
+----------------------
+
+Using Guzzle with Symfony
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bundles are available on GitHub:
+
+- `DdeboerGuzzleBundle <https://github.com/ddeboer/GuzzleBundle>`_ for Guzzle 2
+- `MisdGuzzleBundle <https://github.com/misd-service-development/guzzle-bundle>`_ for Guzzle 3
+
+Using Guzzle with Silex
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A `Guzzle Silex service provider <https://github.com/guzzle/guzzle-silex-extension>`_ is available on GitHub.
diff --git a/vendor/guzzle/guzzle/docs/getting-started/overview.rst b/vendor/guzzle/guzzle/docs/getting-started/overview.rst
new file mode 100644
index 0000000..505b409
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/getting-started/overview.rst
@@ -0,0 +1,85 @@
+=================
+Welcome to Guzzle
+=================
+
+What is Guzzle?
+~~~~~~~~~~~~~~~
+
+Guzzle is a PHP HTTP client and framework for building web service clients. Guzzle takes the pain out of sending HTTP
+requests and the redundancy out of creating web service clients.
+
+Features at a glance
+--------------------
+
+- All the power of cURL with a simple interface.
+- Persistent connections and parallel requests.
+- Streams request and response bodies
+- Service descriptions for quickly building clients.
+- Powered by the Symfony2 EventDispatcher.
+- Use all of the code or only specific components.
+- Plugins for caching, logging, OAuth, mocks, and more
+- Includes a custom node.js webserver to test your clients.
+- Service descriptions for defining the inputs and outputs of an API
+- Resource iterators for traversing paginated resources
+- Batching for sending a large number of requests as efficiently as possible
+
+.. code-block:: php
+
+ // Really simple using a static facade
+ Guzzle\Http\StaticClient::mount();
+ $response = Guzzle::get('http://guzzlephp.org');
+
+ // More control using a client class
+ $client = new \Guzzle\Http\Client('http://guzzlephp.org');
+ $request = $client->get('/');
+ $response = $request->send();
+
+License
+-------
+
+Licensed using the `MIT license <http://opensource.org/licenses/MIT>`_.
+
+ Copyright (c) 2013 Michael Dowling <https://github.com/mtdowling>
+
+ 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.
+
+Contributing
+------------
+
+Guidelines
+~~~~~~~~~~
+
+This is still a work in progress, but there are only a few rules:
+
+1. Guzzle follows PSR-0, PSR-1, and PSR-2
+2. All pull requests must include unit tests to ensure the change works as expected and to prevent future regressions
+
+Reporting a security vulnerability
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We want to ensure that Guzzle is a secure HTTP client library for everyone. If you've discovered a security
+vulnerability in Guzzle, we appreciate your help in disclosing it to us in a
+`responsible manner <http://en.wikipedia.org/wiki/Responsible_disclosure>`_.
+
+Publicly disclosing a vulnerability can put the entire community at risk. If you've discovered a security concern,
+please email us at security@guzzlephp.org. We'll work with you to make sure that we understand the scope of the issue,
+and that we fully address your concern. We consider correspondence sent to security@guzzlephp.org our highest priority,
+and work to address any issues that arise as quickly as possible.
+
+After a security vulnerability has been corrected, a security hotfix release will be deployed as soon as possible.
diff --git a/vendor/guzzle/guzzle/docs/http-client/client.rst b/vendor/guzzle/guzzle/docs/http-client/client.rst
new file mode 100644
index 0000000..723d729
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/client.rst
@@ -0,0 +1,569 @@
+======================
+The Guzzle HTTP client
+======================
+
+Guzzle gives PHP developers complete control over HTTP requests while utilizing HTTP/1.1 best practices. Guzzle's HTTP
+functionality is a robust framework built on top of the `PHP libcurl bindings <http://www.php.net/curl>`_.
+
+The three main parts of the Guzzle HTTP client are:
+
++--------------+-------------------------------------------------------------------------------------------------------+
+| Clients | ``Guzzle\Http\Client`` (creates and sends requests, associates a response with a request) |
++--------------+-------------------------------------------------------------------------------------------------------+
+| Requests | ``Guzzle\Http\Message\Request`` (requests with no body), |
+| | ``Guzzle\Http\Message\EntityEnclosingRequest`` (requests with a body) |
++--------------+-------------------------------------------------------------------------------------------------------+
+| Responses | ``Guzzle\Http\Message\Response`` |
++--------------+-------------------------------------------------------------------------------------------------------+
+
+Creating a Client
+-----------------
+
+Clients create requests, send requests, and set responses on a request object. When instantiating a client object,
+you can pass an optional "base URL" and optional array of configuration options. A base URL is a
+:doc:`URI template <uri-templates>` that contains the URL of a remote server. When creating requests with a relative
+URL, the base URL of a client will be merged into the request's URL.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+
+ // Create a client and provide a base URL
+ $client = new Client('https://api.github.com');
+
+ $request = $client->get('/user');
+ $request->setAuth('user', 'pass');
+ echo $request->getUrl();
+ // >>> https://api.github.com/user
+
+ // You must send a request in order for the transfer to occur
+ $response = $request->send();
+
+ echo $response->getBody();
+ // >>> {"type":"User", ...
+
+ echo $response->getHeader('Content-Length');
+ // >>> 792
+
+ $data = $response->json();
+ echo $data['type'];
+ // >>> User
+
+Base URLs
+~~~~~~~~~
+
+Notice that the URL provided to the client's ``get()`` method is relative. Relative URLs will always merge into the
+base URL of the client. There are a few rules that control how the URLs are merged.
+
+.. tip::
+
+ Guzzle follows `RFC 3986 <http://tools.ietf.org/html/rfc3986#section-5.2>`_ when merging base URLs and
+ relative URLs.
+
+In the above example, we passed ``/user`` to the ``get()`` method of the client. This is a relative URL, so it will
+merge into the base URL of the client-- resulting in the derived URL of ``https://api.github.com/users``.
+
+``/user`` is a relative URL but uses an absolute path because it contains the leading slash. Absolute paths will
+overwrite any existing path of the base URL. If an absolute path is provided (e.g. ``/path/to/something``), then the
+path specified in the base URL of the client will be replaced with the absolute path, and the query string provided
+by the relative URL will replace the query string of the base URL.
+
+Omitting the leading slash and using relative paths will add to the path of the base URL of the client. So using a
+client base URL of ``https://api.twitter.com/v1.1`` and creating a GET request with ``statuses/user_timeline.json``
+will result in a URL of ``https://api.twitter.com/v1.1/statuses/user_timeline.json``. If a relative path and a query
+string are provided, then the relative path will be appended to the base URL path, and the query string provided will
+be merged into the query string of the base URL.
+
+If an absolute URL is provided (e.g. ``http://httpbin.org/ip``), then the request will completely use the absolute URL
+as-is without merging in any of the URL parts specified in the base URL.
+
+Configuration options
+~~~~~~~~~~~~~~~~~~~~~
+
+The second argument of the client's constructor is an array of configuration data. This can include URI template data
+or special options that alter the client's behavior:
+
++-------------------------------+-------------------------------------------------------------------------------------+
+| ``request.options`` | Associative array of :ref:`Request options <request-options>` to apply to every |
+| | request created by the client. |
++-------------------------------+-------------------------------------------------------------------------------------+
+| ``redirect.disable`` | Disable HTTP redirects for every request created by the client. |
++-------------------------------+-------------------------------------------------------------------------------------+
+| ``curl.options`` | Associative array of cURL options to apply to every request created by the client. |
+| | if either the key or value of an entry in the array is a string, Guzzle will |
+| | attempt to find a matching defined cURL constant automatically (e.g. |
+| | "CURLOPT_PROXY" will be converted to the constant ``CURLOPT_PROXY``). |
++-------------------------------+-------------------------------------------------------------------------------------+
+| ``ssl.certificate_authority`` | Set to true to use the Guzzle bundled SSL certificate bundle (this is used by |
+| | default, 'system' to use the bundle on your system, a string pointing to a file to |
+| | use a specific certificate file, a string pointing to a directory to use multiple |
+| | certificates, or ``false`` to disable SSL validation (not recommended). |
+| | |
+| | When using Guzzle inside of a phar file, the bundled SSL certificate will be |
+| | extracted to your system's temp folder, and each time a client is created an MD5 |
+| | check will be performed to ensure the integrity of the certificate. |
++-------------------------------+-------------------------------------------------------------------------------------+
+| ``command.params`` | When using a ``Guzzle\Service\Client`` object, this is an associative array of |
+| | default options to set on each command created by the client. |
++-------------------------------+-------------------------------------------------------------------------------------+
+
+Here's an example showing how to set various configuration options, including default headers to send with each request,
+default query string parameters to add to each request, a default auth scheme for each request, and a proxy to use for
+each request. Values can be injected into the client's base URL using variables from the configuration array.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+
+ $client = new Client('https://api.twitter.com/{version}', array(
+ 'version' => 'v1.1',
+ 'request.options' => array(
+ 'headers' => array('Foo' => 'Bar'),
+ 'query' => array('testing' => '123'),
+ 'auth' => array('username', 'password', 'Basic|Digest|NTLM|Any'),
+ 'proxy' => 'tcp://localhost:80'
+ )
+ ));
+
+Setting a custom User-Agent
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The default Guzzle User-Agent header is ``Guzzle/<Guzzle_Version> curl/<curl_version> PHP/<PHP_VERSION>``. You can
+customize the User-Agent header of a client by calling the ``setUserAgent()`` method of a Client object.
+
+.. code-block:: php
+
+ // Completely override the default User-Agent
+ $client->setUserAgent('Test/123');
+
+ // Prepend a string to the default User-Agent
+ $client->setUserAgent('Test/123', true);
+
+Creating requests with a client
+-------------------------------
+
+A Client object exposes several methods used to create Request objects:
+
+* Create a custom HTTP request: ``$client->createRequest($method, $uri, array $headers, $body, $options)``
+* Create a GET request: ``$client->get($uri, array $headers, $options)``
+* Create a HEAD request: ``$client->head($uri, array $headers, $options)``
+* Create a DELETE request: ``$client->delete($uri, array $headers, $body, $options)``
+* Create a POST request: ``$client->post($uri, array $headers, $postBody, $options)``
+* Create a PUT request: ``$client->put($uri, array $headers, $body, $options)``
+* Create a PATCH request: ``$client->patch($uri, array $headers, $body, $options)``
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+
+ $client = new Client('http://baseurl.com/api/v1');
+
+ // Create a GET request using Relative to base URL
+ // URL of the request: http://baseurl.com/api/v1/path?query=123&value=abc)
+ $request = $client->get('path?query=123&value=abc');
+ $response = $request->send();
+
+ // Create HEAD request using a relative URL with an absolute path
+ // URL of the request: http://baseurl.com/path?query=123&value=abc
+ $request = $client->head('/path?query=123&value=abc');
+ $response = $request->send();
+
+ // Create a DELETE request using an absolute URL
+ $request = $client->delete('http://www.example.com/path?query=123&value=abc');
+ $response = $request->send();
+
+ // Create a PUT request using the contents of a PHP stream as the body
+ // Specify custom HTTP headers
+ $request = $client->put('http://www.example.com/upload', array(
+ 'X-Header' => 'My Header'
+ ), fopen('http://www.test.com/', 'r'));
+ $response = $request->send();
+
+ // Create a POST request and add the POST files manually
+ $request = $client->post('http://localhost:8983/solr/update')
+ ->addPostFiles(array('file' => '/path/to/documents.xml'));
+ $response = $request->send();
+
+ // Check if a resource supports the DELETE method
+ $supportsDelete = $client->options('/path')->send()->isMethodAllowed('DELETE');
+ $response = $request->send();
+
+Client objects create Request objects using a request factory (``Guzzle\Http\Message\RequestFactoryInterface``).
+You can inject a custom request factory into the Client using ``$client->setRequestFactory()``, but you can typically
+rely on a Client's default request factory.
+
+Static clients
+--------------
+
+You can use Guzzle's static client facade to more easily send simple HTTP requests.
+
+.. code-block:: php
+
+ // Mount the client so that you can access it at \Guzzle
+ Guzzle\Http\StaticClient::mount();
+ $response = Guzzle::get('http://guzzlephp.org');
+
+Each request method of the static client (e.g. ``get()``, ``post()`, ``put()``, etc) accepts an associative array of request
+options to apply to the request.
+
+.. code-block:: php
+
+ $response = Guzzle::post('http://test.com', array(
+ 'headers' => array('X-Foo' => 'Bar'),
+ 'body' => array('Test' => '123'),
+ 'timeout' => 10
+ ));
+
+.. _request-options:
+
+Request options
+---------------
+
+Request options can be specified when creating a request or in the ``request.options`` parameter of a client. These
+options can control various aspects of a request including: headers to send, query string data, where the response
+should be downloaded, proxies, auth, etc.
+
+headers
+~~~~~~~
+
+Associative array of headers to apply to the request. When specified in the ``$options`` argument of a client creational
+method (e.g. ``get()``, ``post()``, etc), the headers in the ``$options`` array will overwrite headers specified in the
+``$headers`` array.
+
+.. code-block:: php
+
+ $request = $client->get($url, array(), array(
+ 'headers' => array('X-Foo' => 'Bar')
+ ));
+
+Headers can be specified on a client to add default headers to every request sent by a client.
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client();
+
+ // Set a single header using path syntax
+ $client->setDefaultOption('headers/X-Foo', 'Bar');
+
+ // Set all headers
+ $client->setDefaultOption('headers', array('X-Foo' => 'Bar'));
+
+.. note::
+
+ In addition to setting request options when creating requests or using the ``setDefaultOption()`` method, any
+ default client request option can be set using a client's config object:
+
+ .. code-block:: php
+
+ $client->getConfig()->setPath('request.options/headers/X-Foo', 'Bar');
+
+query
+~~~~~
+
+Associative array of query string parameters to the request. When specified in the ``$options`` argument of a client
+creational method, the query string parameters in the ``$options`` array will overwrite query string parameters
+specified in the `$url`.
+
+.. code-block:: php
+
+ $request = $client->get($url, array(), array(
+ 'query' => array('abc' => '123')
+ ));
+
+Query string parameters can be specified on a client to add default query string parameters to every request sent by a
+client.
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client();
+
+ // Set a single query string parameter using path syntax
+ $client->setDefaultOption('query/abc', '123');
+
+ // Set an array of default query string parameters
+ $client->setDefaultOption('query', array('abc' => '123'));
+
+body
+~~~~
+
+Sets the body of a request. The value supplied to the body option can be a ``Guzzle\Http\EntityBodyInterface``, string,
+fopen resource, or array when sending POST requests. When a ``body`` request option is supplied, the option value will
+overwrite the ``$body`` argument of a client creational method.
+
+auth
+~~~~
+
+Specifies and array of HTTP authorization parameters parameters to use with the request. The array must contain the
+username in index [0], the password in index [1], and can optionally contain the authentication type in index [2].
+The available authentication types are: "Basic" (default), "Digest", "NTLM", or "Any".
+
+.. code-block:: php
+
+ $request = $client->get($url, array(), array(
+ 'auth' => array('username', 'password', 'Digest')
+ ));
+
+ // You can add auth headers to every request of a client
+ $client->setDefaultOption('auth', array('username', 'password', 'Digest'));
+
+cookies
+~~~~~~~
+
+Specifies an associative array of cookies to add to the request.
+
+allow_redirects
+~~~~~~~~~~~~~~~
+
+Specifies whether or not the request should follow redirects. Requests will follow redirects by default. Set
+``allow_redirects`` to ``false`` to disable redirects.
+
+save_to
+~~~~~~~
+
+The ``save_to`` option specifies where the body of a response is downloaded. You can pass the path to a file, an fopen
+resource, or a ``Guzzle\Http\EntityBodyInterface`` object.
+
+See :ref:`Changing where a response is downloaded <request-set-response-body>` for more information on setting the
+`save_to` option.
+
+events
+~~~~~~
+
+The `events` option makes it easy to attach listeners to the various events emitted by a request object. The `events`
+options must be an associative array mapping an event name to a Closure or array the contains a Closure and the
+priority of the event.
+
+.. code-block:: php
+
+ $request = $client->get($url, array(), array(
+ 'events' => array(
+ 'request.before_send' => function (\Guzzle\Common\Event $e) {
+ echo 'About to send ' . $e['request'];
+ }
+ )
+ ));
+
+ // Using the static client:
+ Guzzle::get($url, array(
+ 'events' => array(
+ 'request.before_send' => function (\Guzzle\Common\Event $e) {
+ echo 'About to send ' . $e['request'];
+ }
+ )
+ ));
+
+plugins
+~~~~~~~
+
+The `plugins` options makes it easy to attach an array of plugins to a request.
+
+.. code-block:: php
+
+ // Using the static client:
+ Guzzle::get($url, array(
+ 'plugins' => array(
+ new Guzzle\Plugin\Cache\CachePlugin(),
+ new Guzzle\Plugin\Cookie\CookiePlugin()
+ )
+ ));
+
+exceptions
+~~~~~~~~~~
+
+The `exceptions` option can be used to disable throwing exceptions for unsuccessful HTTP response codes
+(e.g. 404, 500, etc). Set `exceptions` to false to not throw exceptions.
+
+params
+~~~~~~
+
+The `params` options can be used to specify an associative array of data parameters to add to a request. Note that
+these are not query string parameters.
+
+timeout / connect_timeout
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can specify the maximum number of seconds to allow for an entire transfer to take place before timing out using
+the `timeout` request option. You can specify the maximum number of seconds to wait while trying to connect using the
+`connect_timeout` request option. Set either of these options to 0 to wait indefinitely.
+
+.. code-block:: php
+
+ $request = $client->get('http://www.example.com', array(), array(
+ 'timeout' => 20,
+ 'connect_timeout' => 1.5
+ ));
+
+verify
+~~~~~~
+
+Set to true to enable SSL certificate validation (the default), false to disable SSL certificate validation, or supply
+the path to a CA bundle to enable verification using a custom certificate.
+
+cert
+~~~~
+
+The `cert` option lets you specify a PEM formatted SSL client certificate to use with servers that require one. If the
+certificate requires a password, provide an array with the password as the second item.
+
+This would typically be used in conjunction with the `ssl_key` option.
+
+.. code-block:: php
+
+ $request = $client->get('https://www.example.com', array(), array(
+ 'cert' => '/etc/pki/client_certificate.pem'
+ )
+
+ $request = $client->get('https://www.example.com', array(), array(
+ 'cert' => array('/etc/pki/client_certificate.pem', 's3cr3tp455w0rd')
+ )
+
+ssl_key
+~~~~~~~
+
+The `ssl_key` option lets you specify a file containing your PEM formatted private key, optionally protected by a password.
+Note: your password is sensitive, keep the PHP script containing it safe.
+
+This would typically be used in conjunction with the `cert` option.
+
+.. code-block:: php
+
+ $request = $client->get('https://www.example.com', array(), array(
+ 'ssl_key' => '/etc/pki/private_key.pem'
+ )
+
+ $request = $client->get('https://www.example.com', array(), array(
+ 'ssl_key' => array('/etc/pki/private_key.pem', 's3cr3tp455w0rd')
+ )
+
+proxy
+~~~~~
+
+The `proxy` option is used to specify an HTTP proxy (e.g. `http://username:password@192.168.16.1:10`).
+
+debug
+~~~~~
+
+The `debug` option is used to show verbose cURL output for a transfer.
+
+stream
+~~~~~~
+
+When using a static client, you can set the `stream` option to true to return a `Guzzle\Stream\Stream` object that can
+be used to pull data from a stream as needed (rather than have cURL download the entire contents of a response to a
+stream all at once).
+
+.. code-block:: php
+
+ $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true));
+ while (!$stream->feof()) {
+ echo $stream->readLine();
+ }
+
+Sending requests
+----------------
+
+Requests can be sent by calling the ``send()`` method of a Request object, but you can also send requests using the
+``send()`` method of a Client.
+
+.. code-block:: php
+
+ $request = $client->get('http://www.amazon.com');
+ $response = $client->send($request);
+
+Sending requests in parallel
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Client's ``send()`` method accept a single ``Guzzle\Http\Message\RequestInterface`` object or an array of
+RequestInterface objects. When an array is specified, the requests will be sent in parallel.
+
+Sending many HTTP requests serially (one at a time) can cause an unnecessary delay in a script's execution. Each
+request must complete before a subsequent request can be sent. By sending requests in parallel, a pool of HTTP
+requests can complete at the speed of the slowest request in the pool, significantly reducing the amount of time
+needed to execute multiple HTTP requests. Guzzle provides a wrapper for the curl_multi functions in PHP.
+
+Here's an example of sending three requests in parallel using a client object:
+
+.. code-block:: php
+
+ use Guzzle\Common\Exception\MultiTransferException;
+
+ try {
+ $responses = $client->send(array(
+ $client->get('http://www.google.com/'),
+ $client->head('http://www.google.com/'),
+ $client->get('https://www.github.com/')
+ ));
+ } catch (MultiTransferException $e) {
+
+ echo "The following exceptions were encountered:\n";
+ foreach ($e as $exception) {
+ echo $exception->getMessage() . "\n";
+ }
+
+ echo "The following requests failed:\n";
+ foreach ($e->getFailedRequests() as $request) {
+ echo $request . "\n\n";
+ }
+
+ echo "The following requests succeeded:\n";
+ foreach ($e->getSuccessfulRequests() as $request) {
+ echo $request . "\n\n";
+ }
+ }
+
+If the requests succeed, an array of ``Guzzle\Http\Message\Response`` objects are returned. A single request failure
+will not cause the entire pool of requests to fail. Any exceptions thrown while transferring a pool of requests will
+be aggregated into a ``Guzzle\Common\Exception\MultiTransferException`` exception.
+
+Plugins and events
+------------------
+
+Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications
+powered by the
+`Symfony2 Event Dispatcher component <http://symfony.com/doc/2.0/components/event_dispatcher/introduction.html>`_. Any
+event listener or subscriber attached to a Client object will automatically be attached to each request created by the
+client.
+
+Using the same cookie session for each request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Attach a ``Guzzle\Plugin\Cookie\CookiePlugin`` to a client which will in turn add support for cookies to every request
+created by a client, and each request will use the same cookie session:
+
+.. code-block:: php
+
+ use Guzzle\Plugin\Cookie\CookiePlugin;
+ use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;
+
+ // Create a new cookie plugin
+ $cookiePlugin = new CookiePlugin(new ArrayCookieJar());
+
+ // Add the cookie plugin to the client
+ $client->addSubscriber($cookiePlugin);
+
+.. _client-events:
+
+Events emitted from a client
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A ``Guzzle\Http\Client`` object emits the following events:
+
++------------------------------+--------------------------------------------+------------------------------------------+
+| Event name | Description | Event data |
++==============================+============================================+==========================================+
+| client.create_request | Called when a client creates a request | * client: The client |
+| | | * request: The created request |
++------------------------------+--------------------------------------------+------------------------------------------+
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+ use Guzzle\Http\Client;
+
+ $client = new Client();
+
+ // Add a listener that will echo out requests as they are created
+ $client->getEventDispatcher()->addListener('client.create_request', function (Event $e) {
+ echo 'Client object: ' . spl_object_hash($e['client']) . "\n";
+ echo "Request object: {$e['request']}\n";
+ });
diff --git a/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst b/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst
new file mode 100644
index 0000000..823b0c0
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst
@@ -0,0 +1,151 @@
+===========================
+Request and response bodies
+===========================
+
+`Entity body <http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html>`_ is the term used for the body of an HTTP
+message. The entity body of requests and responses is inherently a
+`PHP stream <http://php.net/manual/en/book.stream.php>`_ in Guzzle. The body of the request can be either a string or
+a PHP stream which are converted into a ``Guzzle\Http\EntityBody`` object using its factory method. When using a
+string, the entity body is stored in a `temp PHP stream <http://www.php.net/manual/en/wrappers.php.php>`_. The use of
+temp PHP streams helps to protect your application from running out of memory when sending or receiving large entity
+bodies in your messages. When more than 2MB of data is stored in a temp stream, it automatically stores the data on
+disk rather than in memory.
+
+EntityBody objects provide a great deal of functionality: compression, decompression, calculate the Content-MD5,
+calculate the Content-Length (when the resource is repeatable), guessing the Content-Type, and more. Guzzle doesn't
+need to load an entire entity body into a string when sending or retrieving data; entity bodies are streamed when
+being uploaded and downloaded.
+
+Here's an example of gzip compressing a text file then sending the file to a URL:
+
+.. code-block:: php
+
+ use Guzzle\Http\EntityBody;
+
+ $body = EntityBody::factory(fopen('/path/to/file.txt', 'r+'));
+ echo $body->read(1024);
+ $body->seek(0, SEEK_END);
+ $body->write('foo');
+ echo $body->ftell();
+ $body->rewind();
+
+ // Send a request using the body
+ $response = $client->put('http://localhost:8080/uploads', null, $body)->send();
+
+The body of the request can be specified in the ``Client::put()`` or ``Client::post()`` method, or, you can specify
+the body of the request by calling the ``setBody()`` method of any
+``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object.
+
+Compression
+-----------
+
+You can compress the contents of an EntityBody object using the ``compress()`` method. The compress method accepts a
+filter that must match to one of the supported
+`PHP stream filters <http://www.php.net/manual/en/filters.compression.php>`_ on your system (e.g. `zlib.deflate`,
+``bzip2.compress``, etc). Compressing an entity body will stream the entire entity body through a stream compression
+filter into a temporary PHP stream. You can uncompress an entity body using the ``uncompress()`` method and passing
+the PHP stream filter to use when decompressing the stream (e.g. ``zlib.inflate``).
+
+.. code-block:: php
+
+ use Guzzle\Http\EntityBody;
+
+ $body = EntityBody::factory(fopen('/tmp/test.txt', 'r+'));
+ echo $body->getSize();
+ // >>> 1048576
+
+ // Compress using the default zlib.deflate filter
+ $body->compress();
+ echo $body->getSize();
+ // >>> 314572
+
+ // Decompress the stream
+ $body->uncompress();
+ echo $body->getSize();
+ // >>> 1048576
+
+Decorators
+----------
+
+Guzzle provides several EntityBody decorators that can be used to add functionality to an EntityBody at runtime.
+
+IoEmittingEntityBody
+~~~~~~~~~~~~~~~~~~~~
+
+This decorator will emit events when data is read from a stream or written to a stream. Add an event subscriber to the
+entity body's ``body.read`` or ``body.write`` methods to receive notifications when data data is transferred.
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+ use Guzzle\Http\EntityBody;
+ use Guzzle\Http\IoEmittingEntityBody;
+
+ $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+'));
+ $body = new IoEmittingEntityBody($original);
+
+ // Listen for read events
+ $body->getEventDispatcher()->addListener('body.read', function (Event $e) {
+ // Grab data from the event
+ $entityBody = $e['body'];
+ // Amount of data retrieved from the body
+ $lengthOfData = $e['length'];
+ // The actual data that was read
+ $data = $e['read'];
+ });
+
+ // Listen for write events
+ $body->getEventDispatcher()->addListener('body.write', function (Event $e) {
+ // Grab data from the event
+ $entityBody = $e['body'];
+ // The data that was written
+ $data = $e['write'];
+ // The actual amount of data that was written
+ $data = $e['read'];
+ });
+
+ReadLimitEntityBody
+~~~~~~~~~~~~~~~~~~~
+
+The ReadLimitEntityBody decorator can be used to transfer a subset or slice of an existing EntityBody object. This can
+be useful for breaking a large file into smaller pieces to be sent in chunks (e.g. Amazon S3's multipart upload API).
+
+.. code-block:: php
+
+ use Guzzle\Http\EntityBody;
+ use Guzzle\Http\ReadLimitEntityBody;
+
+ $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+'));
+ echo $original->getSize();
+ // >>> 1048576
+
+ // Limit the size of the body to 1024 bytes and start reading from byte 2048
+ $body = new ReadLimitEntityBody($original, 1024, 2048);
+ echo $body->getSize();
+ // >>> 1024
+ echo $body->ftell();
+ // >>> 0
+
+CachingEntityBody
+~~~~~~~~~~~~~~~~~
+
+The CachingEntityBody decorator is used to allow seeking over previously read bytes on non-seekable read streams. This
+can be useful when transferring a non-seekable entity body fails due to needing to rewind the stream (for example,
+resulting from a redirect). Data that is read from the remote stream will be buffered in a PHP temp stream so that
+previously read bytes are cached first in memory, then on disk.
+
+.. code-block:: php
+
+ use Guzzle\Http\EntityBody;
+ use Guzzle\Http\CachingEntityBody;
+
+ $original = EntityBody::factory(fopen('http://www.google.com', 'r'));
+ $body = new CachingEntityBody($original);
+
+ $body->read(1024);
+ echo $body->ftell();
+ // >>> 1024
+
+ $body->seek(0);
+ echo $body->ftell();
+ // >>> 0
diff --git a/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst b/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst
new file mode 100644
index 0000000..32ba268
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst
@@ -0,0 +1,99 @@
+==============
+HTTP redirects
+==============
+
+By default, Guzzle will automatically follow redirects using the non-RFC compliant implementation used by most web
+browsers. This means that redirects for POST requests are followed by a GET request. You can force RFC compliance by
+enabling the strict mode on a request's parameter object:
+
+.. code-block:: php
+
+ // Set per request
+ $request = $client->post();
+ $request->getParams()->set('redirect.strict', true);
+
+ // You can set globally on a client so all requests use strict redirects
+ $client->getConfig()->set('request.params', array(
+ 'redirect.strict' => true
+ ));
+
+By default, Guzzle will redirect up to 5 times before throwing a ``Guzzle\Http\Exception\TooManyRedirectsException``.
+You can raise or lower this value using the ``redirect.max`` parameter of a request object:
+
+.. code-block:: php
+
+ $request->getParams()->set('redirect.max', 2);
+
+Redirect history
+----------------
+
+You can get the number of redirects of a request using the resulting response object's ``getRedirectCount()`` method.
+Similar to cURL's ``effective_url`` property, Guzzle provides the effective URL, or the last redirect URL that returned
+the request, in a response's ``getEffectiveUrl()`` method.
+
+When testing or debugging, it is often useful to see a history of redirects for a particular request. This can be
+achieved using the HistoryPlugin.
+
+.. code-block:: php
+
+ $request = $client->get('/');
+ $history = new Guzzle\Plugin\History\HistoryPlugin();
+ $request->addSubscriber($history);
+ $response = $request->send();
+
+ // Get the last redirect URL or the URL of the request that received
+ // this response
+ echo $response->getEffectiveUrl();
+
+ // Get the number of redirects
+ echo $response->getRedirectCount();
+
+ // Iterate over each sent request and response
+ foreach ($history->getAll() as $transaction) {
+ // Request object
+ echo $transaction['request']->getUrl() . "\n";
+ // Response object
+ echo $transaction['response']->getEffectiveUrl() . "\n";
+ }
+
+ // Or, simply cast the HistoryPlugin to a string to view each request and response
+ echo $history;
+
+Disabling redirects
+-------------------
+
+You can disable redirects on a client by passing a configuration option in the client's constructor:
+
+.. code-block:: php
+
+ $client = new Client(null, array('redirect.disable' => true));
+
+You can also disable redirects per request:
+
+.. code-block:: php
+
+ $request = $client->get($url, array(), array('allow_redirects' => false));
+
+Redirects and non-repeatable streams
+------------------------------------
+
+If you are redirected when sending data from a non-repeatable stream and some of the data has been read off of the
+stream, then you will get a ``Guzzle\Http\Exception\CouldNotRewindStreamException``. You can get around this error by
+adding a custom rewind method to the entity body object being sent in the request.
+
+.. code-block:: php
+
+ $request = $client->post(
+ 'http://httpbin.com/redirect/2',
+ null,
+ fopen('http://httpbin.com/get', 'r')
+ );
+
+ // Add a custom function that can be used to rewind the stream
+ // (reopen in this example)
+ $request->getBody()->setRewindFunction(function ($body) {
+ $body->setStream(fopen('http://httpbin.com/get', 'r'));
+ return true;
+ );
+
+ $response = $client->send();
diff --git a/vendor/guzzle/guzzle/docs/http-client/request.rst b/vendor/guzzle/guzzle/docs/http-client/request.rst
new file mode 100644
index 0000000..a8387a9
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/request.rst
@@ -0,0 +1,667 @@
+=====================
+Using Request objects
+=====================
+
+HTTP request messages
+---------------------
+
+Request objects are all about building an HTTP message. Each part of an HTTP request message can be set individually
+using methods on the request object or set in bulk using the ``setUrl()`` method. Here's the format of an HTTP request
+with each part of the request referencing the method used to change it::
+
+ PUT(a) /path(b)?query=123(c) HTTP/1.1(d)
+ X-Header(e): header
+ Content-Length(e): 4
+
+ data(f)
+
++-------------------------+---------------------------------------------------------------------------------+
+| a. **Method** | The request method can only be set when instantiating a request |
++-------------------------+---------------------------------------------------------------------------------+
+| b. **Path** | ``$request->setPath('/path');`` |
++-------------------------+---------------------------------------------------------------------------------+
+| c. **Query** | ``$request->getQuery()->set('query', '123');`` |
++-------------------------+---------------------------------------------------------------------------------+
+| d. **Protocol version** | ``$request->setProtocolVersion('1.1');`` |
++-------------------------+---------------------------------------------------------------------------------+
+| e. **Header** | ``$request->setHeader('X-Header', 'header');`` |
++-------------------------+---------------------------------------------------------------------------------+
+| f. **Entity Body** | ``$request->setBody('data'); // Only available with PUT, POST, PATCH, DELETE`` |
++-------------------------+---------------------------------------------------------------------------------+
+
+Creating requests with a client
+-------------------------------
+
+Client objects are responsible for creating HTTP request objects.
+
+GET requests
+~~~~~~~~~~~~
+
+`GET requests <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3>`_ are the most common form of HTTP
+requests. When you visit a website in your browser, the HTML of the website is downloaded using a GET request. GET
+requests are idempotent requests that are typically used to download content (an entity) identified by a request URL.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+
+ $client = new Client();
+
+ // Create a request that has a query string and an X-Foo header
+ $request = $client->get('http://www.amazon.com?a=1', array('X-Foo' => 'Bar'));
+
+ // Send the request and get the response
+ $response = $request->send();
+
+You can change where the body of a response is downloaded on any request using the
+``$request->setResponseBody(string|EntityBodyInterface|resource)`` method of a request. You can also set the ``save_to``
+option of a request:
+
+.. code-block:: php
+
+ // Send the response body to a file
+ $request = $client->get('http://test.com', array(), array('save_to' => '/path/to/file'));
+
+ // Send the response body to an fopen resource
+ $request = $client->get('http://test.com', array(), array('save_to' => fopen('/path/to/file', 'w')));
+
+HEAD requests
+~~~~~~~~~~~~~
+
+`HEAD requests <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4>`_ work exactly like GET requests except
+that they do not actually download the response body (entity) of the response message. HEAD requests are useful for
+retrieving meta information about an entity identified by a Request-URI.
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client();
+ $request = $client->head('http://www.amazon.com');
+ $response = $request->send();
+ echo $response->getContentLength();
+ // >>> Will output the Content-Length header value
+
+DELETE requests
+~~~~~~~~~~~~~~~
+
+A `DELETE method <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7>`_ requests that the origin server
+delete the resource identified by the Request-URI.
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client();
+ $request = $client->delete('http://example.com');
+ $response = $request->send();
+
+POST requests
+~~~~~~~~~~~~~
+
+While `POST requests <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5>`_ can be used for a number of
+reasons, POST requests are often used when submitting HTML form data to a website. POST requests can include an entity
+body in the HTTP request.
+
+POST requests in Guzzle are sent with an ``application/x-www-form-urlencoded`` Content-Type header if POST fields are
+present but no files are being sent in the POST. If files are specified in the POST request, then the Content-Type
+header will become ``multipart/form-data``.
+
+The ``post()`` method of a client object accepts four arguments: the URL, optional headers, post fields, and an array of
+request options. To send files in the POST request, prepend the ``@`` symbol to the array value (just like you would if
+you were using the PHP ``curl_setopt`` function).
+
+Here's how to create a multipart/form-data POST request containing files and fields:
+
+.. code-block:: php
+
+ $request = $client->post('http://httpbin.org/post', array(), array(
+ 'custom_field' => 'my custom value',
+ 'file_field' => '@/path/to/file.xml'
+ ));
+
+ $response = $request->send();
+
+.. note::
+
+ Remember to **always** sanitize user input when sending POST requests:
+
+ .. code-block:: php
+
+ // Prevent users from accessing sensitive files by sanitizing input
+ $_POST = array('firstname' => '@/etc/passwd');
+ $request = $client->post('http://www.example.com', array(), array (
+ 'firstname' => str_replace('@', '', $_POST['firstname'])
+ ));
+
+You can alternatively build up the contents of a POST request.
+
+.. code-block:: php
+
+ $request = $client->post('http://httpbin.org/post')
+ ->setPostField('custom_field', 'my custom value')
+ ->addPostFile('file', '/path/to/file.xml');
+
+ $response = $request->send();
+
+Raw POST data
+^^^^^^^^^^^^^
+
+POST requests can also contain raw POST data that is not related to HTML forms.
+
+.. code-block:: php
+
+ $request = $client->post('http://httpbin.org/post', array(), 'this is the body');
+ $response = $request->send();
+
+You can set the body of POST request using the ``setBody()`` method of the
+``Guzzle\Http\Message\EntityEnclosingRequest`` object. This method accepts a string, a resource returned from
+``fopen``, or a ``Guzzle\Http\EntityBodyInterface`` object.
+
+.. code-block:: php
+
+ $request = $client->post('http://httpbin.org/post');
+ // Set the body of the POST to stream the contents of /path/to/large_body.txt
+ $request->setBody(fopen('/path/to/large_body.txt', 'r'));
+ $response = $request->send();
+
+PUT requests
+~~~~~~~~~~~~
+
+The `PUT method <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6>`_ requests that the enclosed entity be
+stored under the supplied Request-URI. PUT requests are similar to POST requests in that they both can send an entity
+body in the request message.
+
+The body of a PUT request (any any ``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object) is always stored as
+a ``Guzzle\Http\Message\EntityBodyInterface`` object. This allows a great deal of flexibility when sending data to a
+remote server. For example, you can stream the contents of a stream returned by fopen, stream the contents of a
+callback function, or simply send a string of data.
+
+.. code-block:: php
+
+ $request = $client->put('http://httpbin.org/put', array(), 'this is the body');
+ $response = $request->send();
+
+Just like with POST, PATH, and DELETE requests, you can set the body of a PUT request using the ``setBody()`` method.
+
+.. code-block:: php
+
+ $request = $client->put('http://httpbin.org/put');
+ $request->setBody(fopen('/path/to/large_body.txt', 'r'));
+ $response = $request->send();
+
+PATCH requests
+~~~~~~~~~~~~~~
+
+`PATCH requests <http://tools.ietf.org/html/rfc5789>`_ are used to modify a resource.
+
+.. code-block:: php
+
+ $request = $client->patch('http://httpbin.org', array(), 'this is the body');
+ $response = $request->send();
+
+OPTIONS requests
+~~~~~~~~~~~~~~~~
+
+The `OPTIONS method <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2>`_ represents a request for
+information about the communication options available on the request/response chain identified by the Request-URI.
+
+.. code-block:: php
+
+ $request = $client->options('http://httpbin.org');
+ $response = $request->send();
+
+ // Check if the PUT method is supported by this resource
+ var_export($response->isMethodAllows('PUT'));
+
+Custom requests
+~~~~~~~~~~~~~~~
+
+You can create custom HTTP requests that use non-standard HTTP methods using the ``createRequest()`` method of a
+client object.
+
+.. code-block:: php
+
+ $request = $client->createRequest('COPY', 'http://example.com/foo', array(
+ 'Destination' => 'http://example.com/bar',
+ 'Overwrite' => 'T'
+ ));
+ $response = $request->send();
+
+Query string parameters
+-----------------------
+
+Query string parameters of a request are owned by a request's ``Guzzle\Http\Query`` object that is accessible by
+calling ``$request->getQuery()``. The Query class extends from ``Guzzle\Common\Collection`` and allows you to set one
+or more query string parameters as key value pairs. You can set a parameter on a Query object using the
+``set($key, $value)`` method or access the query string object like an associative array. Any previously specified
+value for a key will be overwritten when using ``set()``. Use ``add($key, $value)`` to add a value to query string
+object, and in the event of a collision with an existing value at a specific key, the value will be converted to an
+array that contains all of the previously set values.
+
+.. code-block:: php
+
+ $request = new Guzzle\Http\Message\Request('GET', 'http://www.example.com?foo=bar&abc=123');
+
+ $query = $request->getQuery();
+ echo "{$query}\n";
+ // >>> foo=bar&abc=123
+
+ $query->remove('abc');
+ echo "{$query}\n";
+ // >>> foo=bar
+
+ $query->set('foo', 'baz');
+ echo "{$query}\n";
+ // >>> foo=baz
+
+ $query->add('foo', 'bar');
+ echo "{$query}\n";
+ // >>> foo%5B0%5D=baz&foo%5B1%5D=bar
+
+Whoah! What happened there? When ``foo=bar`` was added to the existing ``foo=baz`` query string parameter, the
+aggregator associated with the Query object was used to help convert multi-value query string parameters into a string.
+Let's disable URL-encoding to better see what's happening.
+
+.. code-block:: php
+
+ $query->useUrlEncoding(false);
+ echo "{$query}\n";
+ // >>> foo[0]=baz&foo[1]=bar
+
+.. note::
+
+ URL encoding can be disabled by passing false, enabled by passing true, set to use RFC 1738 by passing
+ ``Query::FORM_URLENCODED`` (internally uses PHP's ``urlencode`` function), or set to RFC 3986 by passing
+ ``Query::RFC_3986`` (this is the default and internally uses PHP's ``rawurlencode`` function).
+
+As you can see, the multiple values were converted into query string parameters following the default PHP convention of
+adding numerically indexed square bracket suffixes to each key (``foo[0]=baz&foo[1]=bar``). The strategy used to convert
+multi-value parameters into a string can be customized using the ``setAggregator()`` method of the Query class. Guzzle
+ships with the following query string aggregators by default:
+
+1. ``Guzzle\Http\QueryAggregator\PhpAggregator``: Aggregates using PHP style brackets (e.g. ``foo[0]=baz&foo[1]=bar``)
+2. ``Guzzle\Http\QueryAggregator\DuplicateAggregator``: Performs no aggregation and allows for key value pairs to be
+ repeated in a URL (e.g. ``foo=baz&foo=bar``)
+3. ``Guzzle\Http\QueryAggregator\CommaAggregator``: Aggregates using commas (e.g. ``foo=baz,bar``)
+
+.. _http-message-headers:
+
+HTTP Message Headers
+--------------------
+
+HTTP message headers are case insensitive, multiple occurrences of any header can be present in an HTTP message
+(whether it's valid or not), and some servers require specific casing of particular headers. Because of this, request
+and response headers are stored in ``Guzzle\Http\Message\Header`` objects. The Header object can be cast as a string,
+counted, or iterated to retrieve each value from the header. Casting a Header object to a string will return all of
+the header values concatenated together using a glue string (typically ", ").
+
+A request (and response) object have several methods that allow you to retrieve and modify headers.
+
+* ``getHeaders()``: Get all of the headers of a message as a ``Guzzle\Http\Message\Header\HeaderCollection`` object.
+* ``getHeader($header)``: Get a specific header from a message. If the header exists, you'll get a
+ ``Guzzle\Http\Message\Header`` object. If the header does not exist, this methods returns ``null``.
+* ``hasHeader($header)``: Returns true or false based on if the message has a particular header.
+* ``setHeader($header, $value)``: Set a header value and overwrite any previously set value for this header.
+* ``addHeader($header, $value)``: Add a header with a particular name. If a previous value was already set by the same,
+ then the header will contain multiple values.
+* ``removeHeader($header)``: Remove a header by name from the message.
+
+.. code-block:: php
+
+ $request = new Request('GET', 'http://httpbin.com/cookies');
+ // addHeader will set and append to any existing header values
+ $request->addHeader('Foo', 'bar');
+ $request->addHeader('foo', 'baz');
+ // setHeader overwrites any existing values
+ $request->setHeader('Test', '123');
+
+ // Request headers can be cast as a string
+ echo $request->getHeader('Foo');
+ // >>> bar, baz
+ echo $request->getHeader('Test');
+ // >>> 123
+
+ // You can count the number of headers of a particular case insensitive name
+ echo count($request->getHeader('foO'));
+ // >>> 2
+
+ // You can iterate over Header objects
+ foreach ($request->getHeader('foo') as $header) {
+ echo $header . "\n";
+ }
+
+ // You can get all of the request headers as a Guzzle\Http\Message\Header\HeaderCollection object
+ $headers = $request->getHeaders();
+
+ // Missing headers return NULL
+ var_export($request->getHeader('Missing'));
+ // >>> null
+
+ // You can see all of the different variations of a header by calling raw() on the Header
+ var_export($request->getHeader('foo')->raw());
+
+Setting the body of a request
+-----------------------------
+
+Requests that can send a body (e.g. PUT, POST, DELETE, PATCH) are instances of
+``Guzzle\Http\Message\EntityEnclosingRequestInterface``. Entity enclosing requests contain several methods that allow
+you to specify the body to send with a request.
+
+Use the ``setBody()`` method of a request to set the body that will be sent with a request. This method accepts a
+string, a resource returned by ``fopen()``, an array, or an instance of ``Guzzle\Http\EntityBodyInterface``. The body
+will then be streamed from the underlying ``EntityBodyInterface`` object owned by the request. When setting the body
+of the request, you can optionally specify a Content-Type header and whether or not to force the request to use
+chunked Transfer-Encoding.
+
+.. code-block:: php
+
+ $request = $client->put('/user.json');
+ $request->setBody('{"foo":"baz"}', 'application/json');
+
+Content-Type header
+~~~~~~~~~~~~~~~~~~~
+
+Guzzle will automatically add a Content-Type header to a request if the Content-Type can be guessed based on the file
+extension of the payload being sent or the file extension present in the path of a request.
+
+.. code-block:: php
+
+ $request = $client->put('/user.json', array(), '{"foo":"bar"}');
+ // The Content-Type was guessed based on the path of the request
+ echo $request->getHeader('Content-Type');
+ // >>> application/json
+
+ $request = $client->put('/user.json');
+ $request->setBody(fopen('/tmp/user_data.json', 'r'));
+ // The Content-Type was guessed based on the path of the entity body
+ echo $request->getHeader('Content-Type');
+ // >>> application/json
+
+Transfer-Encoding: chunked header
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When sending HTTP requests that contain a payload, you must let the remote server know how to determine when the entire
+message has been sent. This usually is done by supplying a ``Content-Length`` header that tells the origin server the
+size of the body that is to be sent. In some cases, the size of the payload being sent in a request cannot be known
+before initiating the transfer. In these cases (when using HTTP/1.1), you can use the ``Transfer-Encoding: chunked``
+header.
+
+If the Content-Length cannot be determined (i.e. using a PHP ``http://`` stream), then Guzzle will automatically add
+the ``Transfer-Encoding: chunked`` header to the request.
+
+.. code-block:: php
+
+ $request = $client->put('/user.json');
+ $request->setBody(fopen('http://httpbin.org/get', 'r'));
+
+ // The Content-Length could not be determined
+ echo $request->getHeader('Transfer-Encoding');
+ // >>> chunked
+
+See :doc:`/http-client/entity-bodies` for more information on entity bodies.
+
+Expect: 100-Continue header
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Expect: 100-Continue`` header is used to help a client prevent sending a large payload to a server that will
+reject the request. This allows clients to fail fast rather than waste bandwidth sending an erroneous payload. Guzzle
+will automatically add the ``Expect: 100-Continue`` header to a request when the size of the payload exceeds 1MB or if
+the body of the request is not seekable (this helps to prevent errors when a non-seekable body request is redirected).
+
+.. note::
+
+ If you find that your larger requests are taking too long to complete, you should first check if the
+ ``Expect: 100-Continue`` header is being sent with the request. Some servers do not respond well to this header,
+ which causes cURL to sleep for `1 second <http://curl.haxx.se/mail/lib-2010-01/0182.html>`_.
+
+POST fields and files
+~~~~~~~~~~~~~~~~~~~~~
+
+Any entity enclosing request can send POST style fields and files. This includes POST, PUT, PATCH, and DELETE requests.
+Any request that has set POST fields or files will use cURL's POST message functionality.
+
+.. code-block:: php
+
+ $request = $client->post('/post');
+ // Set an overwrite any previously specified value
+ $request->setPostField('foo', 'bar');
+ // Append a value to any existing values
+ $request->getPostFields()->add('foo', 'baz');
+ // Remove a POST field by name
+ $request->removePostField('fizz');
+
+ // Add a file to upload (forces multipart/form-data)
+ $request->addPostFile('my_file', '/path/to/file', 'plain/text');
+ // Remove a POST file by POST key name
+ $request->removePostFile('my_other_file');
+
+.. tip::
+
+ Adding a large number of POST fields to a POST request is faster if you use the ``addPostFields()`` method so that
+ you can add and process multiple fields with a single call. Adding multiple POST files is also faster using
+ ``addPostFiles()``.
+
+Working with cookies
+--------------------
+
+Cookies can be modified and retrieved from a request using the following methods:
+
+.. code-block:: php
+
+ $request->addCookie($name, $value);
+ $request->removeCookie($name);
+ $value = $request->getCookie($name);
+ $valueArray = $request->getCookies();
+
+Use the :doc:`cookie plugin </plugins/cookie-plugin>` if you need to reuse cookies between requests.
+
+.. _request-set-response-body:
+
+Changing where a response is downloaded
+----------------------------------------
+
+When a request is sent, the body of the response will be stored in a PHP temp stream by default. You can change the
+location in which the response will be downloaded using ``$request->setResponseBody($body)`` or the ``save_to`` request
+option. This can be useful for downloading the contents of a URL to a specific file.
+
+Here's an example of using request options:
+
+.. code-block:: php
+
+ $request = $this->client->get('http://example.com/large.mov', array(), array(
+ 'save_to' => '/tmp/large_file.mov'
+ ));
+ $request->send();
+ var_export(file_exists('/tmp/large_file.mov'));
+ // >>> true
+
+Here's an example of using ``setResponseBody()``:
+
+.. code-block:: php
+
+ $body = fopen('/tmp/large_file.mov', 'w');
+ $request = $this->client->get('http://example.com/large.mov');
+ $request->setResponseBody($body);
+
+ // You can more easily specify the name of a file to save the contents
+ // of the response to by passing a string to ``setResponseBody()``.
+
+ $request = $this->client->get('http://example.com/large.mov');
+ $request->setResponseBody('/tmp/large_file.mov');
+
+Custom cURL options
+-------------------
+
+Most of the functionality implemented in the libcurl bindings has been simplified and abstracted by Guzzle. Developers
+who need access to `cURL specific functionality <http://www.php.net/curl_setopt>`_ can still add cURL handle
+specific behavior to Guzzle HTTP requests by modifying the cURL options collection of a request:
+
+.. code-block:: php
+
+ $request->getCurlOptions()->set(CURLOPT_LOW_SPEED_LIMIT, 200);
+
+Other special options that can be set in the ``curl.options`` array include:
+
++-------------------------+---------------------------------------------------------------------------------+
+| debug | Adds verbose cURL output to a temp stream owned by the cURL handle object |
++-------------------------+---------------------------------------------------------------------------------+
+| progress | Instructs cURL to emit events when IO events occur. This allows you to be |
+| | notified when bytes are transferred over the wire by subscribing to a request's |
+| | ``curl.callback.read``, ``curl.callback.write``, and ``curl.callback.progress`` |
+| | events. |
++-------------------------+---------------------------------------------------------------------------------+
+
+Request options
+---------------
+
+Requests options can be specified when creating a request or in the ``request.options`` parameter of a client. These
+options can control various aspects of a request including: headers to send, query string data, where the response
+should be downloaded, proxies, auth, etc.
+
+.. code-block:: php
+
+ $request = $client->get($url, $headers, array('proxy' => 'http://proxy.com'));
+
+See :ref:`Request options <request-options>` for more information.
+
+Working with errors
+-------------------
+
+HTTP errors
+~~~~~~~~~~~
+
+Requests that receive a 4xx or 5xx response will throw a ``Guzzle\Http\Exception\BadResponseException``. More
+specifically, 4xx errors throw a ``Guzzle\Http\Exception\ClientErrorResponseException``, and 5xx errors throw a
+``Guzzle\Http\Exception\ServerErrorResponseException``. You can catch the specific exceptions or just catch the
+BadResponseException to deal with either type of error. Here's an example of catching a generic BadResponseException:
+
+.. code-block:: php
+
+ try {
+ $response = $client->get('/not_found.xml')->send();
+ } catch (Guzzle\Http\Exception\BadResponseException $e) {
+ echo 'Uh oh! ' . $e->getMessage();
+ echo 'HTTP request URL: ' . $e->getRequest()->getUrl() . "\n";
+ echo 'HTTP request: ' . $e->getRequest() . "\n";
+ echo 'HTTP response status: ' . $e->getResponse()->getStatusCode() . "\n";
+ echo 'HTTP response: ' . $e->getResponse() . "\n";
+ }
+
+Throwing an exception when a 4xx or 5xx response is encountered is the default behavior of Guzzle requests. This
+behavior can be overridden by adding an event listener with a higher priority than -255 that stops event propagation.
+You can subscribe to ``request.error`` to receive notifications any time an unsuccessful response is received.
+
+You can change the response that will be associated with the request by calling ``setResponse()`` on the
+``$event['request']`` object passed into your listener, or by changing the ``$event['response']`` value of the
+``Guzzle\Common\Event`` object that is passed to your listener. Transparently changing the response associated with a
+request by modifying the event allows you to retry failed requests without complicating the code that uses the client.
+This might be useful for sending requests to a web service that has expiring auth tokens. When a response shows that
+your token has expired, you can get a new token, retry the request with the new token, and return the successful
+response to the user.
+
+Here's an example of retrying a request using updated authorization credentials when a 401 response is received,
+overriding the response of the original request with the new response, and still allowing the default exception
+behavior to be called when other non-200 response status codes are encountered:
+
+.. code-block:: php
+
+ // Add custom error handling to any request created by this client
+ $client->getEventDispatcher()->addListener('request.error', function(Event $event) {
+
+ if ($event['response']->getStatusCode() == 401) {
+
+ $newRequest = $event['request']->clone();
+ $newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken());
+ $newResponse = $newRequest->send();
+
+ // Set the response object of the request without firing more events
+ $event['response'] = $newResponse;
+
+ // You can also change the response and fire the normal chain of
+ // events by calling $event['request']->setResponse($newResponse);
+
+ // Stop other events from firing when you override 401 responses
+ $event->stopPropagation();
+ }
+
+ });
+
+cURL errors
+~~~~~~~~~~~
+
+Connection problems and cURL specific errors can also occur when transferring requests using Guzzle. When Guzzle
+encounters cURL specific errors while transferring a single request, a ``Guzzle\Http\Exception\CurlException`` is
+thrown with an informative error message and access to the cURL error message.
+
+A ``Guzzle\Http\Exception\MultiTransferException`` exception is thrown when a cURL specific error occurs while
+transferring multiple requests in parallel. You can then iterate over all of the exceptions encountered during the
+transfer.
+
+Plugins and events
+------------------
+
+Guzzle request objects expose various events that allow you to hook in custom logic. A request object owns a
+``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling
+``$request->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or
+event subscribers (classes that listen to specific events of a dispatcher). You can add event subscribers to a request
+directly by just calling ``$request->addSubscriber($mySubscriber);``.
+
+.. _request-events:
+
+Events emitted from a request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A ``Guzzle\Http\Message\Request`` and ``Guzzle\Http\Message\EntityEnclosingRequest`` object emit the following events:
+
++------------------------------+--------------------------------------------+------------------------------------------+
+| Event name | Description | Event data |
++==============================+============================================+==========================================+
+| request.before_send | About to send request | * request: Request to be sent |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.sent | Sent the request | * request: Request that was sent |
+| | | * response: Received response |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.complete | Completed a full HTTP transaction | * request: Request that was sent |
+| | | * response: Received response |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.success | Completed a successful request | * request: Request that was sent |
+| | | * response: Received response |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.error | Completed an unsuccessful request | * request: Request that was sent |
+| | | * response: Received response |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.exception | An unsuccessful response was | * request: Request |
+| | received. | * response: Received response |
+| | | * exception: BadResponseException |
++------------------------------+--------------------------------------------+------------------------------------------+
+| request.receive.status_line | Received the start of a response | * line: Full response start line |
+| | | * status_code: Status code |
+| | | * reason_phrase: Reason phrase |
+| | | * previous_response: (e.g. redirect) |
++------------------------------+--------------------------------------------+------------------------------------------+
+| curl.callback.progress | cURL progress event (only dispatched when | * handle: CurlHandle |
+| | ``emit_io`` is set on a request's curl | * download_size: Total download size |
+| | options) | * downloaded: Bytes downloaded |
+| | | * upload_size: Total upload bytes |
+| | | * uploaded: Bytes uploaded |
++------------------------------+--------------------------------------------+------------------------------------------+
+| curl.callback.write | cURL event called when data is written to | * request: Request |
+| | an outgoing stream | * write: Data being written |
++------------------------------+--------------------------------------------+------------------------------------------+
+| curl.callback.read | cURL event called when data is written to | * request: Request |
+| | an incoming stream | * read: Data being read |
++------------------------------+--------------------------------------------+------------------------------------------+
+
+Creating a request event listener
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here's an example that listens to the ``request.complete`` event of a request and prints the request and response.
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+
+ $request = $client->get('http://www.google.com');
+
+ // Echo out the response that was received
+ $request->getEventDispatcher()->addListener('request.complete', function (Event $e) {
+ echo $e['request'] . "\n\n";
+ echo $e['response'];
+ });
diff --git a/vendor/guzzle/guzzle/docs/http-client/response.rst b/vendor/guzzle/guzzle/docs/http-client/response.rst
new file mode 100644
index 0000000..ba48731
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/response.rst
@@ -0,0 +1,141 @@
+======================
+Using Response objects
+======================
+
+Sending a request will return a ``Guzzle\Http\Message\Response`` object. You can view the raw HTTP response message by
+casting the Response object to a string. Casting the response to a string will return the entity body of the response
+as a string too, so this might be an expensive operation if the entity body is stored in a file or network stream. If
+you only want to see the response headers, you can call ``getRawHeaders()``.
+
+Response status line
+--------------------
+
+The different parts of a response's `status line <http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1>`_
+(the first line of the response HTTP message) are easily retrievable.
+
+.. code-block:: php
+
+ $response = $client->get('http://www.amazon.com')->send();
+
+ echo $response->getStatusCode(); // >>> 200
+ echo $response->getReasonPhrase(); // >>> OK
+ echo $response->getProtocol(); // >>> HTTP
+ echo $response->getProtocolVersion(); // >>> 1.1
+
+You can determine the type of the response using several helper methods:
+
+.. code-block:: php
+
+ $response->isSuccessful(); // true
+ $response->isInformational();
+ $response->isRedirect();
+ $response->isClientError();
+ $response->isServerError();
+
+Response headers
+----------------
+
+The Response object contains helper methods for retrieving common response headers. These helper methods normalize the
+variations of HTTP response headers.
+
+.. code-block:: php
+
+ $response->getCacheControl();
+ $response->getContentType();
+ $response->getContentLength();
+ $response->getContentEncoding();
+ $response->getContentMd5();
+ $response->getEtag();
+ // etc... There are methods for every known response header
+
+You can interact with the Response headers using the same exact methods used to interact with Request headers. See
+:ref:`http-message-headers` for more information.
+
+.. code-block:: php
+
+ echo $response->getHeader('Content-Type');
+ echo $response->getHeader('Content-Length');
+ echo $response->getHeaders()['Content-Type']; // PHP 5.4
+
+Response body
+-------------
+
+The entity body object of a response can be retrieved by calling ``$response->getBody()``. The response EntityBody can
+be cast to a string, or you can pass ``true`` to this method to retrieve the body as a string.
+
+.. code-block:: php
+
+ $request = $client->get('http://www.amazon.com');
+ $response = $request->send();
+ echo $response->getBody();
+
+See :doc:`/http-client/entity-bodies` for more information on entity bodies.
+
+JSON Responses
+~~~~~~~~~~~~~~
+
+You can easily parse and use a JSON response as an array using the ``json()`` method of a response. This method will
+always return an array if the response is valid JSON or if the response body is empty. You will get an exception if you
+call this method and the response is not valid JSON.
+
+.. code-block:: php
+
+ $data = $response->json();
+ echo gettype($data);
+ // >>> array
+
+XML Responses
+~~~~~~~~~~~~~
+
+You can easily parse and use a XML response as SimpleXMLElement object using the ``xml()`` method of a response. This
+method will always return a SimpleXMLElement object if the response is valid XML or if the response body is empty. You
+will get an exception if you call this method and the response is not valid XML.
+
+.. code-block:: php
+
+ $xml = $response->xml();
+ echo $xml->foo;
+ // >>> Bar!
+
+Streaming responses
+-------------------
+
+Some web services provide streaming APIs that allow a client to keep a HTTP request open for an extended period of
+time while polling and reading. Guzzle provides a simple way to convert HTTP request messages into
+``Guzzle\Stream\Stream`` objects so that you can send the initial headers of a request, read the response headers, and
+pull in the response body manually as needed.
+
+Here's an example using the Twitter Streaming API to track the keyword "bieber":
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Stream\PhpStreamRequestFactory;
+
+ $client = new Client('https://stream.twitter.com/1');
+
+ $request = $client->post('statuses/filter.json', null, array(
+ 'track' => 'bieber'
+ ));
+
+ $request->setAuth('myusername', 'mypassword');
+
+ $factory = new PhpStreamRequestFactory();
+ $stream = $factory->fromRequest($request);
+
+ // Read until the stream is closed
+ while (!$stream->feof()) {
+ // Read a line from the stream
+ $line = $stream->readLine();
+ // JSON decode the line of data
+ $data = json_decode($line, true);
+ }
+
+You can use the ``stream`` request option when using a static client to more easily create a streaming response.
+
+.. code-block:: php
+
+ $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true));
+ while (!$stream->feof()) {
+ echo $stream->readLine();
+ }
diff --git a/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst b/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst
new file mode 100644
index 0000000..c18ac3e
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst
@@ -0,0 +1,52 @@
+=============
+URI templates
+=============
+
+The ``$uri`` passed to one of the client's request creational methods or the base URL of a client can utilize URI
+templates. Guzzle supports the entire `URI templates RFC <http://tools.ietf.org/html/rfc6570>`_. URI templates add a
+special syntax to URIs that replace template place holders with user defined variables.
+
+Every request created by a Guzzle HTTP client passes through a URI template so that URI template expressions are
+automatically expanded:
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client('https://example.com/', array('a' => 'hi'));
+ $request = $client->get('/{a}');
+
+Because of URI template expansion, the URL of the above request will become ``https://example.com/hi``. Notice that
+the template was expanded using configuration variables of the client. You can pass in custom URI template variables
+by passing the URI of your request as an array where the first index of the array is the URI template and the second
+index of the array are template variables that are merged into the client's configuration variables.
+
+.. code-block:: php
+
+ $request = $client->get(array('/test{?a,b}', array('b' => 'there')));
+
+The URL for this request will become ``https://test.com?a=hi&b=there``. URI templates aren't limited to just simple
+variable replacements; URI templates can provide an enormous amount of flexibility when creating request URIs.
+
+.. code-block:: php
+
+ $request = $client->get(array('http://example.com{+path}{/segments*}{?query,data*}', array(
+ 'path' => '/foo/bar',
+ 'segments' => array('one', 'two'),
+ 'query' => 'test',
+ 'data' => array(
+ 'more' => 'value'
+ )
+ )));
+
+The resulting URL would become ``http://example.com/foo/bar/one/two?query=test&more=value``.
+
+By default, URI template expressions are enclosed in an opening and closing brace (e.g. ``{var}``). If you are working
+with a web service that actually uses braces (e.g. Solr), then you can specify a custom regular expression to use to
+match URI template expressions.
+
+.. code-block:: php
+
+ $client->getUriTemplate()->setRegex('/\<\$(.+)\>/');
+ $client->get('/<$a>');
+
+You can learn about all of the different features of URI templates by reading the
+`URI templates RFC <http://tools.ietf.org/html/rfc6570>`_.
diff --git a/vendor/guzzle/guzzle/docs/index.rst b/vendor/guzzle/guzzle/docs/index.rst
new file mode 100644
index 0000000..f76f3bb
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/index.rst
@@ -0,0 +1,5 @@
+.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services
+.. toctree::
+ :hidden:
+
+ docs.rst
diff --git a/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst b/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst
new file mode 100644
index 0000000..a5c7fd3
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst
@@ -0,0 +1,97 @@
+================
+Guzzle iterators
+================
+
+Guzzle provides several SPL iterators that can be used with other SPL iterators, including Guzzle resource iterators.
+Guzzle's ``guzzle/iterator`` component can also be used independently of the rest of Guzzle through Packagist and
+Composer: https://packagist.org/packages/guzzle/iterator
+
+ChunkedIterator
+---------------
+
+Pulls out multiple values from an inner iterator and yields and array of values for each outer iteration -- essentially
+pulling out chunks of values from the inner iterator.
+
+.. code-block:: php
+
+ use Guzzle\Iterator\ChunkedIterator;
+
+ $inner = new ArrayIterator(range(0, 8));
+ $chunkedIterator = new ChunkedIterator($inner, 2);
+
+ foreach ($chunkedIterator as $chunk) {
+ echo implode(', ', $chunk) . "\n";
+ }
+
+ // >>> 0, 1
+ // >>> 2, 3
+ // >>> 4, 5
+ // >>> 6, 7
+ // >>> 8
+
+FilterIterator
+--------------
+
+This iterator is used to filter values out of the inner iterator. This iterator can be used when PHP 5.4's
+CallbackFilterIterator is not available.
+
+.. code-block:: php
+
+ use Guzzle\Iterator\FilterIterator;
+
+ $inner = new ArrayIterator(range(1, 10));
+ $filterIterator = new FilterIterator($inner, function ($value) {
+ return $value % 2;
+ });
+
+ foreach ($filterIterator as $value) {
+ echo $value . "\n";
+ }
+
+ // >>> 2
+ // >>> 4
+ // >>> 6
+ // >>> 8
+ // >>> 10
+
+MapIterator
+-----------
+
+This iterator modifies the values of the inner iterator before yielding.
+
+.. code-block:: php
+
+ use Guzzle\Iterator\MapIterator;
+
+ $inner = new ArrayIterator(range(0, 3));
+
+ $mapIterator = new MapIterator($inner, function ($value) {
+ return $value * 10;
+ });
+
+ foreach ($mapIterator as $value) {
+ echo $value . "\n";
+ }
+
+ // >>> 0
+ // >>> 10
+ // >>> 20
+ // >>> 30
+
+MethodProxyIterator
+-------------------
+
+This decorator is useful when you need to expose a specific method from an inner iterator that might be wrapper
+by one or more iterator decorators. This decorator proxies missing method calls to each inner iterator until one
+of the inner iterators can fulfill the call.
+
+.. code-block:: php
+
+ use Guzzle\Iterator\MethodProxyIterator;
+
+ $inner = new \ArrayIterator();
+ $proxy = new MethodProxyIterator($inner);
+
+ // Proxy method calls to the ArrayIterator
+ $proxy->append('a');
+ $proxy->append('b');
diff --git a/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst b/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst
new file mode 100644
index 0000000..ce0bee5
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst
@@ -0,0 +1,149 @@
+==================
+Resource iterators
+==================
+
+Web services often implement pagination in their responses which requires the end-user to issue a series of consecutive
+requests in order to fetch all of the data they asked for. Users of your web service client should not be responsible
+for implementing the logic involved in iterating through pages of results. Guzzle provides a simple resource iterator
+foundation to make it easier on web service client developers to offer a useful abstraction layer.
+
+Getting an iterator from a client
+---------------------------------
+
+ ResourceIteratorInterface Guzzle\Service\Client::getIterator($command [, array $commandOptions, array $iteratorOptions ])
+
+The ``getIterator`` method of a ``Guzzle\Service\ClientInterface`` object provides a convenient interface for
+instantiating a resource iterator for a specific command. This method implicitly uses a
+``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object to create resource iterators. Pass an
+instantiated command object or the name of a command in the first argument. When passing the name of a command, the
+command factory of the client will create the command by name using the ``$commandOptions`` array. The third argument
+may be used to pass an array of options to the constructor of the instantiated ``ResourceIteratorInterface`` object.
+
+.. code-block:: php
+
+ $iterator = $client->getIterator('get_users');
+
+ foreach ($iterator as $user) {
+ echo $user['name'] . ' age ' . $user['age'] . PHP_EOL;
+ }
+
+The above code sample might execute a single request or a thousand requests. As a consumer of a web service, I don't
+care. I just want to iterate over all of the users.
+
+Iterator options
+~~~~~~~~~~~~~~~~
+
+The two universal options that iterators should support are ``limit`` and ``page_size``. Using the ``limit`` option
+tells the resource iterator to attempt to limit the total number of iterated resources to a specific amount. Keep in
+mind that this is not always possible due to limitations that may be inherent to a web service. The ``page_size``
+option is used to tell a resource iterator how many resources to request per page of results. Much like the ``limit``
+option, you can not rely on getting back exactly the number of resources your specify in the ``page_size`` option.
+
+.. note::
+
+ The ``limit`` and ``page_size`` options can also be specified on an iterator using the ``setLimit($limit)`` and
+ ``setPageSize($pageSize)`` methods.
+
+Resolving iterator class names
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The default resource iterator factory of a client object expects that your iterators are stored under the ``Model``
+folder of your client and that an iterator is names after the CamelCase name of a command followed by the word
+"Iterator". For example, if you wanted to create an iterator for the ``get_users`` command, then your iterator class
+would be ``Model\GetUsersIterator`` and would be stored in ``Model/GetUsersIterator.php``.
+
+Creating an iterator
+--------------------
+
+While not required, resource iterators in Guzzle typically iterate using a ``Guzzle\Service\Command\CommandInterface``
+object. ``Guzzle\Service\Resource\ResourceIterator``, the default iterator implementation that you should extend,
+accepts a command object and array of iterator options in its constructor. The command object passed to the resource
+iterator is expected to be ready to execute and not previously executed. The resource iterator keeps a reference of
+this command and clones the original command each time a subsequent request needs to be made to fetch more data.
+
+Implement the sendRequest method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The most important thing (and usually the only thing) you need to do when creating a resource iterator is to implement
+the ``sendRequest()`` method of the resource iterator. The ``sendRequest()`` method is called when you begin
+iterating or if there are no resources left to iterate and it you expect to retrieve more resources by making a
+subsequent request. The ``$this->command`` property of the resource iterator is updated with a cloned copy of the
+original command object passed into the constructor of the iterator. Use this command object to issue your subsequent
+requests.
+
+The ``sendRequest()`` method must return an array of the resources you retrieved from making the subsequent call.
+Returning an empty array will stop the iteration. If you suspect that your web service client will occasionally return
+an empty result set but still requires further iteration, then you must implement a sort of loop in your
+``sendRequest()`` method that will continue to issue subsequent requests until your reach the end of the paginated
+result set or until additional resources are retrieved from the web service.
+
+Update the nextToken property
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Beyond fetching more results, the ``sendRequest()`` method is responsible for updating the ``$this->nextToken``
+property of the iterator. Setting this property to anything other than null tells the iterator that issuing a
+subsequent request using the nextToken value will probably return more results. You must continually update this
+value in your ``sendRequest()`` method as each response is received from the web service.
+
+Example iterator
+----------------
+
+Let's say you want to implement a resource iterator for the ``get_users`` command of your web service. The
+``get_users`` command receives a response that contains a list of users, and if there are more pages of results to
+retrieve, returns a value called ``next_user``. This return value is known as the **next token** and should be used to
+issue subsequent requests.
+
+Assume the response to a ``get_users`` command returns JSON data that looks like this:
+
+.. code-block:: javascript
+
+ {
+ "users": [
+ { "name": "Craig Johnson", "age": 10 },
+ { "name": "Tom Barker", "age": 20 },
+ { "name": "Bob Mitchell", "age": 74 }
+ ],
+ "next_user": "Michael Dowling"
+ }
+
+Assume that because there is a ``next_user`` value, there will be more users if a subsequent request is issued. If the
+``next_user`` value is missing or null, then we know there are no more results to fetch. Let's implement a resource
+iterator for this command.
+
+.. code-block:: php
+
+ namespace MyService\Model;
+
+ use Guzzle\Service\Resource\ResourceIterator;
+
+ /**
+ * Iterate over a get_users command
+ */
+ class GetUsersIterator extends ResourceIterator
+ {
+ protected function sendRequest()
+ {
+ // If a next token is set, then add it to the command
+ if ($this->nextToken) {
+ $this->command->set('next_user', $this->nextToken);
+ }
+
+ // Execute the command and parse the result
+ $result = $this->command->execute();
+
+ // Parse the next token
+ $this->nextToken = isset($result['next_user']) ? $result['next_user'] : false;
+
+ return $result['users'];
+ }
+ }
+
+As you can see, it's pretty simple to implement an iterator. There are a few things that you should notice from this
+example:
+
+1. You do not need to create a new command in the ``sendRequest()`` method. A new command object is cloned from the
+ original command passed into the constructor of the iterator before the ``sendRequest()`` method is called.
+ Remember that the resource iterator expects a command that has not been executed.
+2. When the ``sendRequest()`` method is first called, you will not have a ``$this->nextToken`` value, so always check
+ before setting it on a command. Notice that the next token is being updated each time a request is sent.
+3. After fetching more resources from the service, always return an array of resources.
diff --git a/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst
new file mode 100644
index 0000000..9bd8f42
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst
@@ -0,0 +1,18 @@
+============
+Async plugin
+============
+
+The AsyncPlugin allows you to send requests that do not wait on a response. This is handled through cURL by utilizing
+the progress event. When a request has sent all of its data to the remote server, Guzzle adds a 1ms timeout on the
+request and instructs cURL to not download the body of the response. The async plugin then catches the exception and
+adds a mock response to the request, along with an X-Guzzle-Async header to let you know that the response was not
+fully downloaded.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Async\AsyncPlugin;
+
+ $client = new Client('http://www.example.com');
+ $client->addSubscriber(new AsyncPlugin());
+ $response = $client->get()->send();
diff --git a/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst
new file mode 100644
index 0000000..5a76941
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst
@@ -0,0 +1,22 @@
+====================
+Backoff retry plugin
+====================
+
+The ``Guzzle\Plugin\Backoff\BackoffPlugin`` automatically retries failed HTTP requests using custom backoff strategies:
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Backoff\BackoffPlugin;
+
+ $client = new Client('http://www.test.com/');
+ // Use a static factory method to get a backoff plugin using the exponential backoff strategy
+ $backoffPlugin = BackoffPlugin::getExponentialBackoff();
+
+ // Add the backoff plugin to the client object
+ $client->addSubscriber($backoffPlugin);
+
+The BackoffPlugin's constructor accepts a ``Guzzle\Plugin\Backoff\BackoffStrategyInterface`` object that is used to
+determine when a retry should be issued and how long to delay between retries. The above code example shows how to
+attach a BackoffPlugin to a client that is pre-configured to retry failed 500 and 503 responses using truncated
+exponential backoff (emulating the behavior of Guzzle 2's ExponentialBackoffPlugin).
diff --git a/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst
new file mode 100644
index 0000000..d2fd5df
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst
@@ -0,0 +1,169 @@
+=================
+HTTP Cache plugin
+=================
+
+Guzzle can leverage HTTP's caching specifications using the ``Guzzle\Plugin\Cache\CachePlugin``. The CachePlugin
+provides a private transparent proxy cache that caches HTTP responses. The caching logic, based on
+`RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html>`_, uses HTTP headers to control caching behavior,
+cache lifetime, and supports Vary, ETag, and Last-Modified based revalidation:
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Doctrine\Common\Cache\FilesystemCache;
+ use Guzzle\Cache\DoctrineCacheAdapter;
+ use Guzzle\Plugin\Cache\CachePlugin;
+ use Guzzle\Plugin\Cache\DefaultCacheStorage;
+
+ $client = new Client('http://www.test.com/');
+
+ $cachePlugin = new CachePlugin(array(
+ 'storage' => new DefaultCacheStorage(
+ new DoctrineCacheAdapter(
+ new FilesystemCache('/path/to/cache/files')
+ )
+ )
+ ));
+
+ // Add the cache plugin to the client object
+ $client->addSubscriber($cachePlugin);
+ $client->get('http://www.wikipedia.org/')->send();
+
+ // The next request will revalidate against the origin server to see if it
+ // has been modified. If a 304 response is received the response will be
+ // served from cache
+ $client->get('http://www.wikipedia.org/')->send();
+
+The cache plugin intercepts GET and HEAD requests before they are actually transferred to the origin server. The cache
+plugin then generates a hash key based on the request method and URL, and checks to see if a response exists in the cache. If
+a response exists in the cache, the cache adapter then checks to make sure that the caching rules associated with the response
+satisfy the request, and ensures that response still fresh. If the response is acceptable for the request any required
+revalidation, then the cached response is served instead of contacting the origin server.
+
+Vary
+----
+
+Cache keys are derived from a request method and a request URL. Multiple responses can map to the same cache key and
+stored in Guzzle's underlying cache storage object. You should use the ``Vary`` HTTP header to tell the cache storage
+object that the cache response must have been cached for a request that matches the headers specified in the Vary header
+of the request. This allows you to have specific cache entries for the same request URL but variations in a request's
+headers determine which cache entry is served. Please see the http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+for more information.
+
+Cache options
+-------------
+
+There are several options you can add to requests or clients to modify the behavior of the cache plugin.
+
+Override cache TTL
+~~~~~~~~~~~~~~~~~~
+
+You can override the number of seconds a cacheable response is stored in the cache by setting the
+``cache.override_ttl`` parameter on the params object of a request:
+
+.. code-block:: php
+
+ // If the response to the request is cacheable, then the response will be cached for 100 seconds
+ $request->getParams()->set('cache.override_ttl', 100);
+
+If a response doesn't specify any freshness policy, it will be kept in cache for 3600 seconds by default.
+
+Custom caching decision
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If the service you are interacting with does not return caching headers or returns responses that are normally
+something that would not be cached, you can set a custom ``can_cache`` object on the constructor of the CachePlugin
+and provide a ``Guzzle\Plugin\Cache\CanCacheInterface`` object. You can use the
+``Guzzle\Plugin\Cache\CallbackCanCacheStrategy`` to easily make a caching decision based on an HTTP request and
+response.
+
+Revalidation options
+~~~~~~~~~~~~~~~~~~~~
+
+You can change the revalidation behavior of a request using the ``cache.revalidate`` parameter. Setting this
+parameter to ``never`` will ensure that a revalidation request is never sent, and the response is always served from
+the origin server. Setting this parameter to ``skip`` will never revalidate and uses the response stored in the cache.
+
+Normalizing requests for caching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``cache.key_filter`` parameter if you wish to strip certain query string parameters from your
+request before creating a unique hash for the request. This parameter can be useful if your requests have query
+string values that cause each request URL to be unique (thus preventing a cache hit). The ``cache.key_filter``
+format is simply a comma separated list of query string values to remove from the URL when creating a cache key.
+For example, here we are saying that the ``a`` and ``q`` query string variables should be ignored when generating a
+cache key for the request:
+
+.. code-block:: php
+
+ $request->getParams()->set('cache.key_filter', 'a, q');
+
+Other options
+~~~~~~~~~~~~~
+
+There are many other options available to the CachePlugin that can meet almost any caching requirement, including
+custom revalidation implementations, custom cache key generators, custom caching decision strategies, and custom
+cache storage objects. Take a look the constructor of ``Guzzle\Plugin\Cache\CachePlugin`` for more information.
+
+Setting Client-wide cache settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can specify cache settings for every request created by a client by adding cache settings to the configuration
+options of a client.
+
+.. code-block:: php
+
+ $client = new Guzzle\Http\Client('http://www.test.com', array(
+ 'request.params' => array(
+ 'cache.override_ttl' => 3600,
+ 'params.cache.revalidate' => 'never'
+ )
+ ));
+
+ echo $client->get('/')->getParams()->get('cache.override_ttl');
+ // >>> 3600
+
+ echo $client->get('/')->getParams()->get('cache.revalidate');
+ // >>> never
+
+Cache revalidation
+------------------
+
+If the cache plugin determines that a response to a GET request needs revalidation, a conditional GET is transferred
+to the origin server. If the origin server returns a 304 response, then a response containing the merged headers of
+the cached response with the new response and the entity body of the cached response is returned. Custom revalidation
+strategies can be injected into a CachePlugin if needed.
+
+Cache adapters
+--------------
+
+Guzzle doesn't try to reinvent the wheel when it comes to caching or logging. Plenty of other frameworks have
+excellent solutions in place that you are probably already using in your applications. Guzzle uses adapters for
+caching and logging. The cache plugin requires a cache adapter so that is can store responses in a cache. Guzzle
+currently supports cache adapters for `Doctrine 2.0 <http://www.doctrine-project.org/>`_ and the
+`Zend Framework <http://framework.zend.com>`_.
+
+Doctrine cache adapter
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: php
+
+ use Doctrine\Common\Cache\ArrayCache;
+ use Guzzle\Cache\DoctrineCacheAdapter;
+ use Guzzle\Plugin\Cache\CachePlugin;
+
+ $backend = new ArrayCache();
+ $adapter = new DoctrineCacheAdapter($backend);
+ $cache = new CachePlugin($adapter);
+
+Zend Framework cache adapter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: php
+
+ use Guzzle\Cache\ZendCacheAdapter;
+ use Zend\Cache\Backend\TestBackend;
+
+ $backend = new TestBackend();
+ $adapter = new ZendCacheAdapter($backend);
+ $cache = new CachePlugin($adapter);
diff --git a/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst
new file mode 100644
index 0000000..a6cc7d9
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst
@@ -0,0 +1,33 @@
+=============
+Cookie plugin
+=============
+
+Some web services require a Cookie in order to maintain a session. The ``Guzzle\Plugin\Cookie\CookiePlugin`` will add
+cookies to requests and parse cookies from responses using a CookieJar object:
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Cookie\CookiePlugin;
+ use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;
+
+ $cookiePlugin = new CookiePlugin(new ArrayCookieJar());
+
+ // Add the cookie plugin to a client
+ $client = new Client('http://www.test.com/');
+ $client->addSubscriber($cookiePlugin);
+
+ // Send the request with no cookies and parse the returned cookies
+ $client->get('http://www.yahoo.com/')->send();
+
+ // Send the request again, noticing that cookies are being sent
+ $request = $client->get('http://www.yahoo.com/');
+ $request->send();
+
+ echo $request;
+
+You can disable cookies per-request by setting the ``cookies.disable`` value to true on a request's params object.
+
+.. code-block:: php
+
+ $request->getParams()->set('cookies.disable', true);
diff --git a/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst b/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst
new file mode 100644
index 0000000..0870155
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst
@@ -0,0 +1,93 @@
+================
+Creating plugins
+================
+
+.. highlight:: php
+
+Guzzle is extremely extensible because of the behavioral modifications that can be added to requests, clients, and
+commands using an event system. Before and after the majority of actions are taken in the library, an event is emitted
+with the name of the event and context surrounding the event. Observers can subscribe to a subject and modify the
+subject based on the events received. Guzzle's event system utilizes the Symfony2 EventDispatcher and is the backbone
+of its plugin architecture.
+
+Overview
+--------
+
+Plugins must implement the ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` interface. The
+``EventSubscriberInterface`` requires that your class implements a static method, ``getSubscribedEvents()``, that
+returns an associative array mapping events to methods on the object. See the
+`Symfony2 documentation <http://symfony.com/doc/2.0/book/internals.html#the-event-dispatcher>`_ for more information.
+
+Plugins can be attached to any subject, or object in Guzzle that implements that
+``Guzzle\Common\HasDispatcherInterface``.
+
+Subscribing to a subject
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can subscribe an instantiated observer to an event by calling ``addSubscriber`` on a subject.
+
+.. code-block:: php
+
+ $testPlugin = new TestPlugin();
+ $client->addSubscriber($testPlugin);
+
+You can also subscribe to only specific events using a closure::
+
+ $client->getEventDispatcher()->addListener('request.create', function(Event $event) {
+ echo $event->getName();
+ echo $event['request'];
+ });
+
+``Guzzle\Common\Event`` objects are passed to notified functions. The Event object has a ``getName()`` method which
+return the name of the emitted event and may contain contextual information that can be accessed like an array.
+
+Knowing what events to listen to
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Any class that implements the ``Guzzle\Common\HasDispatcherInterface`` must implement a static method,
+``getAllEvents()``, that returns an array of the events that are emitted from the object. You can browse the source
+to see each event, or you can call the static method directly in your code to get a list of available events.
+
+Event hooks
+-----------
+
+* :ref:`client-events`
+* :ref:`service-client-events`
+* :ref:`request-events`
+* ``Guzzle\Http\Curl\CurlMulti``:
+* :ref:`service-builder-events`
+
+Examples of the event system
+----------------------------
+
+Simple Echo plugin
+~~~~~~~~~~~~~~~~~~
+
+This simple plugin prints a string containing the request that is about to be sent by listening to the
+``request.before_send`` event::
+
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+ class EchoPlugin implements EventSubscriberInterface
+ {
+ public static function getSubscribedEvents()
+ {
+ return array('request.before_send' => 'onBeforeSend');
+ }
+
+ public function onBeforeSend(Guzzle\Common\Event $event)
+ {
+ echo 'About to send a request: ' . $event['request'] . "\n";
+ }
+ }
+
+ $client = new Guzzle\Service\Client('http://www.test.com/');
+
+ // Create the plugin and add it as an event subscriber
+ $plugin = new EchoPlugin();
+ $client->addSubscriber($plugin);
+
+ // Send a request and notice that the request is printed to the screen
+ $client->get('/')->send();
+
+Running the above code will print a string containing the HTTP request that is about to be sent.
diff --git a/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst
new file mode 100644
index 0000000..66d4a01
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst
@@ -0,0 +1,32 @@
+==========================
+cURL authentication plugin
+==========================
+
+.. warning::
+
+ The CurlAuthPlugin is deprecated. You should use the `auth` parameter of a client to add authorization headers to
+ every request created by a client.
+
+ .. code-block:: php
+
+ $client->setDefaultOption('auth', array('username', 'password', 'Basic|Digest|NTLM|Any'));
+
+If your web service client requires basic authorization, then you can use the CurlAuthPlugin to easily add an
+Authorization header to each request sent by the client.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\CurlAuth\CurlAuthPlugin;
+
+ $client = new Client('http://www.test.com/');
+
+ // Add the auth plugin to the client object
+ $authPlugin = new CurlAuthPlugin('username', 'password');
+ $client->addSubscriber($authPlugin);
+
+ $response = $client->get('projects/1/people')->send();
+ $xml = new SimpleXMLElement($response->getBody(true));
+ foreach ($xml->person as $person) {
+ echo $person->email . "\n";
+ }
diff --git a/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst
new file mode 100644
index 0000000..b96befe
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst
@@ -0,0 +1,24 @@
+==============
+History plugin
+==============
+
+The history plugin tracks all of the requests and responses sent through a request or client. This plugin can be
+useful for crawling or unit testing. By default, the history plugin stores up to 10 requests and responses.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\History\HistoryPlugin;
+
+ $client = new Client('http://www.test.com/');
+
+ // Add the history plugin to the client object
+ $history = new HistoryPlugin();
+ $history->setLimit(5);
+ $client->addSubscriber($history);
+
+ $client->get('http://www.yahoo.com/')->send();
+
+ echo $history->getLastRequest();
+ echo $history->getLastResponse();
+ echo count($history);
diff --git a/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst
new file mode 100644
index 0000000..3e2b229
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst
@@ -0,0 +1,69 @@
+==========
+Log plugin
+==========
+
+Use the ``Guzzle\Plugin\Log\LogPlugin`` to view all data sent over the wire, including entity bodies and redirects.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Log\Zf1LogAdapter;
+ use Guzzle\Plugin\Log\LogPlugin;
+ use Guzzle\Log\MessageFormatter;
+
+ $client = new Client('http://www.test.com/');
+
+ $adapter = new Zf1LogAdapter(
+ new \Zend_Log(new \Zend_Log_Writer_Stream('php://output'))
+ );
+ $logPlugin = new LogPlugin($adapter, MessageFormatter::DEBUG_FORMAT);
+
+ // Attach the plugin to the client, which will in turn be attached to all
+ // requests generated by the client
+ $client->addSubscriber($logPlugin);
+
+ $response = $client->get('http://google.com')->send();
+
+The code sample above wraps a ``Zend_Log`` object using a ``Guzzle\Log\Zf1LogAdapter``. After attaching the plugin to
+the client, all data sent over the wire will be logged to stdout.
+
+The first argument of the LogPlugin's constructor accepts a ``Guzzle\Log\LogAdapterInterface`` object. This object is
+an adapter that allows you to use the logging capabilities of your favorite log implementation. The second argument of
+the constructor accepts a ``Guzzle\Log\MessageFormatter`` or a log messaged format string. The format string uses
+variable substitution and allows you to define the log data that is important to your application. The different
+variables that can be injected are as follows:
+
+================== ====================================================================================
+Variable Substitution
+================== ====================================================================================
+{request} Full HTTP request message
+{response} Full HTTP response message
+{ts} Timestamp
+{host} Host of the request
+{method} Method of the request
+{url} URL of the request
+{host} Host of the request
+{protocol} Request protocol
+{version} Protocol version
+{resource} Resource of the request (path + query + fragment)
+{port} Port of the request
+{hostname} Hostname of the machine that sent the request
+{code} Status code of the response (if available)
+{phrase} Reason phrase of the response (if available)
+{curl_error} Curl error message (if available)
+{curl_code} Curl error code (if available)
+{curl_stderr} Curl standard error (if available)
+{connect_time} Time in seconds it took to establish the connection (if available)
+{total_time} Total transaction time in seconds for last transfer (if available)
+{req_header_*} Replace `*` with the lowercased name of a request header to add to the message
+{res_header_*} Replace `*` with the lowercased name of a response header to add to the message
+{req_body} Request body
+{res_body} Response body
+================== ====================================================================================
+
+The LogPlugin has a helper method that can be used when debugging that will output the full HTTP request and
+response of a transaction:
+
+.. code-block:: php
+
+ $client->addSubscriber(LogPlugin::getDebugPlugin());
diff --git a/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst
new file mode 100644
index 0000000..1b1cfa8
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst
@@ -0,0 +1,29 @@
+====================
+MD5 validator plugin
+====================
+
+Entity bodies can sometimes be modified over the wire due to a faulty TCP transport or misbehaving proxy. If an HTTP
+response contains a Content-MD5 header, then a MD5 hash of the entity body of a response can be compared against the
+Content-MD5 header of the response to determine if the response was delivered intact. The
+``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` will throw an ``UnexpectedValueException`` if the calculated MD5 hash does
+not match the Content-MD5 header value:
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Md5\Md5ValidatorPlugin;
+
+ $client = new Client('http://www.test.com/');
+
+ $md5Plugin = new Md5ValidatorPlugin();
+
+ // Add the md5 plugin to the client object
+ $client->addSubscriber($md5Plugin);
+
+ $request = $client->get('http://www.yahoo.com/');
+ $request->send();
+
+Calculating the MD5 hash of a large entity body or an entity body that was transferred using a Content-Encoding is an
+expensive operation. When working in high performance applications, you might consider skipping the MD5 hash
+validation for entity bodies bigger than a certain size or Content-Encoded entity bodies
+(see ``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` for more information).
diff --git a/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst
new file mode 100644
index 0000000..4900cb5
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst
@@ -0,0 +1,27 @@
+===========
+Mock plugin
+===========
+
+The mock plugin is useful for testing Guzzle clients. The mock plugin allows you to queue an array of responses that
+will satisfy requests sent from a client by consuming the request queue in FIFO order.
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Mock\MockPlugin;
+ use Guzzle\Http\Message\Response;
+
+ $client = new Client('http://www.test.com/');
+
+ $mock = new MockPlugin();
+ $mock->addResponse(new Response(200))
+ ->addResponse(new Response(404));
+
+ // Add the mock plugin to the client object
+ $client->addSubscriber($mock);
+
+ // The following request will receive a 200 response from the plugin
+ $client->get('http://www.example.com/')->send();
+
+ // The following request will receive a 404 response from the plugin
+ $client->get('http://www.test.com/')->send();
diff --git a/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst b/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst
new file mode 100644
index 0000000..e67eaba
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst
@@ -0,0 +1,30 @@
+============
+OAuth plugin
+============
+
+Guzzle ships with an OAuth 1.0 plugin that can sign requests using a consumer key, consumer secret, OAuth token,
+and OAuth secret. Here's an example showing how to send an authenticated request to the Twitter REST API:
+
+.. code-block:: php
+
+ use Guzzle\Http\Client;
+ use Guzzle\Plugin\Oauth\OauthPlugin;
+
+ $client = new Client('http://api.twitter.com/1');
+ $oauth = new OauthPlugin(array(
+ 'consumer_key' => 'my_key',
+ 'consumer_secret' => 'my_secret',
+ 'token' => 'my_token',
+ 'token_secret' => 'my_token_secret'
+ ));
+ $client->addSubscriber($oauth);
+
+ $response = $client->get('statuses/public_timeline.json')->send();
+
+If you need to use a custom signing method, you can pass a ``signature_method`` configuration option in the
+constructor of the OAuth plugin. The ``signature_method`` option must be a callable variable that accepts a string to
+sign and signing key and returns a signed string.
+
+.. note::
+
+ You can omit the ``token`` and ``token_secret`` options to use two-legged OAuth.
diff --git a/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc b/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc
new file mode 100644
index 0000000..8d6d09b
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc
@@ -0,0 +1,9 @@
+* :doc:`/plugins/async-plugin`
+* :doc:`/plugins/backoff-plugin`
+* :doc:`/plugins/cache-plugin`
+* :doc:`/plugins/cookie-plugin`
+* :doc:`/plugins/history-plugin`
+* :doc:`/plugins/log-plugin`
+* :doc:`/plugins/md5-validator-plugin`
+* :doc:`/plugins/mock-plugin`
+* :doc:`/plugins/oauth-plugin`
diff --git a/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst b/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst
new file mode 100644
index 0000000..19ae57e
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst
@@ -0,0 +1,59 @@
+======================
+Plugin system overview
+======================
+
+The workflow of sending a request and parsing a response is driven by Guzzle's event system, which is powered by the
+`Symfony2 Event Dispatcher component <http://symfony.com/doc/current/components/event_dispatcher/introduction.html>`_.
+
+Any object in Guzzle that emits events will implement the ``Guzzle\Common\HasEventDispatcher`` interface. You can add
+event subscribers directly to these objects using the ``addSubscriber()`` method, or you can grab the
+``Symfony\Component\EventDispatcher\EventDispatcher`` object owned by the object using ``getEventDispatcher()`` and
+add a listener or event subscriber.
+
+Adding event subscribers to clients
+-----------------------------------
+
+Any event subscriber or event listener attached to the EventDispatcher of a ``Guzzle\Http\Client`` or
+``Guzzle\Service\Client`` object will automatically be attached to all request objects created by the client. This
+allows you to attach, for example, a HistoryPlugin to a client object, and from that point on, every request sent
+through that client will utilize the HistoryPlugin.
+
+.. code-block:: php
+
+ use Guzzle\Plugin\History\HistoryPlugin;
+ use Guzzle\Service\Client;
+
+ $client = new Client();
+
+ // Create a history plugin and attach it to the client
+ $history = new HistoryPlugin();
+ $client->addSubscriber($history);
+
+ // Create and send a request. This request will also utilize the HistoryPlugin
+ $client->get('http://httpbin.org')->send();
+
+ // Echo out the last sent request by the client
+ echo $history->getLastRequest();
+
+.. tip::
+
+ :doc:`Create event subscribers <creating-plugins>`, or *plugins*, to implement reusable logic that can be
+ shared across clients. Event subscribers are also easier to test than anonymous functions.
+
+Pre-Built plugins
+-----------------
+
+Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications
+powered by the Symfony2 Event Dispatcher component.
+
+* :doc:`async-plugin`
+* :doc:`backoff-plugin`
+* :doc:`cache-plugin`
+* :doc:`cookie-plugin`
+* :doc:`curl-auth-plugin`
+* :doc:`history-plugin`
+* :doc:`log-plugin`
+* :doc:`md5-validator-plugin`
+* :doc:`mock-plugin`
+* :doc:`oauth-plugin`
+
diff --git a/vendor/guzzle/guzzle/docs/requirements.txt b/vendor/guzzle/guzzle/docs/requirements.txt
new file mode 100644
index 0000000..f62e318
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/requirements.txt
@@ -0,0 +1,2 @@
+Sphinx>=1.2b1
+guzzle_sphinx_theme>=0.5.0
diff --git a/vendor/guzzle/guzzle/docs/testing/unit-testing.rst b/vendor/guzzle/guzzle/docs/testing/unit-testing.rst
new file mode 100644
index 0000000..f4297af
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/testing/unit-testing.rst
@@ -0,0 +1,201 @@
+===========================
+Unit Testing Guzzle clients
+===========================
+
+Guzzle provides several tools that will enable you to easily unit test your web service clients.
+
+* PHPUnit integration
+* Mock responses
+* node.js web server for integration testing
+
+PHPUnit integration
+-------------------
+
+Guzzle is unit tested using `PHPUnit <http://www.phpunit.de/>`_. Your web service client's unit tests should extend
+``Guzzle\Tests\GuzzleTestCase`` so that you can take advantage of some of the built in helpers.
+
+In order to unit test your client, a developer would need to copy phpunit.xml.dist to phpunit.xml and make any needed
+modifications. As a best practice and security measure for you and your contributors, it is recommended to add an
+ignore statement to your SCM so that phpunit.xml is ignored.
+
+Bootstrapping
+~~~~~~~~~~~~~
+
+Your web service client should have a tests/ folder that contains a bootstrap.php file. The bootstrap.php file
+responsible for autoloading and configuring a ``Guzzle\Service\Builder\ServiceBuilder`` that is used throughout your
+unit tests for loading a configured client. You can add custom parameters to your phpunit.xml file that expects users
+to provide the path to their configuration data.
+
+.. code-block:: php
+
+ Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Aws\Common\Aws::factory($_SERVER['CONFIG']));
+
+ Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Guzzle\Service\Builder\ServiceBuilder::factory(array(
+ 'test.unfuddle' => array(
+ 'class' => 'Guzzle.Unfuddle.UnfuddleClient',
+ 'params' => array(
+ 'username' => 'test_user',
+ 'password' => '****',
+ 'subdomain' => 'test'
+ )
+ )
+ )));
+
+The above code registers a service builder that can be used throughout your unit tests. You would then be able to
+retrieve an instantiated and configured Unfuddle client by calling ``$this->getServiceBuilder()->get('test.unfuddle)``.
+The above code assumes that ``$_SERVER['CONFIG']`` contains the path to a file that stores service description
+configuration.
+
+Unit testing remote APIs
+------------------------
+
+Mock responses
+~~~~~~~~~~~~~~
+
+One of the benefits of unit testing is the ability to quickly determine if there are errors in your code. If your
+unit tests run slowly, then they become tedious and will likely be run less frequently. Guzzle's philosophy on unit
+testing web service clients is that no network access should be required to run the unit tests. This means that
+responses are served from mock responses or local servers. By adhering to this principle, tests will run much faster
+and will not require an external resource to be available. The problem with this approach is that your mock responses
+must first be gathered and then subsequently updated each time the remote API changes.
+
+Integration testing over the internet
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can perform integration testing with a web service over the internet by making calls directly to the service. If
+the web service you are requesting uses a complex signing algorithm or some other specific implementation, then you
+may want to include at least one actual network test that can be run specifically through the command line using
+`PHPUnit group annotations <http://www.phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group>`_.
+
+@group internet annotation
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When creating tests that require an internet connection, it is recommended that you add ``@group internet`` annotations
+to your unit tests to specify which tests require network connectivity.
+
+You can then `run PHPUnit tests <http://www.phpunit.de/manual/current/en/textui.html>`_ that exclude the @internet
+group by running ``phpunit --exclude-group internet``.
+
+API credentials
+^^^^^^^^^^^^^^^
+
+If API credentials are required to run your integration tests, you must add ``<php>`` parameters to your
+phpunit.xml.dist file and extract these parameters in your bootstrap.php file.
+
+.. code-block:: xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <phpunit bootstrap="./tests/bootstrap.php" colors="true">
+ <php>
+ <!-- Specify the path to a service configuration file -->
+ <server name="CONFIG" value="test_services.json" />
+ <!-- Or, specify each require parameter individually -->
+ <server name="API_USER" value="change_me" />
+ <server name="API_PASSWORD" value="****" />
+ </php>
+ <testsuites>
+ <testsuite name="guzzle-service">
+ <directory suffix="Test.php">./Tests</directory>
+ </testsuite>
+ </testsuites>
+ </phpunit>
+
+You can then extract the ``server`` variables in your bootstrap.php file by grabbing them from the ``$_SERVER``
+superglobal: ``$apiUser = $_SERVER['API_USER'];``
+
+Further reading
+^^^^^^^^^^^^^^^
+
+A good discussion on the topic of testing remote APIs can be found in Sebastian Bergmann's
+`Real-World Solutions for Developing High-Quality PHP Frameworks and Applications <http://www.amazon.com/dp/0470872497>`_.
+
+Queueing Mock responses
+-----------------------
+
+Mock responses can be used to test if requests are being generated correctly and responses and handled correctly by
+your client. Mock responses can be queued up for a client using the ``$this->setMockResponse($client, $path)`` method
+of your test class. Pass the client you are adding mock responses to and a single path or array of paths to mock
+response files relative to the ``/tests/mock/ folder``. This will queue one or more mock responses for your client by
+creating a simple observer on the client. Mock response files must contain a full HTTP response message:
+
+.. code-block:: none
+
+ HTTP/1.1 200 OK
+ Date: Wed, 25 Nov 2009 12:00:00 GMT
+ Connection: close
+ Server: AmazonS3
+ Content-Type: application/xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">EU</LocationConstraint>
+
+After queuing mock responses for a client, you can get an array of the requests that were sent by the client that
+were issued a mock response by calling ``$this->getMockedRequests()``.
+
+You can also use the ``Guzzle\Plugin\Mock\MockPlugin`` object directly with your clients.
+
+.. code-block:: php
+
+ $plugin = new Guzzle\Plugin\Mock\MockPlugin();
+ $plugin->addResponse(new Guzzle\Http\Message\Response(200));
+ $client = new Guzzle\Http\Client();
+ $client->addSubscriber($plugin);
+
+ // The following request will get the mock response from the plugin in FIFO order
+ $request = $client->get('http://www.test.com/');
+ $request->send();
+
+ // The MockPlugin maintains a list of requests that were mocked
+ $this->assertContainsOnly($request, $plugin->getReceivedRequests());
+
+node.js web server for integration testing
+------------------------------------------
+
+Using mock responses is usually enough when testing a web service client. If your client needs to add custom cURL
+options to requests, then you should use the node.js test web server to ensure that your HTTP request message is
+being created correctly.
+
+Guzzle is based around PHP's libcurl bindings. cURL sometimes modifies an HTTP request message based on
+``CURLOPT_*`` options. Headers that are added to your request by cURL will not be accounted for if you inject mock
+responses into your tests. Additionally, some request entity bodies cannot be loaded by the client before transmitting
+it to the sever (for example, when using a client as a sort of proxy and streaming content from a remote server). You
+might also need to inspect the entity body of a ``multipart/form-data`` POST request.
+
+.. note::
+
+ You can skip all of the tests that require the node.js test web server by excluding the ``server`` group:
+ ``phpunit --exclude-group server``
+
+Using the test server
+~~~~~~~~~~~~~~~~~~~~~
+
+The node.js test server receives requests and returns queued responses. The test server exposes a simple API that is
+used to enqueue responses and inspect the requests that it has received.
+
+Retrieve the server object by calling ``$this->getServer()``. If the node.js server is not running, it will be
+started as a forked process and an object that interfaces with the server will be returned. (note: stopping the
+server is handled internally by Guzzle.)
+
+You can queue an HTTP response or an array of responses by calling ``$this->getServer()->enqueue()``:
+
+.. code-block:: php
+
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+
+The above code queues a single 200 response with an empty body. Responses are queued using a FIFO order; this
+response will be returned by the server when it receives the first request and then removed from the queue. If a
+request is received by a server with no queued responses, an exception will be thrown in your unit test.
+
+You can inspect the requests that the server has retrieved by calling ``$this->getServer()->getReceivedRequests()``.
+This method accepts an optional ``$hydrate`` parameter that specifies if you are retrieving an array of string HTTP
+requests or an array of ``Guzzle\Http\RequestInterface`` subclassed objects. "Hydrating" the requests will allow
+greater flexibility in your unit tests so that you can easily assert the state of the various parts of a request.
+
+You will need to modify the base_url of your web service client in order to use it against the test server.
+
+.. code-block:: php
+
+ $client = $this->getServiceBuilder()->get('my_client');
+ $client->setBaseUrl($this->getServer()->getUrl());
+
+After running the above code, all calls made from the ``$client`` object will be sent to the test web server.
diff --git a/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst b/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst
new file mode 100644
index 0000000..ad6070b
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst
@@ -0,0 +1,619 @@
+===========================
+Guzzle service descriptions
+===========================
+
+Guzzle allows you to serialize HTTP requests and parse HTTP responses using a DSL called a service descriptions.
+Service descriptions define web service APIs by documenting each operation, the operation's parameters, validation
+options for each parameter, an operation's response, how the response is parsed, and any errors that can be raised for
+an operation. Writing a service description for a web service allows you to more quickly consume a web service than
+writing concrete commands for each web service operation.
+
+Guzzle service descriptions can be representing using a PHP array or JSON document. Guzzle's service descriptions are
+heavily inspired by `Swagger <http://swagger.wordnik.com/>`_.
+
+Service description schema
+==========================
+
+A Guzzle Service description must match the following JSON schema document. This document can also serve as a guide when
+implementing a Guzzle service description.
+
+Download the schema here: :download:`Guzzle JSON schema document </_downloads/guzzle-schema-1.0.json>`
+
+.. class:: overflow-height-500px
+
+ .. literalinclude:: ../_downloads/guzzle-schema-1.0.json
+ :language: json
+
+Top-level attributes
+--------------------
+
+Service descriptions are comprised of the following top-level attributes:
+
+.. code-block:: json
+
+ {
+ "name": "string",
+ "apiVersion": "string|number",
+ "baseUrl": "string",
+ "description": "string",
+ "operations": {},
+ "models": {},
+ "includes": ["string.php", "string.json"]
+ }
+
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| Property Name | Value | Description |
++=========================================+=========================+=======================================================================================================================+
+| name | string | Name of the web service |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| apiVersion | string|number | Version identifier that the service description is compatible with |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| baseUrl or basePath | string | Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the |
+| | | process defined in RFC 2396. Some clients require custom logic to determine the baseUrl. In those cases, it is best |
+| | | to not include a baseUrl in the service description, but rather allow the factory method of the client to configure |
+| | | the client’s baseUrl. |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| description | string | Short summary of the web service |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| operations | object containing | Operations of the service. The key is the name of the operation and value is the attributes of the operation. |
+| | :ref:`operation-schema` | |
+| | | |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| models | object containing | Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP |
+| | :ref:`model-schema` | response is parsed into a ``Guzzle\Service\Resource\Model`` object when an operation uses a ``model`` ``responseType``|
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| includes | array of .js, | Service description files to include and extend from (can be a .json, .js, or .php file) |
+| | .json, or .php | |
+| | files. | |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+| (any additional properties) | mixed | Any additional properties specified as top-level attributes are allowed and will be treated as arbitrary data |
++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+
+
+.. _operation-schema:
+
+Operations
+----------
+
+Operations are the actions that can be taken on a service. Each operation is given a unique name and has a distinct
+endpoint and HTTP method. If an API has a ``DELETE /users/:id`` operation, a satisfactory operation name might be
+``DeleteUser`` with a parameter of ``id`` that is inserted into the URI.
+
+.. class:: overflow-height-250px
+
+ .. code-block:: json
+
+ {
+ "operations": {
+ "operationName": {
+ "extends": "string",
+ "httpMethod": "GET|POST|PUT|DELETE|PATCH|string",
+ "uri": "string",
+ "summary": "string",
+ "class": "string",
+ "responseClass": "string",
+ "responseNotes": "string",
+ "type": "string",
+ "description": "string",
+ "responseType": "primitive|class|(model by name)|documentation|(string)",
+ "deprecated": false,
+ "errorResponses": [
+ {
+ "code": 500,
+ "reason": "Unexpected Error",
+ "class": "string"
+ }
+ ],
+ "data": {
+ "foo": "bar",
+ "baz": "bam"
+ },
+ "parameters": {}
+ }
+ }
+ }
+
+.. csv-table::
+ :header: "Property Name", "Value", "Description"
+ :widths: 20, 15, 65
+
+ "extends", "string", "Extend from another operation by name. The parent operation must be defined before the child."
+ "httpMethod", "string", "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)"
+ "uri", "string", "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri"
+ "summary", "string", "Short summary of what the operation does"
+ "class", "string", "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand. Using this attribute allows you to define an operation using a service description, but allows more customized logic to be implemented in user-land code."
+ "responseClass", "string", "Defined what is returned from the method. Can be a primitive, class name, or model name. You can specify the name of a class to return a more customized result from the operation (for example, a domain model object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``."
+ "responseNotes", "string", "A description of the response returned by the operation"
+ "responseType", "string", "The type of response that the operation creates: one of primitive, class, model, or documentation. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default."
+ "deprecated", "boolean", "Whether or not the operation is deprecated"
+ "errorResponses", "array", "Errors that could occur while executing the operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), 'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this error is encountered)"
+ "data", "object", "Any arbitrary data to associate with the operation"
+ "parameters", "object containing :ref:`parameter-schema` objects", "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request."
+ "additionalParameters", "A single :ref:`parameter-schema` object", "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined."
+
+additionalParameters
+~~~~~~~~~~~~~~~~~~~~
+
+When a webservice offers a large number of parameters that all are set in the same location (for example the query
+string or a JSON document), defining each parameter individually can require a lot of time and repetition. Furthermore,
+some web services allow for completely arbitrary parameters to be supplied for an operation. The
+``additionalParameters`` attribute can be used to solve both of these issues.
+
+As an example, we can define a Twitter API operation quite easily using ``additionalParameters``. The
+GetMentions operation accepts a large number of query string parameters. Defining each of these parameters
+is ideal because it provide much more introspection for the client and opens the possibility to use the description with
+other tools (e.g. a documentation generator). However, you can very quickly provide a "catch-all" serialization rule
+that will place any custom parameters supplied to an operation the generated request's query string parameters.
+
+.. class:: overflow-height-250px
+
+ .. code-block:: json
+
+ {
+ "name": "Twitter",
+ "apiVersion": "1.1",
+ "baseUrl": "https://api.twitter.com/1.1",
+ "operations": {
+ "GetMentions": {
+ "httpMethod": "GET",
+ "uri": "statuses/mentions_timeline.json",
+ "responseClass": "GetMentionsOutput",
+ "additionalParameters": {
+ "location": "query"
+ }
+ }
+ },
+ "models": {
+ "GetMentionsOutput": {
+ "type": "object",
+ "additionalProperties": {
+ "location": "json"
+ }
+ }
+ }
+ }
+
+responseClass
+~~~~~~~~~~~~~
+
+The ``responseClass`` attribute is used to define the return value of an operation (what is returned by calling the
+``getResult()`` method of a command object). The value set in the responseClass attribute can be one of "primitive"
+(meaning the result with be primitive type like a string), a class name meaning the result will be an instance of a
+specific user-land class, or a model name meaning the result will be a ``Guzzle\Service\Resource\Model`` object that
+uses a :ref:`model schema <model-schema>` to define how the HTTP response is parsed.
+
+.. note::
+
+ Using a class name with a ``responseClass`` will only work if it is supported by the ``class`` that is instantiated
+ for the operation. Keep this in mind when specifying a custom ``class`` attribute that points to a custom
+ ``Guzzle\Service\Command\CommandInterface`` class. The default ``class``,
+ ``Guzzle\Service\Command\OperationCommand``, does support setting custom ``class`` attributes.
+
+You can specify the name of a class to return a more customized result from the operation (for example, a domain model
+object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``.
+Here's a very simple example of implementing a custom responseClass object.
+
+.. code-block:: json
+
+ {
+ "operations": {
+ "test": {
+ "responseClass": "MyApplication\\User"
+ }
+ }
+ }
+
+.. code-block:: php
+
+ namespace MyApplication;
+
+ use Guzzle\Service\Command\ResponseClassInterface;
+ use Guzzle\Service\Command\OperationCommand;
+
+ class User implements ResponseClassInterface
+ {
+ protected $name;
+
+ public static function fromCommand(OperationCommand $command)
+ {
+ $response = $command->getResponse();
+ $xml = $response->xml();
+
+ return new self((string) $xml->name);
+ }
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+ }
+
+errorResponses
+~~~~~~~~~~~~~~
+
+``errorResponses`` is an array containing objects that define the errors that could occur while executing the
+operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error),
+'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this
+error is encountered).
+
+ErrorResponsePlugin
+^^^^^^^^^^^^^^^^^^^
+
+Error responses are by default only used for documentation. If you don't need very complex exception logic for your web
+service errors, then you can use the ``Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin`` to automatically throw defined
+exceptions when one of the ``errorResponse`` rules are matched. The error response plugin will listen for the
+``request.complete`` event of a request created by a command object. Every response (including a successful response) is
+checked against the list of error responses for an exact match using the following order of checks:
+
+1. Does the errorResponse have a defined ``class``?
+2. Is the errorResponse ``code`` equal to the status code of the response?
+3. Is the errorResponse ``reason`` equal to the reason phrase of the response?
+4. Throw the exception stored in the ``class`` attribute of the errorResponse.
+
+The ``class`` attribute must point to a class that implements
+``Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface``. This interface requires that an error response class
+implements ``public static function fromCommand(CommandInterface $command, Response $response)``. This method must
+return an object that extends from ``\Exception``. After an exception is returned, it is thrown by the plugin.
+
+.. _parameter-schema:
+
+Parameter schema
+----------------
+
+Parameters in both operations and models are represented using the
+`JSON schema <http://tools.ietf.org/id/draft-zyp-json-schema-04.html>`_ syntax.
+
+.. csv-table::
+ :header: "Property Name", "Value", "Description"
+ :widths: 20, 15, 65
+
+ "name", "string", "Unique name of the parameter"
+ "type", "string|array", "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid."
+ "instanceOf", "string", "When the type is an object, you can specify the class that the object must implement"
+ "required", "boolean", "Whether or not the parameter is required"
+ "default", "mixed", "Default value to use if no value is supplied"
+ "static", "boolean", "Set to true to specify that the parameter value cannot be changed from the default setting"
+ "description", "string", "Documentation of the parameter"
+ "location", "string", "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody"
+ "sentAs", "string", "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar."
+ "filters", "array", "Array of functions to to run a parameter value through."
+
+filters
+~~~~~~~
+
+Each value in the array must be a string containing the full class path to a static method or an array of complex
+filter information. You can specify static methods of classes using the full namespace class name followed by
+"::" (e.g. ``FooBar::baz()``). Some filters require arguments in order to properly filter a value. For complex filters,
+use an object containing a ``method`` attribute pointing to a function, and an ``args`` attribute containing an
+array of positional arguments to pass to the function. Arguments can contain keywords that are replaced when filtering
+a value: ``@value`` is replaced with the value being filtered, and ``@api`` is replaced with the actual Parameter
+object.
+
+.. code-block:: json
+
+ {
+ "filters": [
+ "strtolower",
+ {
+ "method": "MyClass::convertString",
+ "args": [ "test", "@value", "@api" ]
+ }
+ ]
+ }
+
+The above example will filter a parameter using ``strtolower``. It will then call the ``convertString`` static method
+of ``MyClass``, passing in "test", the actual value of the parameter, and a ``Guzzle\Service\Description\Parameter``
+object.
+
+Operation parameter location attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The location field of top-level parameters control how a parameter is serialized when generating a request.
+
+uri location
+^^^^^^^^^^^^
+
+Parameters are injected into the ``uri`` attribute of the operation using
+`URI-template expansion <http://tools.ietf.org/html/rfc6570>`_.
+
+.. code-block:: json
+
+ {
+ "operations": {
+ "uriTest": {
+ "uri": "/test/{testValue}",
+ "parameters": {
+ "testValue": {
+ "location": "uri"
+ }
+ }
+ }
+ }
+ }
+
+query location
+^^^^^^^^^^^^^^
+
+Parameters are injected into the query string of a request. Query values can be nested, which would result in a PHP
+style nested query string. The name of a parameter is the default name of the query string parameter added to the
+request. You can override this behavior by specifying the ``sentAs`` attribute on the parameter.
+
+.. code-block:: json
+
+ {
+ "operations": {
+ "queryTest": {
+ "parameters": {
+ "testValue": {
+ "location": "query",
+ "sentAs": "test_value"
+ }
+ }
+ }
+ }
+ }
+
+header location
+^^^^^^^^^^^^^^^
+
+Parameters are injected as headers on an HTTP request. The name of the parameter is used as the name of the header by
+default. You can change the name of the header created by the parameter using the ``sentAs`` attribute.
+
+Headers that are of type ``object`` will be added as multiple headers to a request using the key of the input array as
+the header key. Setting a ``sentAs`` attribute along with a type ``object`` will use the value of ``sentAs`` as a
+prefix for each header key.
+
+body location
+^^^^^^^^^^^^^
+
+Parameters are injected as the body of a request. The input of these parameters may be anything that can be cast to a
+string or a ``Guzzle\Http\EntityBodyInterface`` object.
+
+postField location
+^^^^^^^^^^^^^^^^^^
+
+Parameters are inserted as POST fields in a request. Nested values may be supplied and will be represented using
+PHP style nested query strings. The POST field name is the same as the parameter name by default. You can use the
+``sentAs`` parameter to override the POST field name.
+
+postFile location
+^^^^^^^^^^^^^^^^^
+
+Parameters are added as POST files. A postFile value may be a string pointing to a local filename or a
+``Guzzle\Http\Message\PostFileInterface`` object. The name of the POST file will be the name of the parameter by
+default. You can use a custom POST file name by using the ``sentAs`` attribute.
+
+Supports "string" and "array" types.
+
+json location
+^^^^^^^^^^^^^
+
+Parameters are added to the body of a request as top level keys of a JSON document. Nested values may be specified,
+with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When JSON parameters are specified, the
+``Content-Type`` of the request will change to ``application/json`` if a ``Content-Type`` has not already been specified
+on the request.
+
+xml location
+^^^^^^^^^^^^
+
+Parameters are added to the body of a request as top level nodes of an XML document. Nested values may be specified,
+with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When XML parameters are specified, the
+``Content-Type`` of the request will change to ``application/xml`` if a ``Content-Type`` has not already been specified
+on the request.
+
+responseBody location
+^^^^^^^^^^^^^^^^^^^^^
+
+Specifies the EntityBody of a response. This can be used to download the response body to a file or a custom Guzzle
+EntityBody object.
+
+No location
+^^^^^^^^^^^
+
+If a parameter has no location attribute, then the parameter is simply used as a data value.
+
+Other locations
+^^^^^^^^^^^^^^^
+
+Custom locations can be registered as new locations or override default locations if needed.
+
+.. _model-schema:
+
+Model Schema
+------------
+
+Models are used in service descriptions to provide generic JSON schema definitions that can be extended from or used in
+``$ref`` attributes. Models can also be referenced in a ``responseClass`` attribute to provide valuable output to an
+operation. Models are JSON schema documents and use the exact syntax and attributes used in parameters.
+
+Response Models
+~~~~~~~~~~~~~~~
+
+Response models describe how a response is parsed into a ``Guzzle\Service\Resource\Model`` object. Response models are
+always modeled as JSON schema objects. When an HTTP response is parsed using a response model, the rules specified on
+each property of a response model will translate 1:1 as keys in a PHP associative array. When a ``sentAs`` attribute is
+found in response model parameters, the value retrieved from the HTTP response is retrieved using the ``sentAs``
+parameter but stored in the response model using the name of the parameter.
+
+The location field of top-level parameters in a response model tell response parsers how data is retrieved from a
+response.
+
+statusCode location
+^^^^^^^^^^^^^^^^^^^
+
+Retrieves the status code of the response.
+
+reasonPhrase location
+^^^^^^^^^^^^^^^^^^^^^
+
+Retrieves the reason phrase of the response.
+
+header location
+^^^^^^^^^^^^^^^
+
+Retrieves a header from the HTTP response.
+
+body location
+^^^^^^^^^^^^^
+
+Retrieves the body of an HTTP response.
+
+json location
+^^^^^^^^^^^^^
+
+Retrieves a top-level parameter from a JSON document contained in an HTTP response.
+
+You can use ``additionalProperties`` if the JSON document is wrapped in an outer array. This allows you to parse the
+contents of each item in the array using the parsing rules defined in the ``additionalProperties`` schema.
+
+xml location
+^^^^^^^^^^^^
+
+Retrieves a top-level node value from an XML document contained in an HTTP response.
+
+Other locations
+^^^^^^^^^^^^^^^
+
+Custom locations can be registered as new locations or override default locations if needed.
+
+Example service description
+---------------------------
+
+Let's say you're interacting with a web service called 'Foo' that allows for the following routes and methods::
+
+ GET/POST /users
+ GET/DELETE /users/:id
+
+The following JSON service description implements this simple web service:
+
+.. class:: overflow-height-500px
+
+ .. code-block:: json
+
+ {
+ "name": "Foo",
+ "apiVersion": "2012-10-14",
+ "baseUrl": "http://api.foo.com",
+ "description": "Foo is an API that allows you to Baz Bar",
+ "operations": {
+ "GetUsers": {
+ "httpMethod": "GET",
+ "uri": "/users",
+ "summary": "Gets a list of users",
+ "responseClass": "GetUsersOutput"
+ },
+ "CreateUser": {
+ "httpMethod": "POST",
+ "uri": "/users",
+ "summary": "Creates a new user",
+ "responseClass": "CreateUserOutput",
+ "parameters": {
+ "name": {
+ "location": "json",
+ "type": "string"
+ },
+ "age": {
+ "location": "json",
+ "type": "integer"
+ }
+ }
+ },
+ "GetUser": {
+ "httpMethod": "GET",
+ "uri": "/users/{id}",
+ "summary": "Retrieves a single user",
+ "responseClass": "GetUserOutput",
+ "parameters": {
+ "id": {
+ "location": "uri",
+ "description": "User to retrieve by ID",
+ "required": true
+ }
+ }
+ },
+ "DeleteUser": {
+ "httpMethod": "DELETE",
+ "uri": "/users/{id}",
+ "summary": "Deletes a user",
+ "responseClass": "DeleteUserOutput",
+ "parameters": {
+ "id": {
+ "location": "uri",
+ "description": "User to delete by ID",
+ "required": true
+ }
+ }
+ }
+ },
+ "models": {
+ "GetUsersOutput": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "location": "json",
+ "type": "string"
+ },
+ "age": {
+ "location": "json",
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "CreateUserOutput": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "location": "json",
+ "type": "string"
+ },
+ "location": {
+ "location": "header",
+ "sentAs": "Location",
+ "type": "string"
+ }
+ }
+ },
+ "GetUserOutput": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "location": "json",
+ "type": "string"
+ },
+ "age": {
+ "location": "json",
+ "type": "integer"
+ }
+ }
+ },
+ "DeleteUserOutput": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "location": "statusCode",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ }
+
+If you attach this service description to a client, you would completely configure the client to interact with the
+Foo web service and provide valuable response models for each operation.
+
+.. code-block:: php
+
+ use Guzzle\Service\Description\ServiceDescription;
+
+ $description = ServiceDescription::factory('/path/to/client.json');
+ $client->setDescription($description);
+
+ $command = $client->getCommand('DeleteUser', array('id' => 123));
+ $responseModel = $client->execute($command);
+ echo $responseModel['status'];
+
+.. note::
+
+ You can add the service description to your client's factory method or constructor.
diff --git a/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst b/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst
new file mode 100644
index 0000000..b7113d6
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst
@@ -0,0 +1,316 @@
+=======================
+Using a service builder
+=======================
+
+The best way to instantiate Guzzle web service clients is to let Guzzle handle building the clients for you using a
+ServiceBuilder. A ServiceBuilder is responsible for creating concrete client objects based on configuration settings
+and helps to manage credentials for different environments.
+
+You don't have to use a service builder, but they help to decouple your application from concrete classes and help to
+share configuration data across multiple clients. Consider the following example. Here we are creating two clients that
+require the same API public key and secret key. The clients are created using their ``factory()`` methods.
+
+.. code-block:: php
+
+ use MyService\FooClient;
+ use MyService\BarClient;
+
+ $foo = FooClient::factory(array(
+ 'key' => 'abc',
+ 'secret' => '123',
+ 'custom' => 'and above all'
+ ));
+
+ $bar = BarClient::factory(array(
+ 'key' => 'abc',
+ 'secret' => '123',
+ 'custom' => 'listen to me'
+ ));
+
+The redundant specification of the API keys can be removed using a service builder.
+
+.. code-block:: php
+
+ use Guzzle\Service\Builder\ServiceBuilder;
+
+ $builder = ServiceBuilder::factory(array(
+ 'services' => array(
+ 'abstract_client' => array(
+ 'params' => array(
+ 'key' => 'abc',
+ 'secret' => '123'
+ )
+ ),
+ 'foo' => array(
+ 'extends' => 'abstract_client',
+ 'class' => 'MyService\FooClient',
+ 'params' => array(
+ 'custom' => 'and above all'
+ )
+ ),
+ 'bar' => array(
+ 'extends' => 'abstract_client',
+ 'class' => 'MyService\FooClient',
+ 'params' => array(
+ 'custom' => 'listen to me'
+ )
+ )
+ )
+ ));
+
+ $foo = $builder->get('foo');
+ $bar = $builder->get('bar');
+
+You can make managing your API keys even easier by saving the service builder configuration in a JSON format in a
+.json file.
+
+Creating a service builder
+--------------------------
+
+A ServiceBuilder can source information from an array, an PHP include file that returns an array, or a JSON file.
+
+.. code-block:: php
+
+ use Guzzle\Service\Builder\ServiceBuilder;
+
+ // Source service definitions from a JSON file
+ $builder = ServiceBuilder::factory('services.json');
+
+Sourcing data from an array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Data can be source from a PHP array. The array must contain an associative ``services`` array that maps the name of a
+client to the configuration information used by the service builder to create the client. Clients are given names
+which are used to identify how a client is retrieved from a service builder. This can be useful for using multiple
+accounts for the same service or creating development clients vs. production clients.
+
+.. code-block:: php
+
+ $services = array(
+ 'includes' => array(
+ '/path/to/other/services.json',
+ '/path/to/other/php_services.php'
+ ),
+ 'services' => array(
+ 'abstract.foo' => array(
+ 'params' => array(
+ 'username' => 'foo',
+ 'password' => 'bar'
+ )
+ ),
+ 'bar' => array(
+ 'extends' => 'abstract.foo',
+ 'class' => 'MyClientClass',
+ 'params' => array(
+ 'other' => 'abc'
+ )
+ )
+ )
+ );
+
+A service builder configuration array contains two top-level array keys:
+
++------------+---------------------------------------------------------------------------------------------------------+
+| Key | Description |
++============+=========================================================================================================+
+| includes | Array of paths to JSON or PHP include files to include in the configuration. |
++------------+---------------------------------------------------------------------------------------------------------+
+| services | Associative array of defined services that can be created by the service builder. Each service can |
+| | contain the following keys: |
+| | |
+| | +------------+----------------------------------------------------------------------------------------+ |
+| | | Key | Description | |
+| | +============+========================================================================================+ |
+| | | class | The concrete class to instantiate that implements the | |
+| | | | ``Guzzle\Common\FromConfigInterface``. | |
+| | +------------+----------------------------------------------------------------------------------------+ |
+| | | extends | The name of a previously defined service to extend from | |
+| | +------------+----------------------------------------------------------------------------------------+ |
+| | | params | Associative array of parameters to pass to the factory method of the service it is | |
+| | | | instantiated | |
+| | +------------+----------------------------------------------------------------------------------------+ |
+| | | alias | An alias that can be used in addition to the array key for retrieving a client from | |
+| | | | the service builder. | |
+| | +------------+----------------------------------------------------------------------------------------+ |
++------------+---------------------------------------------------------------------------------------------------------+
+
+The first client defined, ``abstract.foo``, is used as a placeholder of shared configuration values. Any service
+extending abstract.foo will inherit its params. As an example, this can be useful when clients share the same username
+and password.
+
+The next client, ``bar``, extends from ``abstract.foo`` using the ``extends`` attribute referencing the client from
+which to extend. Additional parameters can be merged into the original service definition when extending a parent
+service.
+
+.. important::
+
+ Each client that you intend to instantiate must specify a ``class`` attribute that references the full class name
+ of the client being created. The class referenced in the ``class`` parameter must implement a static ``factory()``
+ method that accepts an array or ``Guzzle\Common\Collection`` object and returns an instantiated object.
+
+Sourcing from a PHP include
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can create service builder configurations using a PHP include file. This can be useful if you wish to take
+advantage of an opcode cache like APC to speed up the process of loading and processing the configuration. The PHP
+include file is the same format as an array, but you simply create a PHP script that returns an array and save the
+file with the .php file extension.
+
+.. code-block:: php
+
+ <?php return array('services' => '...');
+ // Saved as config.php
+
+This configuration file can then be used with a service builder.
+
+.. code-block:: php
+
+ $builder = ServiceBuilder::factory('/path/to/config.php');
+
+Sourcing from a JSON document
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can use JSON documents to serialize your service descriptions. The JSON format uses the exact same structure as
+the PHP array syntax, but it's just serialized using JSON.
+
+.. code-block:: javascript
+
+ {
+ "includes": ["/path/to/other/services.json", "/path/to/other/php_services.php"],
+ "services": {
+ "abstract.foo": {
+ "params": {
+ "username": "foo",
+ "password": "bar"
+ }
+ },
+ "bar": {
+ "extends": "abstract.foo",
+ "class": "MyClientClass",
+ "params": {
+ "other": "abc"
+ }
+ }
+ }
+ }
+
+Referencing other clients in parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If one of your clients depends on another client as one of its parameters, you can reference that client by name by
+enclosing the client's reference key in ``{}``.
+
+.. code-block:: javascript
+
+ {
+ "services": {
+ "token": {
+ "class": "My\Token\TokenFactory",
+ "params": {
+ "access_key": "xyz"
+ }
+ },
+ "client": {
+ "class": "My\Client",
+ "params": {
+ "token_client": "{token}",
+ "version": "1.0"
+ }
+ }
+ }
+ }
+
+When ``client`` is constructed by the service builder, the service builder will first create the ``token`` service
+and then inject the token service into ``client``'s factory method in the ``token_client`` parameter.
+
+Retrieving clients from a service builder
+-----------------------------------------
+
+Clients are referenced using a customizable name you provide in your service definition. The ServiceBuilder is a sort
+of multiton object-- it will only instantiate a client once and return that client for subsequent retrievals. Clients
+are retrieved by name (the array key used in the configuration) or by the ``alias`` setting of a service.
+
+Here's an example of retrieving a client from your ServiceBuilder:
+
+.. code-block:: php
+
+ $client = $builder->get('foo');
+
+ // You can also use the ServiceBuilder object as an array
+ $client = $builder['foo'];
+
+Creating throwaway clients
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can get a "throwaway" client (a client that is not persisted by the ServiceBuilder) by passing ``true`` in the
+second argument of ``ServiceBuilder::get()``. This allows you to create a client that will not be returned by other
+parts of your code that use the service builder. Instead of passing ``true``, you can pass an array of configuration
+settings that will override the configuration settings specified in the service builder.
+
+.. code-block:: php
+
+ // Get a throwaway client and overwrite the "custom" setting of the client
+ $foo = $builder->get('foo', array(
+ 'custom' => 'in this world there are rules'
+ ));
+
+Getting raw configuration settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can get the raw configuration settings provided to the service builder for a specific service using the
+``getData($name)`` method of a service builder. This method will null if the service was not found in the service
+builder or an array of configuration settings if the service was found.
+
+.. code-block:: php
+
+ $data = $builder->getData('foo');
+ echo $data['key'] . "\n";
+ echo $data['secret'] . "\n";
+ echo $data['custom'] . "\n";
+
+Adding a plugin to all clients
+------------------------------
+
+You can add a plugin to all clients created by a service builder using the ``addGlobalPlugin($plugin)`` method of a
+service builder and passing a ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` object. The service builder
+will then attach each global plugin to every client as it is created. This allows you to, for example, add a LogPlugin
+to every request created by a service builder for easy debugging.
+
+.. code-block:: php
+
+ use Guzzle\Plugin\Log\LogPlugin;
+
+ // Add a debug log plugin to every client as it is created
+ $builder->addGlobalPlugin(LogPlugin::getDebugPlugin());
+
+ $foo = $builder->get('foo');
+ $foo->get('/')->send();
+ // Should output all of the data sent over the wire
+
+.. _service-builder-events:
+
+Events emitted from a service builder
+-------------------------------------
+
+A ``Guzzle\Service\Builder\ServiceBuilder`` object emits the following events:
+
++-------------------------------+--------------------------------------------+-----------------------------------------+
+| Event name | Description | Event data |
++===============================+============================================+=========================================+
+| service_builder.create_client | Called when a client is created | * client: The created client object |
++-------------------------------+--------------------------------------------+-----------------------------------------+
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+ use Guzzle\Service\Builder\ServiceBuilder;
+
+ $builder = ServiceBuilder::factory('/path/to/config.json');
+
+ // Add an event listener to print out each client client as it is created
+ $builder->getEventDispatcher()->addListener('service_builder.create_client', function (Event $e) {
+ echo 'Client created: ' . get_class($e['client']) . "\n";
+ });
+
+ $foo = $builder->get('foo');
+ // Should output the class used for the "foo" client
diff --git a/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst b/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst
new file mode 100644
index 0000000..7ec771e
--- /dev/null
+++ b/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst
@@ -0,0 +1,659 @@
+======================
+The web service client
+======================
+
+The ``Guzzle\Service`` namespace contains various abstractions that help to make it easier to interact with a web
+service API, including commands, service descriptions, and resource iterators.
+
+In this chapter, we'll build a simple `Twitter API client <https://dev.twitter.com/docs/api/1.1>`_.
+
+Creating a client
+=================
+
+A class that extends from ``Guzzle\Service\Client`` or implements ``Guzzle\Service\ClientInterface`` must implement a
+``factory()`` method in order to be used with a :doc:`service builder <using-the-service-builder>`.
+
+Factory method
+--------------
+
+You can use the ``factory()`` method of a client directly if you do not need a service builder.
+
+.. code-block:: php
+
+ use mtdowling\TwitterClient;
+
+ // Create a client and pass an array of configuration data
+ $twitter = TwitterClient::factory(array(
+ 'consumer_key' => '****',
+ 'consumer_secret' => '****',
+ 'token' => '****',
+ 'token_secret' => '****'
+ ));
+
+.. note::
+
+ If you'd like to follow along, here's how to get your Twitter API credentials:
+
+ 1. Visit https://dev.twitter.com/apps
+ 2. Click on an application that you've created
+ 3. Click on the "OAuth tool" tab
+ 4. Copy all of the settings under "OAuth Settings"
+
+Implementing a factory method
+-----------------------------
+
+Creating a client and its factory method is pretty simple. You just need to implement ``Guzzle\Service\ClientInterface``
+or extend from ``Guzzle\Service\Client``.
+
+.. code-block:: php
+
+ namespace mtdowling;
+
+ use Guzzle\Common\Collection;
+ use Guzzle\Plugin\Oauth\OauthPlugin;
+ use Guzzle\Service\Client;
+ use Guzzle\Service\Description\ServiceDescription;
+
+ /**
+ * A simple Twitter API client
+ */
+ class TwitterClient extends Client
+ {
+ public static function factory($config = array())
+ {
+ // Provide a hash of default client configuration options
+ $default = array('base_url' => 'https://api.twitter.com/1.1');
+
+ // The following values are required when creating the client
+ $required = array(
+ 'base_url',
+ 'consumer_key',
+ 'consumer_secret',
+ 'token',
+ 'token_secret'
+ );
+
+ // Merge in default settings and validate the config
+ $config = Collection::fromConfig($config, $default, $required);
+
+ // Create a new Twitter client
+ $client = new self($config->get('base_url'), $config);
+
+ // Ensure that the OauthPlugin is attached to the client
+ $client->addSubscriber(new OauthPlugin($config->toArray()));
+
+ return $client;
+ }
+ }
+
+Service Builder
+---------------
+
+A service builder is used to easily create web service clients, provides a simple configuration driven approach to
+creating clients, and allows you to share configuration settings across multiple clients. You can find out more about
+Guzzle's service builder in :doc:`using-the-service-builder`.
+
+.. code-block:: php
+
+ use Guzzle\Service\Builder\ServiceBuilder;
+
+ // Create a service builder and provide client configuration data
+ $builder = ServiceBuilder::factory('/path/to/client_config.json');
+
+ // Get the client from the service builder by name
+ $twitter = $builder->get('twitter');
+
+The above example assumes you have JSON data similar to the following stored in "/path/to/client_config.json":
+
+.. code-block:: json
+
+ {
+ "services": {
+ "twitter": {
+ "class": "mtdowling\\TwitterClient",
+ "params": {
+ "consumer_key": "****",
+ "consumer_secret": "****",
+ "token": "****",
+ "token_secret": "****"
+ }
+ }
+ }
+ }
+
+.. note::
+
+ A service builder becomes much more valuable when using multiple web service clients in a single application or
+ if you need to utilize the same client with varying configuration settings (e.g. multiple accounts).
+
+Commands
+========
+
+Commands are a concept in Guzzle that helps to hide the underlying implementation of an API by providing an easy to use
+parameter driven object for each action of an API. A command is responsible for accepting an array of configuration
+parameters, serializing an HTTP request, and parsing an HTTP response. Following the
+`command pattern <http://en.wikipedia.org/wiki/Command_pattern>`_, commands in Guzzle offer a greater level of
+flexibility when implementing and utilizing a web service client.
+
+Executing commands
+------------------
+
+You must explicitly execute a command after creating a command using the ``getCommand()`` method. A command has an
+``execute()`` method that may be called, or you can use the ``execute()`` method of a client object and pass in the
+command object. Calling either of these execute methods will return the result value of the command. The result value is
+the result of parsing the HTTP response with the ``process()`` method.
+
+.. code-block:: php
+
+ // Get a command from the client and pass an array of parameters
+ $command = $twitter->getCommand('getMentions', array(
+ 'count' => 5
+ ));
+
+ // Other parameters can be set on the command after it is created
+ $command['trim_user'] = false;
+
+ // Execute the command using the command object.
+ // The result value contains an array of JSON data from the response
+ $result = $command->execute();
+
+ // You can retrieve the result of the command later too
+ $result = $command->getResult().
+
+Command object also contains methods that allow you to inspect the HTTP request and response that was utilized with
+the command.
+
+.. code-block:: php
+
+ $request = $command->getRequest();
+ $response = $command->getResponse();
+
+.. note::
+
+ The format and notation used to retrieve commands from a client can be customized by injecting a custom command
+ factory, ``Guzzle\Service\Command\Factory\FactoryInterface``, on the client using ``$client->setCommandFactory()``.
+
+Executing with magic methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using method missing magic methods with a command, the command will be executed right away and the result of the
+command is returned.
+
+.. code-block:: php
+
+ $jsonData = $twitter->getMentions(array(
+ 'count' => 5,
+ 'trim_user' => true
+ ));
+
+Creating commands
+-----------------
+
+Commands are created using either the ``getCommand()`` method of a client or a magic missing method of a client. Using
+the ``getCommand()`` method allows you to create a command without executing it, allowing for customization of the
+command or the request serialized by the command.
+
+When a client attempts to create a command, it uses the client's ``Guzzle\Service\Command\Factory\FactoryInterface``.
+By default, Guzzle will utilize a command factory that first looks for a concrete class for a particular command
+(concrete commands) followed by a command defined by a service description (operation commands). We'll learn more about
+concrete commands and operation commands later in this chapter.
+
+.. code-block:: php
+
+ // Get a command from the twitter client.
+ $command = $twitter->getCommand('getMentions');
+ $result = $command->execute();
+
+Unless you've skipped ahead, running the above code will throw an exception.
+
+ PHP Fatal error: Uncaught exception 'Guzzle\Common\Exception\InvalidArgumentException' with message
+ 'Command was not found matching getMentions'
+
+This exception was thrown because the "getMentions" command has not yet been implemented. Let's implement one now.
+
+Concrete commands
+~~~~~~~~~~~~~~~~~
+
+Commands can be created in one of two ways: create a concrete command class that extends
+``Guzzle\Service\Command\AbstractCommand`` or
+:doc:`create an OperationCommand based on a service description <guzzle-service-descriptions>`. The recommended
+approach is to use a service description to define your web service, but you can use concrete commands when custom
+logic must be implemented for marshaling or unmarshaling a HTTP message.
+
+Commands are the method in which you abstract away the underlying format of the requests that need to be sent to take
+action on a web service. Commands in Guzzle are meant to be built by executing a series of setter methods on a command
+object. Commands are only validated right before they are executed. A ``Guzzle\Service\Client`` object is responsible
+for executing commands. Commands created for your web service must implement
+``Guzzle\Service\Command\CommandInterface``, but it's easier to extend the ``Guzzle\Service\Command\AbstractCommand``
+class, implement the ``build()`` method, and optionally implement the ``process()`` method.
+
+Serializing requests
+^^^^^^^^^^^^^^^^^^^^
+
+The ``build()`` method of a command is responsible for using the arguments of the command to build and serialize a
+HTTP request and set the request on the ``$request`` property of the command object. This step is usually taken care of
+for you when using a service description driven command that uses the default
+``Guzzle\Service\Command\OperationCommand``. You may wish to implement the process method yourself when you aren't
+using a service description or need to implement more complex request serialization.
+
+.. important::::
+
+ When implementing a custom ``build()`` method, be sure to set the class property of ``$this->request`` to an
+ instantiated and ready to send request.
+
+The following example shows how to implement the ``getMentions``
+`Twitter API <https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline>`_ method using a concrete command.
+
+.. code-block:: php
+
+ namespace mtdowling\Twitter\Command;
+
+ use Guzzle\Service\Command\AbstractCommand;
+
+ class GetMentions extends AbstractCommand
+ {
+ protected function build()
+ {
+ // Create the request property of the command
+ $this->request = $this->client->get('statuses/mentions_timeline.json');
+
+ // Grab the query object of the request because we will use it for
+ // serializing command parameters on the request
+ $query = $this->request->getQuery();
+
+ if ($this['count']) {
+ $query->set('count', $this['count']);
+ }
+
+ if ($this['since_id']) {
+ $query->set('since_id', $this['since_id']);
+ }
+
+ if ($this['max_id']) {
+ $query->set('max_id', $this['max_id']);
+ }
+
+ if ($this['trim_user'] !== null) {
+ $query->set('trim_user', $this['trim_user'] ? 'true' : 'false');
+ }
+
+ if ($this['contributor_details'] !== null) {
+ $query->set('contributor_details', $this['contributor_details'] ? 'true' : 'false');
+ }
+
+ if ($this['include_entities'] !== null) {
+ $query->set('include_entities', $this['include_entities'] ? 'true' : 'false');
+ }
+ }
+ }
+
+By default, a client will attempt to find concrete command classes under the ``Command`` namespace of a client. First
+the client will attempt to find an exact match for the name of the command to the name of the command class. If an
+exact match is not found, the client will calculate a class name using inflection. This is calculated based on the
+folder hierarchy of a command and converting the CamelCased named commands into snake_case. Here are some examples on
+how the command names are calculated:
+
+#. ``Foo\Command\JarJar`` **->** jar_jar
+#. ``Foo\Command\Test`` **->** test
+#. ``Foo\Command\People\GetCurrentPerson`` **->** people.get_current_person
+
+Notice how any sub-namespace beneath ``Command`` is converted from ``\`` to ``.`` (a period). CamelCasing is converted
+to lowercased snake_casing (e.g. JarJar == jar_jar).
+
+Parsing responses
+^^^^^^^^^^^^^^^^^
+
+The ``process()`` method of a command is responsible for converting an HTTP response into something more useful. For
+example, a service description operation that has specified a model object in the ``responseClass`` attribute of the
+operation will set a ``Guzzle\Service\Resource\Model`` object as the result of the command. This behavior can be
+completely modified as needed-- even if you are using operations and responseClass models. Simply implement a custom
+``process()`` method that sets the ``$this->result`` class property to whatever you choose. You can reuse parts of the
+default Guzzle response parsing functionality or get inspiration from existing code by using
+``Guzzle\Service\Command\OperationResponseParser`` and ``Guzzle\Service\Command\DefaultResponseParser`` classes.
+
+If you do not implement a custom ``process()`` method and are not using a service description, then Guzzle will attempt
+to guess how a response should be processed based on the Content-Type header of the response. Because the Twitter API
+sets a ``Content-Type: application/json`` header on this response, we do not need to implement any custom response
+parsing.
+
+Operation commands
+~~~~~~~~~~~~~~~~~~
+
+Operation commands are commands in which the serialization of an HTTP request and the parsing of an HTTP response are
+driven by a Guzzle service description. Because request serialization, validation, and response parsing are
+described using a DSL, creating operation commands is a much faster process than writing concrete commands.
+
+Creating operation commands for our Twitter client can remove a great deal of redundancy from the previous concrete
+command, and allows for a deeper runtime introspection of the API. Here's an example service description we can use to
+create the Twitter API client:
+
+.. code-block:: json
+
+ {
+ "name": "Twitter",
+ "apiVersion": "1.1",
+ "baseUrl": "https://api.twitter.com/1.1",
+ "description": "Twitter REST API client",
+ "operations": {
+ "GetMentions": {
+ "httpMethod": "GET",
+ "uri": "statuses/mentions_timeline.json",
+ "summary": "Returns the 20 most recent mentions for the authenticating user.",
+ "responseClass": "GetMentionsOutput",
+ "parameters": {
+ "count": {
+ "description": "Specifies the number of tweets to try and retrieve",
+ "type": "integer",
+ "location": "query"
+ },
+ "since_id": {
+ "description": "Returns results with an ID greater than the specified ID",
+ "type": "integer",
+ "location": "query"
+ },
+ "max_id": {
+ "description": "Returns results with an ID less than or equal to the specified ID.",
+ "type": "integer",
+ "location": "query"
+ },
+ "trim_user": {
+ "description": "Limits the amount of data returned for each user",
+ "type": "boolean",
+ "location": "query"
+ },
+ "contributor_details": {
+ "description": "Adds more data to contributor elements",
+ "type": "boolean",
+ "location": "query"
+ },
+ "include_entities": {
+ "description": "The entities node will be disincluded when set to false.",
+ "type": "boolean",
+ "location": "query"
+ }
+ }
+ }
+ },
+ "models": {
+ "GetMentionsOutput": {
+ "type": "object",
+ "additionalProperties": {
+ "location": "json"
+ }
+ }
+ }
+ }
+
+If you're lazy, you can define the API in a less descriptive manner using ``additionalParameters``.
+``additionalParameters`` define the serialization and validation rules of parameters that are not explicitly defined
+in a service description.
+
+.. code-block:: json
+
+ {
+ "name": "Twitter",
+ "apiVersion": "1.1",
+ "baseUrl": "https://api.twitter.com/1.1",
+ "description": "Twitter REST API client",
+ "operations": {
+ "GetMentions": {
+ "httpMethod": "GET",
+ "uri": "statuses/mentions_timeline.json",
+ "summary": "Returns the 20 most recent mentions for the authenticating user.",
+ "responseClass": "GetMentionsOutput",
+ "additionalParameters": {
+ "location": "query"
+ }
+ }
+ },
+ "models": {
+ "GetMentionsOutput": {
+ "type": "object",
+ "additionalProperties": {
+ "location": "json"
+ }
+ }
+ }
+ }
+
+You should attach the service description to the client at the end of the client's factory method:
+
+.. code-block:: php
+
+ // ...
+ class TwitterClient extends Client
+ {
+ public static function factory($config = array())
+ {
+ // ... same code as before ...
+
+ // Set the service description
+ $client->setDescription(ServiceDescription::factory('path/to/twitter.json'));
+
+ return $client;
+ }
+ }
+
+The client can now use operations defined in the service description instead of requiring you to create concrete
+command classes. Feel free to delete the concrete command class we created earlier.
+
+.. code-block:: php
+
+ $jsonData = $twitter->getMentions(array(
+ 'count' => 5,
+ 'trim_user' => true
+ ));
+
+Executing commands in parallel
+------------------------------
+
+Much like HTTP requests, Guzzle allows you to send multiple commands in parallel. You can send commands in parallel by
+passing an array of command objects to a client's ``execute()`` method. The client will serialize each request and
+send them all in parallel. If an error is encountered during the transfer, then a
+``Guzzle\Service\Exception\CommandTransferException`` is thrown, which allows you to retrieve a list of commands that
+succeeded and a list of commands that failed.
+
+.. code-block:: php
+
+ use Guzzle\Service\Exception\CommandTransferException;
+
+ $commands = array();
+ $commands[] = $twitter->getCommand('getMentions');
+ $commands[] = $twitter->getCommand('otherCommandName');
+ // etc...
+
+ try {
+ $result = $client->execute($commands);
+ foreach ($result as $command) {
+ echo $command->getName() . ': ' . $command->getResponse()->getStatusCode() . "\n";
+ }
+ } catch (CommandTransferException $e) {
+ // Get an array of the commands that succeeded
+ foreach ($e->getSuccessfulCommands() as $command) {
+ echo $command->getName() . " succeeded\n";
+ }
+ // Get an array of the commands that failed
+ foreach ($e->getFailedCommands() as $command) {
+ echo $command->getName() . " failed\n";
+ }
+ }
+
+.. note::
+
+ All commands executed from a client using an array must originate from the same client.
+
+Special command options
+-----------------------
+
+Guzzle exposes several options that help to control how commands are validated, serialized, and parsed.
+Command options can be specified when creating a command or in the ``command.params`` parameter in the
+``Guzzle\Service\Client``.
+
+=========================== ============================================================================================
+command.request_options Option used to add :ref:`Request options <request-options>` to the request created by a
+ command
+command.hidden_params An array of the names of parameters ignored by the ``additionalParameters`` parameter schema
+command.disable_validation Set to true to disable JSON schema validation of the command's input parameters
+command.response_processing Determines how the default response parser will parse the command. One of "raw" no parsing,
+ "model" (the default method used to parse commands using response models defined in service
+ descriptions)
+command.headers (deprecated) Option used to specify custom headers. Use ``command.request_options`` instead
+command.on_complete (deprecated) Option used to add an onComplete method to a command. Use
+ ``command.after_send`` event instead
+command.response_body (deprecated) Option used to change the entity body used to store a response.
+ Use ``command.request_options`` instead
+=========================== ============================================================================================
+
+Advanced client configuration
+=============================
+
+Default command parameters
+--------------------------
+
+When creating a client object, you can specify default command parameters to pass into all commands. Any key value pair
+present in the ``command.params`` settings of a client will be added as default parameters to any command created
+by the client.
+
+.. code-block:: php
+
+ $client = new Guzzle\Service\Client(array(
+ 'command.params' => array(
+ 'default_1' => 'foo',
+ 'another' => 'bar'
+ )
+ ));
+
+Magic methods
+-------------
+
+Client objects will, by default, attempt to create and execute commands when a missing method is invoked on a client.
+This powerful concept applies to both concrete commands and operation commands powered by a service description. This
+makes it appear to the end user that you have defined actual methods on a client object, when in fact, the methods are
+invoked using PHP's magic ``__call`` method.
+
+The ``__call`` method uses the ``getCommand()`` method of a client, which uses the client's internal
+``Guzzle\Service\Command\Factory\FactoryInterface`` object. The default command factory allows you to instantiate
+operations defined in a client's service description. The method in which a client determines which command to
+execute is defined as follows:
+
+1. The client will first try to find a literal match for an operation in the service description.
+2. If the literal match is not found, the client will try to uppercase the first character of the operation and find
+ the match again.
+3. If a match is still not found, the command factory will inflect the method name from CamelCase to snake_case and
+ attempt to find a matching command.
+4. If a command still does not match, an exception is thrown.
+
+.. code-block:: php
+
+ // Use the magic method
+ $result = $twitter->getMentions();
+
+ // This is exactly the same as:
+ $result = $twitter->getCommand('getMentions')->execute();
+
+You can disable magic methods on a client by passing ``false`` to the ``enableMagicMethod()`` method.
+
+Custom command factory
+----------------------
+
+A client by default uses the ``Guzzle\Service\Command\Factory\CompositeFactory`` which allows multiple command
+factories to attempt to create a command by a certain name. The default CompositeFactory uses a ``ConcreteClassFactory``
+and a ``ServiceDescriptionFactory`` if a service description is specified on a client. You can specify a custom
+command factory if your client requires custom command creation logic using the ``setCommandFactory()`` method of
+a client.
+
+Custom resource Iterator factory
+--------------------------------
+
+Resource iterators can be retrieved from a client using the ``getIterator($name)`` method of a client. This method uses
+a client's internal ``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object. A client by default uses a
+``Guzzle\Service\Resource\ResourceIteratorClassFactory`` to attempt to find concrete classes that implement resource
+iterators. The default factory will first look for matching iterators in the ``Iterator`` subdirectory of the client
+followed by the ``Model`` subdirectory of a client. Use the ``setResourceIteratorFactory()`` method of a client to
+specify a custom resource iterator factory.
+
+Plugins and events
+==================
+
+``Guzzle\Service\Client`` exposes various events that allow you to hook in custom logic. A client object owns a
+``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling
+``$client->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or
+event subscribers (classes that listen to specific events of a dispatcher).
+
+.. _service-client-events:
+
+Events emitted from a Service Client
+------------------------------------
+
+A ``Guzzle\Service\Client`` object emits the following events:
+
++------------------------------+--------------------------------------------+------------------------------------------+
+| Event name | Description | Event data |
++==============================+============================================+==========================================+
+| client.command.create | The client created a command object | * client: Client object |
+| | | * command: Command object |
++------------------------------+--------------------------------------------+------------------------------------------+
+| command.before_prepare | Before a command is validated and built. | * command: Command being prepared |
+| | This is also before a request is created. | |
++------------------------------+--------------------------------------------+------------------------------------------+
+| command.after_prepare | After a command instantiates and | * command: Command that was prepared |
+| | configures its request object. | |
++------------------------------+--------------------------------------------+------------------------------------------+
+| command.before_send | The client is about to execute a prepared | * command: Command to execute |
+| | command | |
++------------------------------+--------------------------------------------+------------------------------------------+
+| command.after_send | The client successfully completed | * command: The command that was executed |
+| | executing a command | |
++------------------------------+--------------------------------------------+------------------------------------------+
+| command.parse_response | Called when ``responseType`` is ``class`` | * command: The command with a response |
+| | and the response is about to be parsed. | about to be parsed. |
++------------------------------+--------------------------------------------+------------------------------------------+
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+ use Guzzle\Service\Client;
+
+ $client = new Client();
+
+ // create an event listener that operates on request objects
+ $client->getEventDispatcher()->addListener('command.after_prepare', function (Event $event) {
+ $command = $event['command'];
+ $request = $command->getRequest();
+
+ // do something with request
+ });
+
+.. code-block:: php
+
+ use Guzzle\Common\Event;
+ use Guzzle\Common\Client;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+ class EventSubscriber implements EventSubscriberInterface
+ {
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'client.command.create' => 'onCommandCreate',
+ 'command.parse_response' => 'onParseResponse'
+ );
+ }
+
+ public function onCommandCreate(Event $event)
+ {
+ $client = $event['client'];
+ $command = $event['command'];
+ // operate on client and command
+ }
+
+ public function onParseResponse(Event $event)
+ {
+ $command = $event['command'];
+ // operate on the command
+ }
+ }
+
+ $client = new Client();
+
+ $client->addSubscriber(new EventSubscriber());
diff --git a/vendor/guzzle/guzzle/phar-stub.php b/vendor/guzzle/guzzle/phar-stub.php
new file mode 100644
index 0000000..cc2b53f
--- /dev/null
+++ b/vendor/guzzle/guzzle/phar-stub.php
@@ -0,0 +1,16 @@
+<?php
+
+Phar::mapPhar('guzzle.phar');
+
+require_once 'phar://guzzle.phar/vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php';
+
+$classLoader = new Symfony\Component\ClassLoader\UniversalClassLoader();
+$classLoader->registerNamespaces(array(
+ 'Guzzle' => 'phar://guzzle.phar/src',
+ 'Symfony\\Component\\EventDispatcher' => 'phar://guzzle.phar/vendor/symfony/event-dispatcher',
+ 'Doctrine' => 'phar://guzzle.phar/vendor/doctrine/common/lib',
+ 'Monolog' => 'phar://guzzle.phar/vendor/monolog/monolog/src'
+));
+$classLoader->register();
+
+__HALT_COMPILER();
diff --git a/vendor/guzzle/guzzle/phing/build.properties.dist b/vendor/guzzle/guzzle/phing/build.properties.dist
new file mode 100644
index 0000000..c60d3d9
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/build.properties.dist
@@ -0,0 +1,16 @@
+# you may need to update this if you're working on a fork.
+guzzle.remote=git@github.com:guzzle/guzzle.git
+
+# github credentials -- only used by GitHub API calls to create subtree repos
+github.basicauth=username:password
+# for the subtree split and testing
+github.org=guzzle
+
+# your git path
+cmd.git=git
+
+# your composer command
+cmd.composer=composer
+
+# test server start
+cmd.testserver=node
diff --git a/vendor/guzzle/guzzle/phing/imports/dependencies.xml b/vendor/guzzle/guzzle/phing/imports/dependencies.xml
new file mode 100644
index 0000000..e40e037
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/imports/dependencies.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir="../../" default="install-dependencies">
+
+ <property name="cmd.composer" value="" />
+ <property name="cmd.git" value="" />
+ <property name="cmd.testserver" value="" />
+
+ <!--
+ Our custom tasks
+ -->
+ <taskdef name="composerlint" classname="phing.tasks.ComposerLintTask" />
+ <taskdef name="guzzlesubsplit" classname="phing.tasks.GuzzleSubSplitTask" />
+ <taskdef name="guzzlepear" classname="phing.tasks.GuzzlePearPharPackageTask" />
+
+ <target name="find-git">
+ <if>
+ <contains string="${cmd.git}" substring="git" />
+ <then>
+ <echo>using git at ${cmd.git}</echo>
+ </then>
+ <else>
+ <exec command="which git" outputProperty="cmd.git" />
+ <echo>found git at ${cmd.git}</echo>
+ </else>
+ </if>
+ </target>
+
+ <target name="clean-dependencies">
+ <delete dir="${project.basedir}/vendor"/>
+ <delete file="${project.basedir}/composer.lock" />
+ </target>
+
+</project>
diff --git a/vendor/guzzle/guzzle/phing/imports/deploy.xml b/vendor/guzzle/guzzle/phing/imports/deploy.xml
new file mode 100644
index 0000000..109e5ec
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/imports/deploy.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir="../../" default="deploy">
+
+ <property name="git.status" value=""/>
+ <property name="git.currentbranch" value=""/>
+ <target name="check-git-branch-status">
+ <exec command="git status -s -b" outputProperty="git.currentbranch" />
+ <echo msg="${git.currentbranch}"/>
+ <if>
+ <contains string="${git.currentbranch}" substring="${head}"/>
+ <then>
+ <echo>On branch ${head}</echo>
+ </then>
+ <else>
+ <fail message="-Dhead=${head} arg did not match ${git.currentbranch}"/>
+ </else>
+ </if>
+ <exec command="git status -s" outputProperty="git.status" />
+ <if>
+ <equals arg1="${git.status}" arg2="" trim="true"/>
+ <then>
+ <echo>working directory clean</echo>
+ </then>
+ <else>
+ <echo>${git.status}</echo>
+ <fail message="Working directory isn't clean." />
+ </else>
+ </if>
+ </target>
+
+ <property name="version.changelog" value=""/>
+ <property name="version.version" value=""/>
+ <target name="check-changelog-version">
+ <exec executable="fgrep" outputProperty="version.changelog">
+ <arg value="${new.version} ("/>
+ <arg value="${project.basedir}/CHANGELOG.md"/>
+ </exec>
+ <if>
+ <equals arg1="${version.changelog}" arg2="" trim="true"/>
+ <then>
+ <fail message="${new.version} not mentioned in CHANGELOG"/>
+ </then>
+ </if>
+
+ <exec executable="fgrep" outputProperty="version.version">
+ <arg value="const VERSION = '${new.version}'"/>
+ <arg value="${project.basedir}/src/Guzzle/Common/Version.php"/>
+ </exec>
+ <if>
+ <equals arg1="${version.version}" arg2="" trim="true"/>
+ <then>
+ <fail message="${new.version} not mentioned in Guzzle\Common\Version"/>
+ </then>
+ </if>
+
+ <echo>ChangeLog Match: ${version.changelog}</echo>
+ <echo>Guzzle\Common\Version Match: ${version.version}</echo>
+ </target>
+
+ <target name="help" description="HELP AND REMINDERS about what you can do with this project">
+ <echo>releasing: phing -Dnew.version=3.0.x -Dhead=master release</echo>
+ <echo>--</echo>
+ <exec command="phing -l" passthru="true"/>
+ </target>
+
+ <target name="release" depends="check-changelog-version,check-git-branch-status"
+ description="tag, subtree split, package, deploy: Use: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release">
+ <if>
+ <isset property="new.version" />
+ <then>
+ <if>
+ <contains string="${new.version}" substring="v" casesensitive="false" />
+ <then>
+ <fail message="Please specify version as [0-9].[0-9].[0-9]. (I'll add v for you.)"/>
+ </then>
+ <else>
+
+ <echo>BEGINNING RELEASE FOR ${new.version}</echo>
+
+ <!-- checkout the specified branch -->
+ <!-- <gitcheckout repository="${repo.dir}" branchname="${head}" gitPath="${cmd.git}" /> -->
+ <!-- Ensure that the tag exists -->
+ <!-- push the tag up so subsplit will get it -->
+ <!--gitpush repository="${repo.dir}" tags="true" gitPath="${cmd.git}" /-->
+
+ <!-- now do the subsplits -->
+ <guzzlesubsplit
+ repository="${repo.dir}"
+ remote="${guzzle.remote}"
+ heads="${head}"
+ tags="v${new.version}"
+ base="src"
+ subIndicatorFile="composer.json"
+ gitPath="${cmd.git}" />
+
+ <!-- Copy .md files into the PEAR package -->
+ <copy file="${repo.dir}/LICENSE" tofile=".subsplit/src/Guzzle/LICENSE.md" />
+ <copy file="${repo.dir}/README.md" tofile=".subsplit/src/Guzzle/README.md" />
+ <copy file="${repo.dir}/CHANGELOG.md" tofile=".subsplit/src/Guzzle/CHANGELOG.md" />
+
+ <!-- and now the pear packages -->
+ <guzzlepear
+ version="${new.version}"
+ makephar="true"
+ />
+ </else>
+
+ </if>
+ </then>
+
+ <else>
+ <echo>Tip: to create a new release, do: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release</echo>
+ </else>
+
+ </if>
+ </target>
+
+ <target name="pear-channel">
+ <guzzlepear version="${new.version}" deploy="true" makephar="true" />
+ </target>
+
+ <target name="package-phar" description="Create a phar with an autoloader">
+ <pharpackage
+ destfile="${dir.output}/guzzle.phar"
+ basedir="${project.basedir}/.subsplit"
+ stub="phar-stub.php"
+ signature="md5">
+ <fileset dir="${project.basedir}/.subsplit">
+ <include name="src/**/*.php" />
+ <include name="src/**/*.pem" />
+ <include name="vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php" />
+ <include name="vendor/symfony/event-dispatcher/**/*.php" />
+ <include name="vendor/doctrine/common/lib/Doctrine/Common/Cache/*.php" />
+ <include name="vendor/monolog/monolog/src/**/*.php" />
+ </fileset>
+ <metadata>
+ <element name="author" value="Michael Dowling" />
+ </metadata>
+ </pharpackage>
+ </target>
+
+</project>
diff --git a/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php b/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php
new file mode 100644
index 0000000..3b70409
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Phing task for composer validation.
+ *
+ * @copyright 2012 Clay Loveless <clay@php.net>
+ * @license http://claylo.mit-license.org/2012/ MIT License
+ */
+
+require_once 'phing/Task.php';
+
+class ComposerLintTask extends Task
+{
+ protected $dir = null;
+ protected $file = null;
+ protected $passthru = false;
+ protected $composer = null;
+
+ /**
+ * The setter for the dir
+ *
+ * @param string $str Directory to crawl recursively for composer files
+ */
+ public function setDir($str)
+ {
+ $this->dir = $str;
+ }
+
+ /**
+ * The setter for the file
+ *
+ * @param string $str Individual file to validate
+ */
+ public function setFile($str)
+ {
+ $this->file = $str;
+ }
+
+ /**
+ * Whether to use PHP's passthru() function instead of exec()
+ *
+ * @param boolean $passthru If passthru shall be used
+ */
+ public function setPassthru($passthru)
+ {
+ $this->passthru = (bool) $passthru;
+ }
+
+ /**
+ * Composer to execute. If unset, will attempt composer.phar in project
+ * basedir, and if that fails, will attempt global composer
+ * installation.
+ *
+ * @param string $str Individual file to validate
+ */
+ public function setComposer($str)
+ {
+ $this->file = $str;
+ }
+
+ /**
+ * The init method: do init steps
+ */
+ public function init()
+ {
+ // nothing needed here
+ }
+
+ /**
+ * The main entry point
+ */
+ public function main()
+ {
+ if ($this->composer === null) {
+ $this->findComposer();
+ }
+
+ $files = array();
+ if (!empty($this->file) && file_exists($this->file)) {
+ $files[] = $this->file;
+ }
+
+ if (!empty($this->dir)) {
+ $found = $this->findFiles();
+ foreach ($found as $file) {
+ $files[] = $this->dir . DIRECTORY_SEPARATOR . $file;
+ }
+ }
+
+ foreach ($files as $file) {
+
+ $cmd = $this->composer . ' validate ' . $file;
+ $cmd = escapeshellcmd($cmd);
+
+ if ($this->passthru) {
+ $retval = null;
+ passthru($cmd, $retval);
+ if ($retval == 1) {
+ throw new BuildException('invalid composer.json');
+ }
+ } else {
+ $out = array();
+ $retval = null;
+ exec($cmd, $out, $retval);
+ if ($retval == 1) {
+ $err = join("\n", $out);
+ throw new BuildException($err);
+ } else {
+ $this->log($out[0]);
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * Find the composer.json files using Phing's directory scanner
+ *
+ * @return array
+ */
+ protected function findFiles()
+ {
+ $ds = new DirectoryScanner();
+ $ds->setBasedir($this->dir);
+ $ds->setIncludes(array('**/composer.json'));
+ $ds->scan();
+ return $ds->getIncludedFiles();
+ }
+
+ /**
+ * Find composer installation
+ *
+ */
+ protected function findComposer()
+ {
+ $basedir = $this->project->getBasedir();
+ $php = $this->project->getProperty('php.interpreter');
+
+ if (file_exists($basedir . '/composer.phar')) {
+ $this->composer = "$php $basedir/composer.phar";
+ } else {
+ $out = array();
+ exec('which composer', $out);
+ if (empty($out)) {
+ throw new BuildException(
+ 'Could not determine composer location.'
+ );
+ }
+ $this->composer = $out[0];
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php b/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php
new file mode 100644
index 0000000..f72a6b5
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ * This file is part of Guzzle's build process.
+ *
+ * @copyright 2012 Clay Loveless <clay@php.net>
+ * @license http://claylo.mit-license.org/2012/ MIT License
+ */
+
+require_once 'phing/Task.php';
+require_once 'PEAR/PackageFileManager2.php';
+require_once 'PEAR/PackageFileManager/File.php';
+require_once 'PEAR/Packager.php';
+
+class GuzzlePearPharPackageTask extends Task
+{
+ private $version;
+ private $deploy = true;
+ private $makephar = true;
+
+ private $subpackages = array();
+
+ public function setVersion($str)
+ {
+ $this->version = $str;
+ }
+
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ public function setDeploy($deploy)
+ {
+ $this->deploy = (bool) $deploy;
+ }
+
+ public function getDeploy()
+ {
+ return $this->deploy;
+ }
+
+ public function setMakephar($makephar)
+ {
+ $this->makephar = (bool) $makephar;
+ }
+
+ public function getMakephar()
+ {
+ return $this->makephar;
+ }
+
+ private $basedir;
+ private $guzzleinfo;
+ private $changelog_release_date;
+ private $changelog_notes = '-';
+
+ public function main()
+ {
+ $this->basedir = $this->getProject()->getBasedir();
+
+ if (!is_dir((string) $this->basedir.'/.subsplit')) {
+ throw new BuildException('PEAR packaging requires .subsplit directory');
+ }
+
+ // main composer file
+ $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/composer.json');
+ $this->guzzleinfo = json_decode($composer_file, true);
+
+ // make sure we have a target
+ $pearwork = (string) $this->basedir . '/build/pearwork';
+ if (!is_dir($pearwork)) {
+ mkdir($pearwork, 0777, true);
+ }
+ $pearlogs = (string) $this->basedir . '/build/artifacts/logs';
+ if (!is_dir($pearlogs)) {
+ mkdir($pearlogs, 0777, true);
+ }
+
+ $version = $this->getVersion();
+ $this->grabChangelog();
+ if ($version[0] == '2') {
+ $this->log('building single PEAR package');
+ $this->buildSinglePackage();
+ } else {
+ // $this->log("building PEAR subpackages");
+ // $this->createSubPackages();
+ // $this->log("building PEAR bundle package");
+ $this->buildSinglePackage();
+ }
+
+ if ($this->getMakephar()) {
+ $this->log("building PHAR");
+ $this->getProject()->executeTarget('package-phar');
+ }
+
+ if ($this->getDeploy()) {
+ $this->doDeployment();
+ }
+ }
+
+ public function doDeployment()
+ {
+ $basedir = (string) $this->basedir;
+ $this->log('beginning PEAR/PHAR deployment');
+
+ chdir($basedir . '/build/pearwork');
+ if (!is_dir('./channel')) {
+ mkdir('./channel');
+ }
+
+ // Pull the PEAR channel down locally
+ passthru('aws s3 sync s3://pear.guzzlephp.org ./channel');
+
+ // add PEAR packages
+ foreach (scandir('./') as $file) {
+ if (substr($file, -4) == '.tgz') {
+ passthru('pirum add ./channel ' . $file);
+ }
+ }
+
+ // if we have a new phar, add it
+ if ($this->getMakephar() && file_exists($basedir . '/build/artifacts/guzzle.phar')) {
+ rename($basedir . '/build/artifacts/guzzle.phar', './channel/guzzle.phar');
+ }
+
+ // Sync up with the S3 bucket
+ chdir($basedir . '/build/pearwork/channel');
+ passthru('aws s3 sync . s3://pear.guzzlephp.org');
+ }
+
+ public function buildSinglePackage()
+ {
+ $v = $this->getVersion();
+ $apiversion = $v[0] . '.0.0';
+
+ $opts = array(
+ 'packagedirectory' => (string) $this->basedir . '/.subsplit/src/',
+ 'filelistgenerator' => 'file',
+ 'ignore' => array('*composer.json'),
+ 'baseinstalldir' => '/',
+ 'packagefile' => 'package.xml'
+ //'outputdirectory' => (string) $this->basedir . '/build/pearwork/'
+ );
+ $pfm = new PEAR_PackageFileManager2();
+ $pfm->setOptions($opts);
+ $pfm->addRole('md', 'doc');
+ $pfm->addRole('pem', 'php');
+ $pfm->setPackage('Guzzle');
+ $pfm->setSummary("Object-oriented PHP HTTP Client for PHP 5.3+");
+ $pfm->setDescription($this->guzzleinfo['description']);
+ $pfm->setPackageType('php');
+ $pfm->setChannel('guzzlephp.org/pear');
+ $pfm->setAPIVersion($apiversion);
+ $pfm->setReleaseVersion($this->getVersion());
+ $pfm->setAPIStability('stable');
+ $pfm->setReleaseStability('stable');
+ $pfm->setNotes($this->changelog_notes);
+ $pfm->setPackageType('php');
+ $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE');
+ $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes');
+ $pfm->setDate($this->changelog_release_date);
+ $pfm->generateContents();
+
+ $phpdep = $this->guzzleinfo['require']['php'];
+ $phpdep = str_replace('>=', '', $phpdep);
+ $pfm->setPhpDep($phpdep);
+ $pfm->addExtensionDep('required', 'curl');
+ $pfm->setPearinstallerDep('1.4.6');
+ $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0');
+ if (!empty($this->subpackages)) {
+ foreach ($this->subpackages as $package) {
+ $pkg = dirname($package);
+ $pkg = str_replace('/', '_', $pkg);
+ $pfm->addConflictingPackageDepWithChannel($pkg, 'guzzlephp.org/pear', false, $apiversion);
+ }
+ }
+
+ ob_start();
+ $startdir = getcwd();
+ chdir((string) $this->basedir . '/build/pearwork');
+
+ echo "DEBUGGING GENERATED PACKAGE FILE\n";
+ $result = $pfm->debugPackageFile();
+ if ($result) {
+ $out = $pfm->writePackageFile();
+ echo "\n\n\nWRITE PACKAGE FILE RESULT:\n";
+ var_dump($out);
+ // load up package file and build package
+ $packager = new PEAR_Packager();
+ echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n";
+ $dest_package = $packager->package($opts['packagedirectory'].'package.xml');
+ var_dump($dest_package);
+ } else {
+ echo "\n\n\nDEBUGGING RESULT:\n";
+ var_dump($result);
+ }
+ echo "removing package.xml";
+ unlink($opts['packagedirectory'].'package.xml');
+ $log = ob_get_clean();
+ file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package.log', $log);
+ chdir($startdir);
+ }
+
+ public function createSubPackages()
+ {
+ $this->findComponents();
+
+ foreach ($this->subpackages as $package) {
+ $baseinstalldir = dirname($package);
+ $dir = (string) $this->basedir.'/.subsplit/src/' . $baseinstalldir;
+ $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/src/'. $package);
+ $package_info = json_decode($composer_file, true);
+ $this->log('building ' . $package_info['target-dir'] . ' subpackage');
+ $this->buildSubPackage($dir, $baseinstalldir, $package_info);
+ }
+ }
+
+ public function buildSubPackage($dir, $baseinstalldir, $info)
+ {
+ $package = str_replace('/', '_', $baseinstalldir);
+ $opts = array(
+ 'packagedirectory' => $dir,
+ 'filelistgenerator' => 'file',
+ 'ignore' => array('*composer.json', '*package.xml'),
+ 'baseinstalldir' => '/' . $info['target-dir'],
+ 'packagefile' => 'package.xml'
+ );
+ $pfm = new PEAR_PackageFileManager2();
+ $pfm->setOptions($opts);
+ $pfm->setPackage($package);
+ $pfm->setSummary($info['description']);
+ $pfm->setDescription($info['description']);
+ $pfm->setPackageType('php');
+ $pfm->setChannel('guzzlephp.org/pear');
+ $pfm->setAPIVersion('3.0.0');
+ $pfm->setReleaseVersion($this->getVersion());
+ $pfm->setAPIStability('stable');
+ $pfm->setReleaseStability('stable');
+ $pfm->setNotes($this->changelog_notes);
+ $pfm->setPackageType('php');
+ $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE');
+ $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes');
+ $pfm->setDate($this->changelog_release_date);
+ $pfm->generateContents();
+
+ $phpdep = $this->guzzleinfo['require']['php'];
+ $phpdep = str_replace('>=', '', $phpdep);
+ $pfm->setPhpDep($phpdep);
+ $pfm->setPearinstallerDep('1.4.6');
+
+ foreach ($info['require'] as $type => $version) {
+ if ($type == 'php') {
+ continue;
+ }
+ if ($type == 'symfony/event-dispatcher') {
+ $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0');
+ }
+ if ($type == 'ext-curl') {
+ $pfm->addExtensionDep('required', 'curl');
+ }
+ if (substr($type, 0, 6) == 'guzzle') {
+ $gdep = str_replace('/', ' ', $type);
+ $gdep = ucwords($gdep);
+ $gdep = str_replace(' ', '_', $gdep);
+ $pfm->addPackageDepWithChannel('required', $gdep, 'guzzlephp.org/pear', $this->getVersion());
+ }
+ }
+
+ // can't have main Guzzle package AND sub-packages
+ $pfm->addConflictingPackageDepWithChannel('Guzzle', 'guzzlephp.org/pear', false, $apiversion);
+
+ ob_start();
+ $startdir = getcwd();
+ chdir((string) $this->basedir . '/build/pearwork');
+
+ echo "DEBUGGING GENERATED PACKAGE FILE\n";
+ $result = $pfm->debugPackageFile();
+ if ($result) {
+ $out = $pfm->writePackageFile();
+ echo "\n\n\nWRITE PACKAGE FILE RESULT:\n";
+ var_dump($out);
+ // load up package file and build package
+ $packager = new PEAR_Packager();
+ echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n";
+ $dest_package = $packager->package($opts['packagedirectory'].'/package.xml');
+ var_dump($dest_package);
+ } else {
+ echo "\n\n\nDEBUGGING RESULT:\n";
+ var_dump($result);
+ }
+ echo "removing package.xml";
+ unlink($opts['packagedirectory'].'/package.xml');
+ $log = ob_get_clean();
+ file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package_'.$package.'.log', $log);
+ chdir($startdir);
+ }
+
+ public function findComponents()
+ {
+ $ds = new DirectoryScanner();
+ $ds->setBasedir((string) $this->basedir.'/.subsplit/src');
+ $ds->setIncludes(array('**/composer.json'));
+ $ds->scan();
+ $files = $ds->getIncludedFiles();
+ $this->subpackages = $files;
+ }
+
+ public function grabChangelog()
+ {
+ $cl = file((string) $this->basedir.'/.subsplit/CHANGELOG.md');
+ $notes = '';
+ $in_version = false;
+ $release_date = null;
+
+ foreach ($cl as $line) {
+ $line = trim($line);
+ if (preg_match('/^\* '.$this->getVersion().' \(([0-9\-]+)\)$/', $line, $matches)) {
+ $release_date = $matches[1];
+ $in_version = true;
+ continue;
+ }
+ if ($in_version && empty($line) && empty($notes)) {
+ continue;
+ }
+ if ($in_version && ! empty($line)) {
+ $notes .= $line."\n";
+ }
+ if ($in_version && empty($line) && !empty($notes)) {
+ $in_version = false;
+ }
+ }
+ $this->changelog_release_date = $release_date;
+
+ if (! empty($notes)) {
+ $this->changelog_notes = $notes;
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php b/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php
new file mode 100644
index 0000000..5d56a5b
--- /dev/null
+++ b/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php
@@ -0,0 +1,385 @@
+<?php
+/**
+ * Phing wrapper around git subsplit.
+ *
+ * @see https://github.com/dflydev/git-subsplit
+ * @copyright 2012 Clay Loveless <clay@php.net>
+ * @license http://claylo.mit-license.org/2012/ MIT License
+ */
+
+require_once 'phing/tasks/ext/git/GitBaseTask.php';
+
+// base - base of tree to split out
+// subIndicatorFile - composer.json, package.xml?
+class GuzzleSubSplitTask extends GitBaseTask
+{
+ /**
+ * What git repository to pull from and publish to
+ */
+ protected $remote = null;
+
+ /**
+ * Publish for comma-separated heads instead of all heads
+ */
+ protected $heads = null;
+
+ /**
+ * Publish for comma-separated tags instead of all tags
+ */
+ protected $tags = null;
+
+ /**
+ * Base of the tree RELATIVE TO .subsplit working dir
+ */
+ protected $base = null;
+
+ /**
+ * The presence of this file will indicate that the directory it resides
+ * in is at the top level of a split.
+ */
+ protected $subIndicatorFile = 'composer.json';
+
+ /**
+ * Do everything except actually send the update.
+ */
+ protected $dryRun = null;
+
+ /**
+ * Do not sync any heads.
+ */
+ protected $noHeads = false;
+
+ /**
+ * Do not sync any tags.
+ */
+ protected $noTags = false;
+
+ /**
+ * The splits we found in the heads
+ */
+ protected $splits;
+
+ public function setRemote($str)
+ {
+ $this->remote = $str;
+ }
+
+ public function getRemote()
+ {
+ return $this->remote;
+ }
+
+ public function setHeads($str)
+ {
+ $this->heads = explode(',', $str);
+ }
+
+ public function getHeads()
+ {
+ return $this->heads;
+ }
+
+ public function setTags($str)
+ {
+ $this->tags = explode(',', $str);
+ }
+
+ public function getTags()
+ {
+ return $this->tags;
+ }
+
+ public function setBase($str)
+ {
+ $this->base = $str;
+ }
+
+ public function getBase()
+ {
+ return $this->base;
+ }
+
+ public function setSubIndicatorFile($str)
+ {
+ $this->subIndicatorFile = $str;
+ }
+
+ public function getSubIndicatorFile()
+ {
+ return $this->subIndicatorFile;
+ }
+
+ public function setDryRun($bool)
+ {
+ $this->dryRun = (bool) $bool;
+ }
+
+ public function getDryRun()
+ {
+ return $this->dryRun;
+ }
+
+ public function setNoHeads($bool)
+ {
+ $this->noHeads = (bool) $bool;
+ }
+
+ public function getNoHeads()
+ {
+ return $this->noHeads;
+ }
+
+ public function setNoTags($bool)
+ {
+ $this->noTags = (bool) $bool;
+ }
+
+ public function getNoTags()
+ {
+ return $this->noTags;
+ }
+
+ /**
+ * GitClient from VersionControl_Git
+ */
+ protected $client = null;
+
+ /**
+ * The main entry point
+ */
+ public function main()
+ {
+ $repo = $this->getRepository();
+ if (empty($repo)) {
+ throw new BuildException('"repository" is a required parameter');
+ }
+
+ $remote = $this->getRemote();
+ if (empty($remote)) {
+ throw new BuildException('"remote" is a required parameter');
+ }
+
+ chdir($repo);
+ $this->client = $this->getGitClient(false, $repo);
+
+ // initalized yet?
+ if (!is_dir('.subsplit')) {
+ $this->subsplitInit();
+ } else {
+ // update
+ $this->subsplitUpdate();
+ }
+
+ // find all splits based on heads requested
+ $this->findSplits();
+
+ // check that GitHub has the repos
+ $this->verifyRepos();
+
+ // execute the subsplits
+ $this->publish();
+ }
+
+ public function publish()
+ {
+ $this->log('DRY RUN ONLY FOR NOW');
+ $base = $this->getBase();
+ $base = rtrim($base, '/') . '/';
+ $org = $this->getOwningTarget()->getProject()->getProperty('github.org');
+
+ $splits = array();
+
+ $heads = $this->getHeads();
+ foreach ($heads as $head) {
+ foreach ($this->splits[$head] as $component => $meta) {
+ $splits[] = $base . $component . ':git@github.com:'. $org.'/'.$meta['repo'];
+ }
+
+ $cmd = 'git subsplit publish ';
+ $cmd .= escapeshellarg(implode(' ', $splits));
+
+ if ($this->getNoHeads()) {
+ $cmd .= ' --no-heads';
+ } else {
+ $cmd .= ' --heads='.$head;
+ }
+
+ if ($this->getNoTags()) {
+ $cmd .= ' --no-tags';
+ } else {
+ if ($this->getTags()) {
+ $cmd .= ' --tags=' . escapeshellarg(implode(' ', $this->getTags()));
+ }
+ }
+
+ passthru($cmd);
+ }
+ }
+
+ /**
+ * Runs `git subsplit update`
+ */
+ public function subsplitUpdate()
+ {
+ $repo = $this->getRepository();
+ $this->log('git-subsplit update...');
+ $cmd = $this->client->getCommand('subsplit');
+ $cmd->addArgument('update');
+ try {
+ $cmd->execute();
+ } catch (Exception $e) {
+ throw new BuildException('git subsplit update failed'. $e);
+ }
+ chdir($repo . '/.subsplit');
+ passthru('php ../composer.phar update --dev');
+ chdir($repo);
+ }
+
+ /**
+ * Runs `git subsplit init` based on the remote repository.
+ */
+ public function subsplitInit()
+ {
+ $remote = $this->getRemote();
+ $cmd = $this->client->getCommand('subsplit');
+ $this->log('running git-subsplit init ' . $remote);
+
+ $cmd->setArguments(array(
+ 'init',
+ $remote
+ ));
+
+ try {
+ $output = $cmd->execute();
+ } catch (Exception $e) {
+ throw new BuildException('git subsplit init failed'. $e);
+ }
+ $this->log(trim($output), Project::MSG_INFO);
+ $repo = $this->getRepository();
+ chdir($repo . '/.subsplit');
+ passthru('php ../composer.phar install --dev');
+ chdir($repo);
+ }
+
+ /**
+ * Find the composer.json files using Phing's directory scanner
+ *
+ * @return array
+ */
+ protected function findSplits()
+ {
+ $this->log("checking heads for subsplits");
+ $repo = $this->getRepository();
+ $base = $this->getBase();
+
+ $splits = array();
+ $heads = $this->getHeads();
+
+ if (!empty($base)) {
+ $base = '/' . ltrim($base, '/');
+ } else {
+ $base = '/';
+ }
+
+ chdir($repo . '/.subsplit');
+ foreach ($heads as $head) {
+ $splits[$head] = array();
+
+ // check each head requested *BEFORE* the actual subtree split command gets it
+ passthru("git checkout '$head'");
+ $ds = new DirectoryScanner();
+ $ds->setBasedir($repo . '/.subsplit' . $base);
+ $ds->setIncludes(array('**/'.$this->subIndicatorFile));
+ $ds->scan();
+ $files = $ds->getIncludedFiles();
+
+ // Process the files we found
+ foreach ($files as $file) {
+ $pkg = file_get_contents($repo . '/.subsplit' . $base .'/'. $file);
+ $pkg_json = json_decode($pkg, true);
+ $name = $pkg_json['name'];
+ $component = str_replace('/composer.json', '', $file);
+ // keep this for split cmd
+ $tmpreponame = explode('/', $name);
+ $reponame = $tmpreponame[1];
+ $splits[$head][$component]['repo'] = $reponame;
+ $nscomponent = str_replace('/', '\\', $component);
+ $splits[$head][$component]['desc'] = "[READ ONLY] Subtree split of $nscomponent: " . $pkg_json['description'];
+ }
+ }
+
+ // go back to how we found it
+ passthru("git checkout master");
+ chdir($repo);
+ $this->splits = $splits;
+ }
+
+ /**
+ * Based on list of repositories we determined we *should* have, talk
+ * to GitHub and make sure they're all there.
+ *
+ */
+ protected function verifyRepos()
+ {
+ $this->log('verifying GitHub target repos');
+ $github_org = $this->getOwningTarget()->getProject()->getProperty('github.org');
+ $github_creds = $this->getOwningTarget()->getProject()->getProperty('github.basicauth');
+
+ if ($github_creds == 'username:password') {
+ $this->log('Skipping GitHub repo checks. Update github.basicauth in build.properties to verify repos.', 1);
+ return;
+ }
+
+ $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos?type=all');
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_USERPWD, $github_creds);
+ // change this when we know we can use our bundled CA bundle!
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $repos = json_decode($result, true);
+ $existing_repos = array();
+
+ // parse out the repos we found on GitHub
+ foreach ($repos as $repo) {
+ $tmpreponame = explode('/', $repo['full_name']);
+ $reponame = $tmpreponame[1];
+ $existing_repos[$reponame] = $repo['description'];
+ }
+
+ $heads = $this->getHeads();
+ foreach ($heads as $head) {
+ foreach ($this->splits[$head] as $component => $meta) {
+
+ $reponame = $meta['repo'];
+
+ if (!isset($existing_repos[$reponame])) {
+ $this->log("Creating missing repo $reponame");
+ $payload = array(
+ 'name' => $reponame,
+ 'description' => $meta['desc'],
+ 'homepage' => 'http://www.guzzlephp.org/',
+ 'private' => true,
+ 'has_issues' => false,
+ 'has_wiki' => false,
+ 'has_downloads' => true,
+ 'auto_init' => false
+ );
+ $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos');
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_USERPWD, $github_creds);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
+ // change this when we know we can use our bundled CA bundle!
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ $result = curl_exec($ch);
+ echo "Response code: ".curl_getinfo($ch, CURLINFO_HTTP_CODE)."\n";
+ curl_close($ch);
+ } else {
+ $this->log("Repo $reponame exists", 2);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/phpunit.xml.dist b/vendor/guzzle/guzzle/phpunit.xml.dist
new file mode 100644
index 0000000..208fdc0
--- /dev/null
+++ b/vendor/guzzle/guzzle/phpunit.xml.dist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="./tests/bootstrap.php"
+ colors="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader">
+
+ <testsuites>
+ <testsuite>
+ <directory>./tests/Guzzle/Tests</directory>
+ </testsuite>
+ </testsuites>
+
+ <logging>
+ <log type="junit" target="build/artifacts/logs/junit.xml" logIncompleteSkipped="false" />
+ </logging>
+
+ <filter>
+ <whitelist>
+ <directory suffix=".php">./src/Guzzle</directory>
+ <exclude>
+ <directory suffix="Interface.php">./src/Guzzle</directory>
+ <file>./src/Guzzle/Common/Exception/GuzzleException.php</file>
+ <file>./src/Guzzle/Http/Exception/HttpException.php</file>
+ <file>./src/Guzzle/Http/Exception/ServerErrorResponseException.php</file>
+ <file>./src/Guzzle/Http/Exception/ClientErrorResponseException.php</file>
+ <file>./src/Guzzle/Http/Exception/TooManyRedirectsException.php</file>
+ <file>./src/Guzzle/Http/Exception/CouldNotRewindStreamException.php</file>
+ <file>./src/Guzzle/Common/Exception/BadMethodCallException.php</file>
+ <file>./src/Guzzle/Common/Exception/InvalidArgumentException.php</file>
+ <file>./src/Guzzle/Common/Exception/RuntimeException.php</file>
+ <file>./src/Guzzle/Common/Exception/UnexpectedValueException.php</file>
+ <file>./src/Guzzle/Service/Exception/ClientNotFoundException.php</file>
+ <file>./src/Guzzle/Service/Exception/CommandException.php</file>
+ <file>./src/Guzzle/Service/Exception/DescriptionBuilderException.php</file>
+ <file>./src/Guzzle/Service/Exception/ServiceBuilderException.php</file>
+ <file>./src/Guzzle/Service/Exception/ServiceNotFoundException.php</file>
+ <file>./src/Guzzle/Service/Exception/ValidationException.php</file>
+ <file>./src/Guzzle/Service/Exception/JsonException.php</file>
+ </exclude>
+ </whitelist>
+ </filter>
+
+</phpunit>
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php
new file mode 100644
index 0000000..0625d71
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * Abstract decorator used when decorating a BatchInterface
+ */
+abstract class AbstractBatchDecorator implements BatchInterface
+{
+ /** @var BatchInterface Decorated batch object */
+ protected $decoratedBatch;
+
+ /**
+ * @param BatchInterface $decoratedBatch BatchInterface that is being decorated
+ */
+ public function __construct(BatchInterface $decoratedBatch)
+ {
+ $this->decoratedBatch = $decoratedBatch;
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ * @codeCoverageIgnore
+ */
+ public function __call($method, array $args)
+ {
+ return call_user_func_array(array($this->decoratedBatch, $method), $args);
+ }
+
+ public function add($item)
+ {
+ $this->decoratedBatch->add($item);
+
+ return $this;
+ }
+
+ public function flush()
+ {
+ return $this->decoratedBatch->flush();
+ }
+
+ public function isEmpty()
+ {
+ return $this->decoratedBatch->isEmpty();
+ }
+
+ /**
+ * Trace the decorators associated with the batch
+ *
+ * @return array
+ */
+ public function getDecorators()
+ {
+ $found = array($this);
+ if (method_exists($this->decoratedBatch, 'getDecorators')) {
+ $found = array_merge($found, $this->decoratedBatch->getDecorators());
+ }
+
+ return $found;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php
new file mode 100644
index 0000000..4d41c54
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Batch\Exception\BatchTransferException;
+
+/**
+ * Default batch implementation used to convert queued items into smaller chunks of batches using a
+ * {@see BatchDivisorIterface} and transfers each batch using a {@see BatchTransferInterface}.
+ *
+ * Any exception encountered during a flush operation will throw a {@see BatchTransferException} object containing the
+ * batch that failed. After an exception is encountered, you can flush the batch again to attempt to finish transferring
+ * any previously created batches or queued items.
+ */
+class Batch implements BatchInterface
+{
+ /** @var \SplQueue Queue of items in the queue */
+ protected $queue;
+
+ /** @var array Divided batches to be transferred */
+ protected $dividedBatches;
+
+ /** @var BatchTransferInterface */
+ protected $transferStrategy;
+
+ /** @var BatchDivisorInterface */
+ protected $divisionStrategy;
+
+ /**
+ * @param BatchTransferInterface $transferStrategy Strategy used to transfer items
+ * @param BatchDivisorInterface $divisionStrategy Divisor used to create batches
+ */
+ public function __construct(BatchTransferInterface $transferStrategy, BatchDivisorInterface $divisionStrategy)
+ {
+ $this->transferStrategy = $transferStrategy;
+ $this->divisionStrategy = $divisionStrategy;
+ $this->queue = new \SplQueue();
+ $this->queue->setIteratorMode(\SplQueue::IT_MODE_DELETE);
+ $this->dividedBatches = array();
+ }
+
+ public function add($item)
+ {
+ $this->queue->enqueue($item);
+
+ return $this;
+ }
+
+ public function flush()
+ {
+ $this->createBatches();
+
+ $items = array();
+ foreach ($this->dividedBatches as $batchIndex => $dividedBatch) {
+ while ($dividedBatch->valid()) {
+ $batch = $dividedBatch->current();
+ $dividedBatch->next();
+ try {
+ $this->transferStrategy->transfer($batch);
+ $items = array_merge($items, $batch);
+ } catch (\Exception $e) {
+ throw new BatchTransferException($batch, $items, $e, $this->transferStrategy, $this->divisionStrategy);
+ }
+ }
+ // Keep the divided batch down to a minimum in case of a later exception
+ unset($this->dividedBatches[$batchIndex]);
+ }
+
+ return $items;
+ }
+
+ public function isEmpty()
+ {
+ return count($this->queue) == 0 && count($this->dividedBatches) == 0;
+ }
+
+ /**
+ * Create batches for any queued items
+ */
+ protected function createBatches()
+ {
+ if (count($this->queue)) {
+ if ($batches = $this->divisionStrategy->createBatches($this->queue)) {
+ // Convert arrays into iterators
+ if (is_array($batches)) {
+ $batches = new \ArrayIterator($batches);
+ }
+ $this->dividedBatches[] = $batches;
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php
new file mode 100644
index 0000000..ea99b4d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Builder used to create custom batch objects
+ */
+class BatchBuilder
+{
+ /** @var bool Whether or not the batch should automatically flush*/
+ protected $autoFlush = false;
+
+ /** @var bool Whether or not to maintain a batch history */
+ protected $history = false;
+
+ /** @var bool Whether or not to buffer exceptions encountered in transfer */
+ protected $exceptionBuffering = false;
+
+ /** @var mixed Callable to invoke each time a flush completes */
+ protected $afterFlush;
+
+ /** @var BatchTransferInterface Object used to transfer items in the queue */
+ protected $transferStrategy;
+
+ /** @var BatchDivisorInterface Object used to divide the queue into batches */
+ protected $divisorStrategy;
+
+ /** @var array of Mapped transfer strategies by handle name */
+ protected static $mapping = array(
+ 'request' => 'Guzzle\Batch\BatchRequestTransfer',
+ 'command' => 'Guzzle\Batch\BatchCommandTransfer'
+ );
+
+ /**
+ * Create a new instance of the BatchBuilder
+ *
+ * @return BatchBuilder
+ */
+ public static function factory()
+ {
+ return new self();
+ }
+
+ /**
+ * Automatically flush the batch when the size of the queue reaches a certain threshold. Adds {@see FlushingBatch}.
+ *
+ * @param $threshold Number of items to allow in the queue before a flush
+ *
+ * @return BatchBuilder
+ */
+ public function autoFlushAt($threshold)
+ {
+ $this->autoFlush = $threshold;
+
+ return $this;
+ }
+
+ /**
+ * Maintain a history of all items that have been transferred using the batch. Adds {@see HistoryBatch}.
+ *
+ * @return BatchBuilder
+ */
+ public function keepHistory()
+ {
+ $this->history = true;
+
+ return $this;
+ }
+
+ /**
+ * Buffer exceptions thrown during transfer so that you can transfer as much as possible, and after a transfer
+ * completes, inspect each exception that was thrown. Enables the {@see ExceptionBufferingBatch} decorator.
+ *
+ * @return BatchBuilder
+ */
+ public function bufferExceptions()
+ {
+ $this->exceptionBuffering = true;
+
+ return $this;
+ }
+
+ /**
+ * Notify a callable each time a batch flush completes. Enables the {@see NotifyingBatch} decorator.
+ *
+ * @param mixed $callable Callable function to notify
+ *
+ * @return BatchBuilder
+ * @throws InvalidArgumentException if the argument is not callable
+ */
+ public function notify($callable)
+ {
+ $this->afterFlush = $callable;
+
+ return $this;
+ }
+
+ /**
+ * Configures the batch to transfer batches of requests. Associates a {@see \Guzzle\Http\BatchRequestTransfer}
+ * object as both the transfer and divisor strategy.
+ *
+ * @param int $batchSize Batch size for each batch of requests
+ *
+ * @return BatchBuilder
+ */
+ public function transferRequests($batchSize = 50)
+ {
+ $className = self::$mapping['request'];
+ $this->transferStrategy = new $className($batchSize);
+ $this->divisorStrategy = $this->transferStrategy;
+
+ return $this;
+ }
+
+ /**
+ * Configures the batch to transfer batches commands. Associates as
+ * {@see \Guzzle\Service\Command\BatchCommandTransfer} as both the transfer and divisor strategy.
+ *
+ * @param int $batchSize Batch size for each batch of commands
+ *
+ * @return BatchBuilder
+ */
+ public function transferCommands($batchSize = 50)
+ {
+ $className = self::$mapping['command'];
+ $this->transferStrategy = new $className($batchSize);
+ $this->divisorStrategy = $this->transferStrategy;
+
+ return $this;
+ }
+
+ /**
+ * Specify the strategy used to divide the queue into an array of batches
+ *
+ * @param BatchDivisorInterface $divisorStrategy Strategy used to divide a batch queue into batches
+ *
+ * @return BatchBuilder
+ */
+ public function createBatchesWith(BatchDivisorInterface $divisorStrategy)
+ {
+ $this->divisorStrategy = $divisorStrategy;
+
+ return $this;
+ }
+
+ /**
+ * Specify the strategy used to transport the items when flush is called
+ *
+ * @param BatchTransferInterface $transferStrategy How items are transferred
+ *
+ * @return BatchBuilder
+ */
+ public function transferWith(BatchTransferInterface $transferStrategy)
+ {
+ $this->transferStrategy = $transferStrategy;
+
+ return $this;
+ }
+
+ /**
+ * Create and return the instantiated batch
+ *
+ * @return BatchInterface
+ * @throws RuntimeException if no transfer strategy has been specified
+ */
+ public function build()
+ {
+ if (!$this->transferStrategy) {
+ throw new RuntimeException('No transfer strategy has been specified');
+ }
+
+ if (!$this->divisorStrategy) {
+ throw new RuntimeException('No divisor strategy has been specified');
+ }
+
+ $batch = new Batch($this->transferStrategy, $this->divisorStrategy);
+
+ if ($this->exceptionBuffering) {
+ $batch = new ExceptionBufferingBatch($batch);
+ }
+
+ if ($this->afterFlush) {
+ $batch = new NotifyingBatch($batch, $this->afterFlush);
+ }
+
+ if ($this->autoFlush) {
+ $batch = new FlushingBatch($batch, $this->autoFlush);
+ }
+
+ if ($this->history) {
+ $batch = new HistoryBatch($batch);
+ }
+
+ return $batch;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php
new file mode 100644
index 0000000..e0a2d95
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Divides batches using a callable
+ */
+class BatchClosureDivisor implements BatchDivisorInterface
+{
+ /** @var callable Method used to divide the batches */
+ protected $callable;
+
+ /** @var mixed $context Context passed to the callable */
+ protected $context;
+
+ /**
+ * @param callable $callable Method used to divide the batches. The method must accept an \SplQueue and return an
+ * array of arrays containing the divided items.
+ * @param mixed $context Optional context to pass to the batch divisor
+ *
+ * @throws InvalidArgumentException if the callable is not callable
+ */
+ public function __construct($callable, $context = null)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Must pass a callable');
+ }
+
+ $this->callable = $callable;
+ $this->context = $context;
+ }
+
+ public function createBatches(\SplQueue $queue)
+ {
+ return call_user_func($this->callable, $queue, $this->context);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php
new file mode 100644
index 0000000..9cbf1ab
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Batch transfer strategy where transfer logic can be defined via a Closure.
+ * This class is to be used with {@see Guzzle\Batch\BatchInterface}
+ */
+class BatchClosureTransfer implements BatchTransferInterface
+{
+ /** @var callable A closure that performs the transfer */
+ protected $callable;
+
+ /** @var mixed $context Context passed to the callable */
+ protected $context;
+
+ /**
+ * @param mixed $callable Callable that performs the transfer. This function should accept two arguments:
+ * (array $batch, mixed $context).
+ * @param mixed $context Optional context to pass to the batch divisor
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($callable, $context = null)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Argument must be callable');
+ }
+
+ $this->callable = $callable;
+ $this->context = $context;
+ }
+
+ public function transfer(array $batch)
+ {
+ return empty($batch) ? null : call_user_func($this->callable, $batch, $this->context);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php
new file mode 100644
index 0000000..d55ac7d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Batch\BatchTransferInterface;
+use Guzzle\Batch\BatchDivisorInterface;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Exception\InconsistentClientTransferException;
+
+/**
+ * Efficiently transfers multiple commands in parallel per client
+ * This class is to be used with {@see Guzzle\Batch\BatchInterface}
+ */
+class BatchCommandTransfer implements BatchTransferInterface, BatchDivisorInterface
+{
+ /** @var int Size of each command batch */
+ protected $batchSize;
+
+ /**
+ * @param int $batchSize Size of each batch
+ */
+ public function __construct($batchSize = 50)
+ {
+ $this->batchSize = $batchSize;
+ }
+
+ /**
+ * Creates batches by grouping commands by their associated client
+ * {@inheritdoc}
+ */
+ public function createBatches(\SplQueue $queue)
+ {
+ $groups = new \SplObjectStorage();
+ foreach ($queue as $item) {
+ if (!$item instanceof CommandInterface) {
+ throw new InvalidArgumentException('All items must implement Guzzle\Service\Command\CommandInterface');
+ }
+ $client = $item->getClient();
+ if (!$groups->contains($client)) {
+ $groups->attach($client, new \ArrayObject(array($item)));
+ } else {
+ $groups[$client]->append($item);
+ }
+ }
+
+ $batches = array();
+ foreach ($groups as $batch) {
+ $batches = array_merge($batches, array_chunk($groups[$batch]->getArrayCopy(), $this->batchSize));
+ }
+
+ return $batches;
+ }
+
+ public function transfer(array $batch)
+ {
+ if (empty($batch)) {
+ return;
+ }
+
+ // Get the client of the first found command
+ $client = reset($batch)->getClient();
+
+ // Keep a list of all commands with invalid clients
+ $invalid = array_filter($batch, function ($command) use ($client) {
+ return $command->getClient() !== $client;
+ });
+
+ if (!empty($invalid)) {
+ throw new InconsistentClientTransferException($invalid);
+ }
+
+ $client->execute($batch);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php
new file mode 100644
index 0000000..0214f05
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * Interface used for dividing a queue of items into an array of batches
+ */
+interface BatchDivisorInterface
+{
+ /**
+ * Divide a queue of items into an array batches
+ *
+ * @param \SplQueue $queue Queue of items to divide into batches. Items are removed as they are iterated.
+ *
+ * @return array|\Traversable Returns an array or Traversable object that contains arrays of items to transfer
+ */
+ public function createBatches(\SplQueue $queue);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchInterface.php
new file mode 100644
index 0000000..28ea65c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * Interface for efficiently transferring items in a queue using batches
+ */
+interface BatchInterface
+{
+ /**
+ * Add an item to the queue
+ *
+ * @param mixed $item Item to add
+ *
+ * @return self
+ */
+ public function add($item);
+
+ /**
+ * Flush the batch and transfer the items
+ *
+ * @return array Returns an array flushed items
+ */
+ public function flush();
+
+ /**
+ * Check if the batch is empty and has further items to transfer
+ *
+ * @return bool
+ */
+ public function isEmpty();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchRequestTransfer.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchRequestTransfer.php
new file mode 100644
index 0000000..4d8489c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchRequestTransfer.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Batch\BatchTransferInterface;
+use Guzzle\Batch\BatchDivisorInterface;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Batch transfer strategy used to efficiently transfer a batch of requests.
+ * This class is to be used with {@see Guzzle\Batch\BatchInterface}
+ */
+class BatchRequestTransfer implements BatchTransferInterface, BatchDivisorInterface
+{
+ /** @var int Size of each command batch */
+ protected $batchSize;
+
+ /**
+ * Constructor used to specify how large each batch should be
+ *
+ * @param int $batchSize Size of each batch
+ */
+ public function __construct($batchSize = 50)
+ {
+ $this->batchSize = $batchSize;
+ }
+
+ /**
+ * Creates batches of requests by grouping requests by their associated curl multi object.
+ * {@inheritdoc}
+ */
+ public function createBatches(\SplQueue $queue)
+ {
+ // Create batches by client objects
+ $groups = new \SplObjectStorage();
+ foreach ($queue as $item) {
+ if (!$item instanceof RequestInterface) {
+ throw new InvalidArgumentException('All items must implement Guzzle\Http\Message\RequestInterface');
+ }
+ $client = $item->getClient();
+ if (!$groups->contains($client)) {
+ $groups->attach($client, array($item));
+ } else {
+ $current = $groups[$client];
+ $current[] = $item;
+ $groups[$client] = $current;
+ }
+ }
+
+ $batches = array();
+ foreach ($groups as $batch) {
+ $batches = array_merge($batches, array_chunk($groups[$batch], $this->batchSize));
+ }
+
+ return $batches;
+ }
+
+ public function transfer(array $batch)
+ {
+ if ($batch) {
+ reset($batch)->getClient()->send($batch);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php
new file mode 100644
index 0000000..67f90a5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * Divides batches into smaller batches under a certain size
+ */
+class BatchSizeDivisor implements BatchDivisorInterface
+{
+ /** @var int Size of each batch */
+ protected $size;
+
+ /** @param int $size Size of each batch */
+ public function __construct($size)
+ {
+ $this->size = $size;
+ }
+
+ /**
+ * Set the size of each batch
+ *
+ * @param int $size Size of each batch
+ *
+ * @return BatchSizeDivisor
+ */
+ public function setSize($size)
+ {
+ $this->size = $size;
+
+ return $this;
+ }
+
+ /**
+ * Get the size of each batch
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function createBatches(\SplQueue $queue)
+ {
+ return array_chunk(iterator_to_array($queue, false), $this->size);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php
new file mode 100644
index 0000000..2e0b60d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * Interface used for transferring batches of items
+ */
+interface BatchTransferInterface
+{
+ /**
+ * Transfer an array of items
+ *
+ * @param array $batch Array of items to transfer
+ */
+ public function transfer(array $batch);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/Exception/BatchTransferException.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/Exception/BatchTransferException.php
new file mode 100644
index 0000000..2e1f817
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/Exception/BatchTransferException.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Guzzle\Batch\Exception;
+
+use Guzzle\Common\Exception\GuzzleException;
+use Guzzle\Batch\BatchTransferInterface as TransferStrategy;
+use Guzzle\Batch\BatchDivisorInterface as DivisorStrategy;
+
+/**
+ * Exception thrown during a batch transfer
+ */
+class BatchTransferException extends \Exception implements GuzzleException
+{
+ /** @var array The batch being sent when the exception occurred */
+ protected $batch;
+
+ /** @var TransferStrategy The transfer strategy in use when the exception occurred */
+ protected $transferStrategy;
+
+ /** @var DivisorStrategy The divisor strategy in use when the exception occurred */
+ protected $divisorStrategy;
+
+ /** @var array Items transferred at the point in which the exception was encountered */
+ protected $transferredItems;
+
+ /**
+ * @param array $batch The batch being sent when the exception occurred
+ * @param array $transferredItems Items transferred at the point in which the exception was encountered
+ * @param \Exception $exception Exception encountered
+ * @param TransferStrategy $transferStrategy The transfer strategy in use when the exception occurred
+ * @param DivisorStrategy $divisorStrategy The divisor strategy in use when the exception occurred
+ */
+ public function __construct(
+ array $batch,
+ array $transferredItems,
+ \Exception $exception,
+ TransferStrategy $transferStrategy = null,
+ DivisorStrategy $divisorStrategy = null
+ ) {
+ $this->batch = $batch;
+ $this->transferredItems = $transferredItems;
+ $this->transferStrategy = $transferStrategy;
+ $this->divisorStrategy = $divisorStrategy;
+ parent::__construct(
+ 'Exception encountered while transferring batch: ' . $exception->getMessage(),
+ $exception->getCode(),
+ $exception
+ );
+ }
+
+ /**
+ * Get the batch that we being sent when the exception occurred
+ *
+ * @return array
+ */
+ public function getBatch()
+ {
+ return $this->batch;
+ }
+
+ /**
+ * Get the items transferred at the point in which the exception was encountered
+ *
+ * @return array
+ */
+ public function getTransferredItems()
+ {
+ return $this->transferredItems;
+ }
+
+ /**
+ * Get the transfer strategy
+ *
+ * @return TransferStrategy
+ */
+ public function getTransferStrategy()
+ {
+ return $this->transferStrategy;
+ }
+
+ /**
+ * Get the divisor strategy
+ *
+ * @return DivisorStrategy
+ */
+ public function getDivisorStrategy()
+ {
+ return $this->divisorStrategy;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php
new file mode 100644
index 0000000..d7a8928
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Batch\Exception\BatchTransferException;
+
+/**
+ * BatchInterface decorator used to buffer exceptions encountered during a transfer. The exceptions can then later be
+ * processed after a batch flush has completed.
+ */
+class ExceptionBufferingBatch extends AbstractBatchDecorator
+{
+ /** @var array Array of BatchTransferException exceptions */
+ protected $exceptions = array();
+
+ public function flush()
+ {
+ $items = array();
+
+ while (!$this->decoratedBatch->isEmpty()) {
+ try {
+ $transferredItems = $this->decoratedBatch->flush();
+ } catch (BatchTransferException $e) {
+ $this->exceptions[] = $e;
+ $transferredItems = $e->getTransferredItems();
+ }
+ $items = array_merge($items, $transferredItems);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Get the buffered exceptions
+ *
+ * @return array Array of BatchTransferException objects
+ */
+ public function getExceptions()
+ {
+ return $this->exceptions;
+ }
+
+ /**
+ * Clear the buffered exceptions
+ */
+ public function clearExceptions()
+ {
+ $this->exceptions = array();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php
new file mode 100644
index 0000000..367b684
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * BatchInterface decorator used to add automatic flushing of the queue when the size of the queue reaches a threshold.
+ */
+class FlushingBatch extends AbstractBatchDecorator
+{
+ /** @var int The threshold for which to automatically flush */
+ protected $threshold;
+
+ /** @var int Current number of items known to be in the queue */
+ protected $currentTotal = 0;
+
+ /**
+ * @param BatchInterface $decoratedBatch BatchInterface that is being decorated
+ * @param int $threshold Flush when the number in queue matches the threshold
+ */
+ public function __construct(BatchInterface $decoratedBatch, $threshold)
+ {
+ $this->threshold = $threshold;
+ parent::__construct($decoratedBatch);
+ }
+
+ /**
+ * Set the auto-flush threshold
+ *
+ * @param int $threshold The auto-flush threshold
+ *
+ * @return FlushingBatch
+ */
+ public function setThreshold($threshold)
+ {
+ $this->threshold = $threshold;
+
+ return $this;
+ }
+
+ /**
+ * Get the auto-flush threshold
+ *
+ * @return int
+ */
+ public function getThreshold()
+ {
+ return $this->threshold;
+ }
+
+ public function add($item)
+ {
+ $this->decoratedBatch->add($item);
+ if (++$this->currentTotal >= $this->threshold) {
+ $this->currentTotal = 0;
+ $this->decoratedBatch->flush();
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php
new file mode 100644
index 0000000..e345fdc
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Batch;
+
+/**
+ * BatchInterface decorator used to keep a history of items that were added to the batch. You must clear the history
+ * manually to remove items from the history.
+ */
+class HistoryBatch extends AbstractBatchDecorator
+{
+ /** @var array Items in the history */
+ protected $history = array();
+
+ public function add($item)
+ {
+ $this->history[] = $item;
+ $this->decoratedBatch->add($item);
+
+ return $this;
+ }
+
+ /**
+ * Get the batch history
+ *
+ * @return array
+ */
+ public function getHistory()
+ {
+ return $this->history;
+ }
+
+ /**
+ * Clear the batch history
+ */
+ public function clearHistory()
+ {
+ $this->history = array();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php b/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php
new file mode 100644
index 0000000..96d04da
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Guzzle\Batch;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * BatchInterface decorator used to call a method each time flush is called
+ */
+class NotifyingBatch extends AbstractBatchDecorator
+{
+ /** @var mixed Callable to call */
+ protected $callable;
+
+ /**
+ * @param BatchInterface $decoratedBatch Batch object to decorate
+ * @param mixed $callable Callable to call
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(BatchInterface $decoratedBatch, $callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('The passed argument is not callable');
+ }
+
+ $this->callable = $callable;
+ parent::__construct($decoratedBatch);
+ }
+
+ public function flush()
+ {
+ $items = $this->decoratedBatch->flush();
+ call_user_func($this->callable, $items);
+
+ return $items;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json
new file mode 100644
index 0000000..12404d3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "guzzle/batch",
+ "description": "Guzzle batch component for batching requests, commands, or custom transfers",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["batch", "HTTP", "REST", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/common": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Batch": "" }
+ },
+ "suggest": {
+ "guzzle/http": "self.version",
+ "guzzle/service": "self.version"
+ },
+ "target-dir": "Guzzle/Batch",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php
new file mode 100644
index 0000000..a5c5271
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Cache;
+
+/**
+ * Abstract cache adapter
+ */
+abstract class AbstractCacheAdapter implements CacheAdapterInterface
+{
+ protected $cache;
+
+ /**
+ * Get the object owned by the adapter
+ *
+ * @return mixed
+ */
+ public function getCacheObject()
+ {
+ return $this->cache;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php
new file mode 100644
index 0000000..94e6234
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Guzzle\Cache;
+
+use Doctrine\Common\Cache\Cache;
+use Guzzle\Common\Version;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Common\FromConfigInterface;
+use Zend\Cache\Storage\StorageInterface;
+
+/**
+ * Generates cache adapters from any number of known cache implementations
+ */
+class CacheAdapterFactory implements FromConfigInterface
+{
+ /**
+ * Create a Guzzle cache adapter based on an array of options
+ *
+ * @param mixed $cache Cache value
+ *
+ * @return CacheAdapterInterface
+ * @throws InvalidArgumentException
+ */
+ public static function fromCache($cache)
+ {
+ if (!is_object($cache)) {
+ throw new InvalidArgumentException('Cache must be one of the known cache objects');
+ }
+
+ if ($cache instanceof CacheAdapterInterface) {
+ return $cache;
+ } elseif ($cache instanceof Cache) {
+ return new DoctrineCacheAdapter($cache);
+ } elseif ($cache instanceof StorageInterface) {
+ return new Zf2CacheAdapter($cache);
+ } else {
+ throw new InvalidArgumentException('Unknown cache type: ' . get_class($cache));
+ }
+ }
+
+ /**
+ * Create a Guzzle cache adapter based on an array of options
+ *
+ * @param array $config Array of configuration options
+ *
+ * @return CacheAdapterInterface
+ * @throws InvalidArgumentException
+ * @deprecated This will be removed in a future version
+ * @codeCoverageIgnore
+ */
+ public static function factory($config = array())
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ if (!is_array($config)) {
+ throw new InvalidArgumentException('$config must be an array');
+ }
+
+ if (!isset($config['cache.adapter']) && !isset($config['cache.provider'])) {
+ $config['cache.adapter'] = 'Guzzle\Cache\NullCacheAdapter';
+ $config['cache.provider'] = null;
+ } else {
+ // Validate that the options are valid
+ foreach (array('cache.adapter', 'cache.provider') as $required) {
+ if (!isset($config[$required])) {
+ throw new InvalidArgumentException("{$required} is a required CacheAdapterFactory option");
+ }
+ if (is_string($config[$required])) {
+ // Convert dot notation to namespaces
+ $config[$required] = str_replace('.', '\\', $config[$required]);
+ if (!class_exists($config[$required])) {
+ throw new InvalidArgumentException("{$config[$required]} is not a valid class for {$required}");
+ }
+ }
+ }
+ // Instantiate the cache provider
+ if (is_string($config['cache.provider'])) {
+ $args = isset($config['cache.provider.args']) ? $config['cache.provider.args'] : null;
+ $config['cache.provider'] = self::createObject($config['cache.provider'], $args);
+ }
+ }
+
+ // Instantiate the cache adapter using the provider and options
+ if (is_string($config['cache.adapter'])) {
+ $args = isset($config['cache.adapter.args']) ? $config['cache.adapter.args'] : array();
+ array_unshift($args, $config['cache.provider']);
+ $config['cache.adapter'] = self::createObject($config['cache.adapter'], $args);
+ }
+
+ return $config['cache.adapter'];
+ }
+
+ /**
+ * Create a class using an array of constructor arguments
+ *
+ * @param string $className Class name
+ * @param array $args Arguments for the class constructor
+ *
+ * @return mixed
+ * @throws RuntimeException
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ private static function createObject($className, array $args = null)
+ {
+ try {
+ if (!$args) {
+ return new $className;
+ } else {
+ $c = new \ReflectionClass($className);
+ return $c->newInstanceArgs($args);
+ }
+ } catch (\Exception $e) {
+ throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php
new file mode 100644
index 0000000..970c9e2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Guzzle\Cache;
+
+/**
+ * Interface for cache adapters.
+ *
+ * Cache adapters allow Guzzle to utilize various frameworks for caching HTTP responses.
+ *
+ * @link http://www.doctrine-project.org/ Inspired by Doctrine 2
+ */
+interface CacheAdapterInterface
+{
+ /**
+ * Test if an entry exists in the cache.
+ *
+ * @param string $id cache id The cache id of the entry to check for.
+ * @param array $options Array of cache adapter options
+ *
+ * @return bool Returns TRUE if a cache entry exists for the given cache id, FALSE otherwise.
+ */
+ public function contains($id, array $options = null);
+
+ /**
+ * Deletes a cache entry.
+ *
+ * @param string $id cache id
+ * @param array $options Array of cache adapter options
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ public function delete($id, array $options = null);
+
+ /**
+ * Fetches an entry from the cache.
+ *
+ * @param string $id cache id The id of the cache entry to fetch.
+ * @param array $options Array of cache adapter options
+ *
+ * @return string The cached data or FALSE, if no cache entry exists for the given id.
+ */
+ public function fetch($id, array $options = null);
+
+ /**
+ * Puts data into the cache.
+ *
+ * @param string $id The cache id
+ * @param string $data The cache entry/data
+ * @param int|bool $lifeTime The lifetime. If != false, sets a specific lifetime for this cache entry
+ * @param array $options Array of cache adapter options
+ *
+ * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
+ */
+ public function save($id, $data, $lifeTime = false, array $options = null);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/ClosureCacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/ClosureCacheAdapter.php
new file mode 100644
index 0000000..c7a3df4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/ClosureCacheAdapter.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Guzzle\Cache;
+
+/**
+ * Cache adapter that defers to closures for implementation
+ */
+class ClosureCacheAdapter implements CacheAdapterInterface
+{
+ /**
+ * @var array Mapping of method names to callables
+ */
+ protected $callables;
+
+ /**
+ * The callables array is an array mapping the actions of the cache adapter to callables.
+ * - contains: Callable that accepts an $id and $options argument
+ * - delete: Callable that accepts an $id and $options argument
+ * - fetch: Callable that accepts an $id and $options argument
+ * - save: Callable that accepts an $id, $data, $lifeTime, and $options argument
+ *
+ * @param array $callables array of action names to callable
+ *
+ * @throws \InvalidArgumentException if the callable is not callable
+ */
+ public function __construct(array $callables)
+ {
+ // Validate each key to ensure it exists and is callable
+ foreach (array('contains', 'delete', 'fetch', 'save') as $key) {
+ if (!array_key_exists($key, $callables) || !is_callable($callables[$key])) {
+ throw new \InvalidArgumentException("callables must contain a callable {$key} key");
+ }
+ }
+
+ $this->callables = $callables;
+ }
+
+ public function contains($id, array $options = null)
+ {
+ return call_user_func($this->callables['contains'], $id, $options);
+ }
+
+ public function delete($id, array $options = null)
+ {
+ return call_user_func($this->callables['delete'], $id, $options);
+ }
+
+ public function fetch($id, array $options = null)
+ {
+ return call_user_func($this->callables['fetch'], $id, $options);
+ }
+
+ public function save($id, $data, $lifeTime = false, array $options = null)
+ {
+ return call_user_func($this->callables['save'], $id, $data, $lifeTime, $options);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php
new file mode 100644
index 0000000..e1aaf9f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Guzzle\Cache;
+
+use Doctrine\Common\Cache\Cache;
+
+/**
+ * Doctrine 2 cache adapter
+ *
+ * @link http://www.doctrine-project.org/
+ */
+class DoctrineCacheAdapter extends AbstractCacheAdapter
+{
+ /**
+ * @param Cache $cache Doctrine cache object
+ */
+ public function __construct(Cache $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ public function contains($id, array $options = null)
+ {
+ return $this->cache->contains($id);
+ }
+
+ public function delete($id, array $options = null)
+ {
+ return $this->cache->delete($id);
+ }
+
+ public function fetch($id, array $options = null)
+ {
+ return $this->cache->fetch($id);
+ }
+
+ public function save($id, $data, $lifeTime = false, array $options = null)
+ {
+ return $this->cache->save($id, $data, $lifeTime !== false ? $lifeTime : 0);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php
new file mode 100644
index 0000000..68bd4af
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Guzzle\Cache;
+
+/**
+ * Null cache adapter
+ */
+class NullCacheAdapter extends AbstractCacheAdapter
+{
+ public function __construct() {}
+
+ public function contains($id, array $options = null)
+ {
+ return false;
+ }
+
+ public function delete($id, array $options = null)
+ {
+ return true;
+ }
+
+ public function fetch($id, array $options = null)
+ {
+ return false;
+ }
+
+ public function save($id, $data, $lifeTime = false, array $options = null)
+ {
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf1CacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf1CacheAdapter.php
new file mode 100644
index 0000000..48f8e24
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf1CacheAdapter.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Guzzle\Cache;
+
+use Guzzle\Common\Version;
+
+/**
+ * Zend Framework 1 cache adapter
+ *
+ * @link http://framework.zend.com/manual/en/zend.cache.html
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+class Zf1CacheAdapter extends AbstractCacheAdapter
+{
+ /**
+ * @param \Zend_Cache_Backend $cache Cache object to wrap
+ */
+ public function __construct(\Zend_Cache_Backend $cache)
+ {
+ Version::warn(__CLASS__ . ' is deprecated. Upgrade to ZF2 or use PsrCacheAdapter');
+ $this->cache = $cache;
+ }
+
+ public function contains($id, array $options = null)
+ {
+ return $this->cache->test($id);
+ }
+
+ public function delete($id, array $options = null)
+ {
+ return $this->cache->remove($id);
+ }
+
+ public function fetch($id, array $options = null)
+ {
+ return $this->cache->load($id);
+ }
+
+ public function save($id, $data, $lifeTime = false, array $options = null)
+ {
+ return $this->cache->save($data, $id, array(), $lifeTime);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php
new file mode 100644
index 0000000..1fc18a5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Guzzle\Cache;
+
+use Zend\Cache\Storage\StorageInterface;
+
+/**
+ * Zend Framework 2 cache adapter
+ *
+ * @link http://packages.zendframework.com/docs/latest/manual/en/zend.cache.html
+ */
+class Zf2CacheAdapter extends AbstractCacheAdapter
+{
+ /**
+ * @param StorageInterface $cache Zend Framework 2 cache adapter
+ */
+ public function __construct(StorageInterface $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ public function contains($id, array $options = null)
+ {
+ return $this->cache->hasItem($id);
+ }
+
+ public function delete($id, array $options = null)
+ {
+ return $this->cache->removeItem($id);
+ }
+
+ public function fetch($id, array $options = null)
+ {
+ return $this->cache->getItem($id);
+ }
+
+ public function save($id, $data, $lifeTime = false, array $options = null)
+ {
+ return $this->cache->setItem($id, $data);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json
new file mode 100644
index 0000000..a5d999b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/cache",
+ "description": "Guzzle cache adapter component",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["cache", "adapter", "zf", "doctrine", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/common": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Cache": "" }
+ },
+ "target-dir": "Guzzle/Cache",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php b/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php
new file mode 100644
index 0000000..d1e842b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Guzzle\Common;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Class that holds an event dispatcher
+ */
+class AbstractHasDispatcher implements HasDispatcherInterface
+{
+ /** @var EventDispatcherInterface */
+ protected $eventDispatcher;
+
+ public static function getAllEvents()
+ {
+ return array();
+ }
+
+ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+
+ return $this;
+ }
+
+ public function getEventDispatcher()
+ {
+ if (!$this->eventDispatcher) {
+ $this->eventDispatcher = new EventDispatcher();
+ }
+
+ return $this->eventDispatcher;
+ }
+
+ public function dispatch($eventName, array $context = array())
+ {
+ return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
+ }
+
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->getEventDispatcher()->addSubscriber($subscriber);
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php
new file mode 100644
index 0000000..5cb1535
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php
@@ -0,0 +1,403 @@
+<?php
+
+namespace Guzzle\Common;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Key value pair collection object
+ */
+class Collection implements \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface
+{
+ /** @var array Data associated with the object. */
+ protected $data;
+
+ /**
+ * @param array $data Associative array of data to set
+ */
+ public function __construct(array $data = array())
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Create a new collection from an array, validate the keys, and add default values where missing
+ *
+ * @param array $config Configuration values to apply.
+ * @param array $defaults Default parameters
+ * @param array $required Required parameter names
+ *
+ * @return self
+ * @throws InvalidArgumentException if a parameter is missing
+ */
+ public static function fromConfig(array $config = array(), array $defaults = array(), array $required = array())
+ {
+ $data = $config + $defaults;
+
+ if ($missing = array_diff($required, array_keys($data))) {
+ throw new InvalidArgumentException('Config is missing the following keys: ' . implode(', ', $missing));
+ }
+
+ return new self($data);
+ }
+
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->data);
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Removes all key value pairs
+ *
+ * @return Collection
+ */
+ public function clear()
+ {
+ $this->data = array();
+
+ return $this;
+ }
+
+ /**
+ * Get all or a subset of matching key value pairs
+ *
+ * @param array $keys Pass an array of keys to retrieve only a subset of key value pairs
+ *
+ * @return array Returns an array of all matching key value pairs
+ */
+ public function getAll(array $keys = null)
+ {
+ return $keys ? array_intersect_key($this->data, array_flip($keys)) : $this->data;
+ }
+
+ /**
+ * Get a specific key value.
+ *
+ * @param string $key Key to retrieve.
+ *
+ * @return mixed|null Value of the key or NULL
+ */
+ public function get($key)
+ {
+ return isset($this->data[$key]) ? $this->data[$key] : null;
+ }
+
+ /**
+ * Set a key value pair
+ *
+ * @param string $key Key to set
+ * @param mixed $value Value to set
+ *
+ * @return Collection Returns a reference to the object
+ */
+ public function set($key, $value)
+ {
+ $this->data[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add a value to a key. If a key of the same name has already been added, the key value will be converted into an
+ * array and the new value will be pushed to the end of the array.
+ *
+ * @param string $key Key to add
+ * @param mixed $value Value to add to the key
+ *
+ * @return Collection Returns a reference to the object.
+ */
+ public function add($key, $value)
+ {
+ if (!array_key_exists($key, $this->data)) {
+ $this->data[$key] = $value;
+ } elseif (is_array($this->data[$key])) {
+ $this->data[$key][] = $value;
+ } else {
+ $this->data[$key] = array($this->data[$key], $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove a specific key value pair
+ *
+ * @param string $key A key to remove
+ *
+ * @return Collection
+ */
+ public function remove($key)
+ {
+ unset($this->data[$key]);
+
+ return $this;
+ }
+
+ /**
+ * Get all keys in the collection
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ return array_keys($this->data);
+ }
+
+ /**
+ * Returns whether or not the specified key is present.
+ *
+ * @param string $key The key for which to check the existence.
+ *
+ * @return bool
+ */
+ public function hasKey($key)
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ /**
+ * Case insensitive search the keys in the collection
+ *
+ * @param string $key Key to search for
+ *
+ * @return bool|string Returns false if not found, otherwise returns the key
+ */
+ public function keySearch($key)
+ {
+ foreach (array_keys($this->data) as $k) {
+ if (!strcasecmp($k, $key)) {
+ return $k;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if any keys contains a certain value
+ *
+ * @param string $value Value to search for
+ *
+ * @return mixed Returns the key if the value was found FALSE if the value was not found.
+ */
+ public function hasValue($value)
+ {
+ return array_search($value, $this->data);
+ }
+
+ /**
+ * Replace the data of the object with the value of an array
+ *
+ * @param array $data Associative array of data
+ *
+ * @return Collection Returns a reference to the object
+ */
+ public function replace(array $data)
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * Add and merge in a Collection or array of key value pair data.
+ *
+ * @param Collection|array $data Associative array of key value pair data
+ *
+ * @return Collection Returns a reference to the object.
+ */
+ public function merge($data)
+ {
+ foreach ($data as $key => $value) {
+ $this->add($key, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Over write key value pairs in this collection with all of the data from an array or collection.
+ *
+ * @param array|\Traversable $data Values to override over this config
+ *
+ * @return self
+ */
+ public function overwriteWith($data)
+ {
+ if (is_array($data)) {
+ $this->data = $data + $this->data;
+ } elseif ($data instanceof Collection) {
+ $this->data = $data->toArray() + $this->data;
+ } else {
+ foreach ($data as $key => $value) {
+ $this->data[$key] = $value;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns a Collection containing all the elements of the collection after applying the callback function to each
+ * one. The Closure should accept three parameters: (string) $key, (string) $value, (array) $context and return a
+ * modified value
+ *
+ * @param \Closure $closure Closure to apply
+ * @param array $context Context to pass to the closure
+ * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
+ *
+ * @return Collection
+ */
+ public function map(\Closure $closure, array $context = array(), $static = true)
+ {
+ $collection = $static ? new static() : new self();
+ foreach ($this as $key => $value) {
+ $collection->add($key, $closure($key, $value, $context));
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Iterates over each key value pair in the collection passing them to the Closure. If the Closure function returns
+ * true, the current value from input is returned into the result Collection. The Closure must accept three
+ * parameters: (string) $key, (string) $value and return Boolean TRUE or FALSE for each value.
+ *
+ * @param \Closure $closure Closure evaluation function
+ * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
+ *
+ * @return Collection
+ */
+ public function filter(\Closure $closure, $static = true)
+ {
+ $collection = ($static) ? new static() : new self();
+ foreach ($this->data as $key => $value) {
+ if ($closure($key, $value)) {
+ $collection->add($key, $value);
+ }
+ }
+
+ return $collection;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($this->data[$offset]) ? $this->data[$offset] : null;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->data[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->data[$offset]);
+ }
+
+ /**
+ * Set a value into a nested array key. Keys will be created as needed to set the value.
+ *
+ * @param string $path Path to set
+ * @param mixed $value Value to set at the key
+ *
+ * @return self
+ * @throws RuntimeException when trying to setPath using a nested path that travels through a scalar value
+ */
+ public function setPath($path, $value)
+ {
+ $current =& $this->data;
+ $queue = explode('/', $path);
+ while (null !== ($key = array_shift($queue))) {
+ if (!is_array($current)) {
+ throw new RuntimeException("Trying to setPath {$path}, but {$key} is set and is not an array");
+ } elseif (!$queue) {
+ $current[$key] = $value;
+ } elseif (isset($current[$key])) {
+ $current =& $current[$key];
+ } else {
+ $current[$key] = array();
+ $current =& $current[$key];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets a value from the collection using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays)
+ * Allows for wildcard searches which recursively combine matches up to the level at which the wildcard occurs. This
+ * can be useful for accepting any key of a sub-array and combining matching keys from each diverging path.
+ *
+ * @param string $path Path to traverse and retrieve a value from
+ * @param string $separator Character used to add depth to the search
+ * @param mixed $data Optional data to descend into (used when wildcards are encountered)
+ *
+ * @return mixed|null
+ */
+ public function getPath($path, $separator = '/', $data = null)
+ {
+ if ($data === null) {
+ $data =& $this->data;
+ }
+
+ $path = is_array($path) ? $path : explode($separator, $path);
+ while (null !== ($part = array_shift($path))) {
+ if (!is_array($data)) {
+ return null;
+ } elseif (isset($data[$part])) {
+ $data =& $data[$part];
+ } elseif ($part != '*') {
+ return null;
+ } else {
+ // Perform a wildcard search by diverging and merging paths
+ $result = array();
+ foreach ($data as $value) {
+ if (!$path) {
+ $result = array_merge_recursive($result, (array) $value);
+ } elseif (null !== ($test = $this->getPath($path, $separator, $value))) {
+ $result = array_merge_recursive($result, (array) $test);
+ }
+ }
+ return $result;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Inject configuration settings into an input string
+ *
+ * @param string $input Input to inject
+ *
+ * @return string
+ * @deprecated
+ */
+ public function inject($input)
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ $replace = array();
+ foreach ($this->data as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ return strtr($input, $replace);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php
new file mode 100644
index 0000000..fad76a9
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Guzzle\Common;
+
+use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
+
+/**
+ * Default event for Guzzle notifications
+ */
+class Event extends SymfonyEvent implements ToArrayInterface, \ArrayAccess, \IteratorAggregate
+{
+ /** @var array */
+ private $context;
+
+ /**
+ * @param array $context Contextual information
+ */
+ public function __construct(array $context = array())
+ {
+ $this->context = $context;
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->context);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($this->context[$offset]) ? $this->context[$offset] : null;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->context[$offset] = $value;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->context[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->context[$offset]);
+ }
+
+ public function toArray()
+ {
+ return $this->context;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php
new file mode 100644
index 0000000..08d1c72
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+class BadMethodCallException extends \BadMethodCallException implements GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/ExceptionCollection.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/ExceptionCollection.php
new file mode 100644
index 0000000..750e483
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/ExceptionCollection.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+/**
+ * Collection of exceptions
+ */
+class ExceptionCollection extends \Exception implements GuzzleException, \IteratorAggregate, \Countable
+{
+ /** @var array Array of Exceptions */
+ protected $exceptions = array();
+
+ /** @var string Succinct exception message not including sub-exceptions */
+ private $shortMessage;
+
+ public function __construct($message = '', $code = 0, \Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ $this->shortMessage = $message;
+ }
+
+ /**
+ * Set all of the exceptions
+ *
+ * @param array $exceptions Array of exceptions
+ *
+ * @return self
+ */
+ public function setExceptions(array $exceptions)
+ {
+ $this->exceptions = array();
+ foreach ($exceptions as $exception) {
+ $this->add($exception);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add exceptions to the collection
+ *
+ * @param ExceptionCollection|\Exception $e Exception to add
+ *
+ * @return ExceptionCollection;
+ */
+ public function add($e)
+ {
+ $this->exceptions[] = $e;
+ if ($this->message) {
+ $this->message .= "\n";
+ }
+
+ $this->message .= $this->getExceptionMessage($e, 0);
+
+ return $this;
+ }
+
+ /**
+ * Get the total number of request exceptions
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->exceptions);
+ }
+
+ /**
+ * Allows array-like iteration over the request exceptions
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->exceptions);
+ }
+
+ /**
+ * Get the first exception in the collection
+ *
+ * @return \Exception
+ */
+ public function getFirst()
+ {
+ return $this->exceptions ? $this->exceptions[0] : null;
+ }
+
+ private function getExceptionMessage(\Exception $e, $depth = 0)
+ {
+ static $sp = ' ';
+ $prefix = $depth ? str_repeat($sp, $depth) : '';
+ $message = "{$prefix}(" . get_class($e) . ') ' . $e->getFile() . ' line ' . $e->getLine() . "\n";
+
+ if ($e instanceof self) {
+ if ($e->shortMessage) {
+ $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->shortMessage) . "\n";
+ }
+ foreach ($e as $ee) {
+ $message .= "\n" . $this->getExceptionMessage($ee, $depth + 1);
+ }
+ } else {
+ $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getMessage()) . "\n";
+ $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getTraceAsString()) . "\n";
+ }
+
+ return str_replace(getcwd(), '.', $message);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php
new file mode 100644
index 0000000..458e6f2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+/**
+ * Guzzle exception
+ */
+interface GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..ae674be
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/RuntimeException.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/RuntimeException.php
new file mode 100644
index 0000000..9254094
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/RuntimeException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+class RuntimeException extends \RuntimeException implements GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/UnexpectedValueException.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/UnexpectedValueException.php
new file mode 100644
index 0000000..843c017
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/UnexpectedValueException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Common\Exception;
+
+class UnexpectedValueException extends \UnexpectedValueException implements GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/FromConfigInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Common/FromConfigInterface.php
new file mode 100644
index 0000000..c8b1317
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/FromConfigInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Common;
+
+/**
+ * Interfaces that adds a factory method which is used to instantiate a class from an array of configuration options.
+ */
+interface FromConfigInterface
+{
+ /**
+ * Static factory method used to turn an array or collection of configuration data into an instantiated object.
+ *
+ * @param array|Collection $config Configuration data
+ *
+ * @return FromConfigInterface
+ */
+ public static function factory($config = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/HasDispatcherInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Common/HasDispatcherInterface.php
new file mode 100644
index 0000000..8067598
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/HasDispatcherInterface.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Guzzle\Common;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Holds an event dispatcher
+ */
+interface HasDispatcherInterface
+{
+ /**
+ * Get a list of all of the events emitted from the class
+ *
+ * @return array
+ */
+ public static function getAllEvents();
+
+ /**
+ * Set the EventDispatcher of the request
+ *
+ * @param EventDispatcherInterface $eventDispatcher
+ *
+ * @return self
+ */
+ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher);
+
+ /**
+ * Get the EventDispatcher of the request
+ *
+ * @return EventDispatcherInterface
+ */
+ public function getEventDispatcher();
+
+ /**
+ * Helper to dispatch Guzzle events and set the event name on the event
+ *
+ * @param string $eventName Name of the event to dispatch
+ * @param array $context Context of the event
+ *
+ * @return Event Returns the created event object
+ */
+ public function dispatch($eventName, array $context = array());
+
+ /**
+ * Add an event subscriber to the dispatcher
+ *
+ * @param EventSubscriberInterface $subscriber Event subscriber
+ *
+ * @return self
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/ToArrayInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Common/ToArrayInterface.php
new file mode 100644
index 0000000..245328c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/ToArrayInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Guzzle\Common;
+
+/**
+ * An object that can be represented as an array
+ */
+interface ToArrayInterface
+{
+ /**
+ * Get the array representation of an object
+ *
+ * @return array
+ */
+ public function toArray();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/Version.php b/vendor/guzzle/guzzle/src/Guzzle/Common/Version.php
new file mode 100644
index 0000000..1a171c3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/Version.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Guzzle\Common;
+
+/**
+ * Guzzle version information
+ */
+class Version
+{
+ const VERSION = '3.9.3';
+
+ /**
+ * @var bool Set this value to true to enable warnings for deprecated functionality use. This should be on in your
+ * unit tests, but probably not in production.
+ */
+ public static $emitWarnings = false;
+
+ /**
+ * Emit a deprecation warning
+ *
+ * @param string $message Warning message
+ */
+ public static function warn($message)
+ {
+ if (self::$emitWarnings) {
+ trigger_error('Deprecation warning: ' . $message, E_USER_DEPRECATED);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Common/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Common/composer.json
new file mode 100644
index 0000000..c02fa69
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Common/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "guzzle/common",
+ "homepage": "http://guzzlephp.org/",
+ "description": "Common libraries used by Guzzle",
+ "keywords": ["common", "event", "exception", "collection"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.2",
+ "symfony/event-dispatcher": ">=2.1"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Common": "" }
+ },
+ "target-dir": "Guzzle/Common",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php b/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php
new file mode 100644
index 0000000..5005a88
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php
@@ -0,0 +1,221 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Stream\Stream;
+
+/**
+ * Abstract decorator used to wrap entity bodies
+ */
+class AbstractEntityBodyDecorator implements EntityBodyInterface
+{
+ /** @var EntityBodyInterface Decorated entity body */
+ protected $body;
+
+ /**
+ * @param EntityBodyInterface $body Entity body to decorate
+ */
+ public function __construct(EntityBodyInterface $body)
+ {
+ $this->body = $body;
+ }
+
+ public function __toString()
+ {
+ return (string) $this->body;
+ }
+
+ /**
+ * 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)
+ {
+ return call_user_func_array(array($this->body, $method), $args);
+ }
+
+ public function close()
+ {
+ return $this->body->close();
+ }
+
+ public function setRewindFunction($callable)
+ {
+ $this->body->setRewindFunction($callable);
+
+ return $this;
+ }
+
+ public function rewind()
+ {
+ return $this->body->rewind();
+ }
+
+ public function compress($filter = 'zlib.deflate')
+ {
+ return $this->body->compress($filter);
+ }
+
+ public function uncompress($filter = 'zlib.inflate')
+ {
+ return $this->body->uncompress($filter);
+ }
+
+ public function getContentLength()
+ {
+ return $this->getSize();
+ }
+
+ public function getContentType()
+ {
+ return $this->body->getContentType();
+ }
+
+ public function getContentMd5($rawOutput = false, $base64Encode = false)
+ {
+ $hash = Stream::getHash($this, 'md5', $rawOutput);
+
+ return $hash && $base64Encode ? base64_encode($hash) : $hash;
+ }
+
+ public function getContentEncoding()
+ {
+ return $this->body->getContentEncoding();
+ }
+
+ public function getMetaData($key = null)
+ {
+ return $this->body->getMetaData($key);
+ }
+
+ public function getStream()
+ {
+ return $this->body->getStream();
+ }
+
+ public function setStream($stream, $size = 0)
+ {
+ $this->body->setStream($stream, $size);
+
+ return $this;
+ }
+
+ public function detachStream()
+ {
+ $this->body->detachStream();
+
+ return $this;
+ }
+
+ public function getWrapper()
+ {
+ return $this->body->getWrapper();
+ }
+
+ public function getWrapperData()
+ {
+ return $this->body->getWrapperData();
+ }
+
+ public function getStreamType()
+ {
+ return $this->body->getStreamType();
+ }
+
+ public function getUri()
+ {
+ return $this->body->getUri();
+ }
+
+ public function getSize()
+ {
+ return $this->body->getSize();
+ }
+
+ public function isReadable()
+ {
+ return $this->body->isReadable();
+ }
+
+ public function isRepeatable()
+ {
+ return $this->isSeekable() && $this->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->body->isWritable();
+ }
+
+ public function isConsumed()
+ {
+ return $this->body->isConsumed();
+ }
+
+ /**
+ * Alias of isConsumed()
+ * {@inheritdoc}
+ */
+ public function feof()
+ {
+ return $this->isConsumed();
+ }
+
+ public function isLocal()
+ {
+ return $this->body->isLocal();
+ }
+
+ public function isSeekable()
+ {
+ return $this->body->isSeekable();
+ }
+
+ public function setSize($size)
+ {
+ $this->body->setSize($size);
+
+ return $this;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return $this->body->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->body->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->body->write($string);
+ }
+
+ public function readLine($maxLength = null)
+ {
+ return $this->body->readLine($maxLength);
+ }
+
+ public function ftell()
+ {
+ return $this->body->ftell();
+ }
+
+ public function getCustomData($key)
+ {
+ return $this->body->getCustomData($key);
+ }
+
+ public function setCustomData($key, $value)
+ {
+ $this->body->setCustomData($key, $value);
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php b/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php
new file mode 100644
index 0000000..c65c136
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * EntityBody decorator that can cache previously read bytes from a sequentially read tstream
+ */
+class CachingEntityBody extends AbstractEntityBodyDecorator
+{
+ /** @var EntityBody Remote stream used to actually pull data onto the buffer */
+ protected $remoteStream;
+
+ /** @var int The number of bytes to skip reading due to a write on the temporary buffer */
+ protected $skipReadBytes = 0;
+
+ /**
+ * We will treat the buffer object as the body of the entity body
+ * {@inheritdoc}
+ */
+ public function __construct(EntityBodyInterface $body)
+ {
+ $this->remoteStream = $body;
+ $this->body = new EntityBody(fopen('php://temp', 'r+'));
+ }
+
+ /**
+ * Will give the contents of the buffer followed by the exhausted remote stream.
+ *
+ * Warning: Loads the entire stream into memory
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $pos = $this->ftell();
+ $this->rewind();
+
+ $str = '';
+ while (!$this->isConsumed()) {
+ $str .= $this->read(16384);
+ }
+
+ $this->seek($pos);
+
+ return $str;
+ }
+
+ public function getSize()
+ {
+ return max($this->body->getSize(), $this->remoteStream->getSize());
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException 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->ftell();
+ } else {
+ throw new RuntimeException(__CLASS__ . ' supports only SEEK_SET and SEEK_CUR seek operations');
+ }
+
+ // You cannot skip ahead past where you've read from the remote stream
+ if ($byte > $this->body->getSize()) {
+ throw new RuntimeException(
+ "Cannot seek to byte {$byte} when the buffered stream only contains {$this->body->getSize()} bytes"
+ );
+ }
+
+ return $this->body->seek($byte);
+ }
+
+ public function rewind()
+ {
+ return $this->seek(0);
+ }
+
+ /**
+ * Does not support custom rewind functions
+ *
+ * @throws RuntimeException
+ */
+ public function setRewindFunction($callable)
+ {
+ throw new RuntimeException(__CLASS__ . ' does not support custom stream rewind functions');
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->body->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->body->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->ftell()) - $this->remoteStream->ftell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->body->write($string);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @link http://php.net/manual/en/function.fgets.php
+ */
+ public function readLine($maxLength = null)
+ {
+ $buffer = '';
+ $size = 0;
+ while (!$this->isConsumed()) {
+ $byte = $this->read(1);
+ $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;
+ }
+
+ public function isConsumed()
+ {
+ return $this->body->isConsumed() && $this->remoteStream->isConsumed();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ return $this->remoteStream->close() && $this->body->close();
+ }
+
+ public function setStream($stream, $size = 0)
+ {
+ $this->remoteStream->setStream($stream, $size);
+ }
+
+ public function getContentType()
+ {
+ return $this->remoteStream->getContentType();
+ }
+
+ public function getContentEncoding()
+ {
+ return $this->remoteStream->getContentEncoding();
+ }
+
+ public function getMetaData($key = null)
+ {
+ return $this->remoteStream->getMetaData($key);
+ }
+
+ public function getStream()
+ {
+ return $this->remoteStream->getStream();
+ }
+
+ public function getWrapper()
+ {
+ return $this->remoteStream->getWrapper();
+ }
+
+ public function getWrapperData()
+ {
+ return $this->remoteStream->getWrapperData();
+ }
+
+ public function getStreamType()
+ {
+ return $this->remoteStream->getStreamType();
+ }
+
+ public function getUri()
+ {
+ return $this->remoteStream->getUri();
+ }
+
+ /**
+ * Always retrieve custom data from the remote stream
+ * {@inheritdoc}
+ */
+ public function getCustomData($key)
+ {
+ return $this->remoteStream->getCustomData($key);
+ }
+
+ /**
+ * Always set custom data on the remote stream
+ * {@inheritdoc}
+ */
+ public function setCustomData($key, $value)
+ {
+ $this->remoteStream->setCustomData($key, $value);
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php
new file mode 100644
index 0000000..3d7298d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php
@@ -0,0 +1,524 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Common\Exception\ExceptionCollection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Common\Version;
+use Guzzle\Parser\ParserRegistry;
+use Guzzle\Parser\UriTemplate\UriTemplateInterface;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\RequestFactoryInterface;
+use Guzzle\Http\Curl\CurlMultiInterface;
+use Guzzle\Http\Curl\CurlMultiProxy;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Http\Curl\CurlVersion;
+
+/**
+ * HTTP client
+ */
+class Client extends AbstractHasDispatcher implements ClientInterface
+{
+ /** @deprecated Use [request.options][params] */
+ const REQUEST_PARAMS = 'request.params';
+
+ const REQUEST_OPTIONS = 'request.options';
+ const CURL_OPTIONS = 'curl.options';
+ const SSL_CERT_AUTHORITY = 'ssl.certificate_authority';
+ const DISABLE_REDIRECTS = RedirectPlugin::DISABLE;
+ const DEFAULT_SELECT_TIMEOUT = 1.0;
+ const MAX_HANDLES = 3;
+
+ /** @var Collection Default HTTP headers to set on each request */
+ protected $defaultHeaders;
+
+ /** @var string The user agent string to set on each request */
+ protected $userAgent;
+
+ /** @var Collection Parameter object holding configuration data */
+ private $config;
+
+ /** @var Url Base URL of the client */
+ private $baseUrl;
+
+ /** @var CurlMultiInterface CurlMulti object used internally */
+ private $curlMulti;
+
+ /** @var UriTemplateInterface URI template owned by the client */
+ private $uriTemplate;
+
+ /** @var RequestFactoryInterface Request factory used by the client */
+ protected $requestFactory;
+
+ public static function getAllEvents()
+ {
+ return array(self::CREATE_REQUEST);
+ }
+
+ /**
+ * @param string $baseUrl Base URL of the web service
+ * @param array|Collection $config Configuration settings
+ *
+ * @throws RuntimeException if cURL is not installed
+ */
+ public function __construct($baseUrl = '', $config = null)
+ {
+ if (!extension_loaded('curl')) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('The PHP cURL extension must be installed to use Guzzle.');
+ // @codeCoverageIgnoreEnd
+ }
+ $this->setConfig($config ?: new Collection());
+ $this->initSsl();
+ $this->setBaseUrl($baseUrl);
+ $this->defaultHeaders = new Collection();
+ $this->setRequestFactory(RequestFactory::getInstance());
+ $this->userAgent = $this->getDefaultUserAgent();
+ if (!$this->config[self::DISABLE_REDIRECTS]) {
+ $this->addSubscriber(new RedirectPlugin());
+ }
+ }
+
+ final public function setConfig($config)
+ {
+ if ($config instanceof Collection) {
+ $this->config = $config;
+ } elseif (is_array($config)) {
+ $this->config = new Collection($config);
+ } else {
+ throw new InvalidArgumentException('Config must be an array or Collection');
+ }
+
+ return $this;
+ }
+
+ final public function getConfig($key = false)
+ {
+ return $key ? $this->config[$key] : $this->config;
+ }
+
+ /**
+ * Set a default request option on the client that will be used as a default for each request
+ *
+ * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
+ * @param mixed $value Value to set
+ *
+ * @return $this
+ */
+ public function setDefaultOption($keyOrPath, $value)
+ {
+ $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
+ $this->config->setPath($keyOrPath, $value);
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a default request option from the client
+ *
+ * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
+ *
+ * @return mixed|null
+ */
+ public function getDefaultOption($keyOrPath)
+ {
+ $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
+
+ return $this->config->getPath($keyOrPath);
+ }
+
+ final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2)
+ {
+ $opts = $this->config[self::CURL_OPTIONS] ?: array();
+
+ if ($certificateAuthority === true) {
+ // use bundled CA bundle, set secure defaults
+ $opts[CURLOPT_CAINFO] = __DIR__ . '/Resources/cacert.pem';
+ $opts[CURLOPT_SSL_VERIFYPEER] = true;
+ $opts[CURLOPT_SSL_VERIFYHOST] = 2;
+ } elseif ($certificateAuthority === false) {
+ unset($opts[CURLOPT_CAINFO]);
+ $opts[CURLOPT_SSL_VERIFYPEER] = false;
+ $opts[CURLOPT_SSL_VERIFYHOST] = 0;
+ } elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) {
+ throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean');
+ } elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) {
+ throw new InvalidArgumentException('verifyHost must be 0, 1 or 2');
+ } else {
+ $opts[CURLOPT_SSL_VERIFYPEER] = $verifyPeer;
+ $opts[CURLOPT_SSL_VERIFYHOST] = $verifyHost;
+ if (is_file($certificateAuthority)) {
+ unset($opts[CURLOPT_CAPATH]);
+ $opts[CURLOPT_CAINFO] = $certificateAuthority;
+ } elseif (is_dir($certificateAuthority)) {
+ unset($opts[CURLOPT_CAINFO]);
+ $opts[CURLOPT_CAPATH] = $certificateAuthority;
+ } else {
+ throw new RuntimeException(
+ 'Invalid option passed to ' . self::SSL_CERT_AUTHORITY . ': ' . $certificateAuthority
+ );
+ }
+ }
+
+ $this->config->set(self::CURL_OPTIONS, $opts);
+
+ return $this;
+ }
+
+ public function createRequest($method = 'GET', $uri = null, $headers = null, $body = null, array $options = array())
+ {
+ if (!$uri) {
+ $url = $this->getBaseUrl();
+ } else {
+ if (!is_array($uri)) {
+ $templateVars = null;
+ } else {
+ list($uri, $templateVars) = $uri;
+ }
+ if (strpos($uri, '://')) {
+ // Use absolute URLs as-is
+ $url = $this->expandTemplate($uri, $templateVars);
+ } else {
+ $url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars));
+ }
+ }
+
+ // If default headers are provided, then merge them under any explicitly provided headers for the request
+ if (count($this->defaultHeaders)) {
+ if (!$headers) {
+ $headers = $this->defaultHeaders->toArray();
+ } elseif (is_array($headers)) {
+ $headers += $this->defaultHeaders->toArray();
+ } elseif ($headers instanceof Collection) {
+ $headers = $headers->toArray() + $this->defaultHeaders->toArray();
+ }
+ }
+
+ return $this->prepareRequest($this->requestFactory->create($method, (string) $url, $headers, $body), $options);
+ }
+
+ public function getBaseUrl($expand = true)
+ {
+ return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl;
+ }
+
+ public function setBaseUrl($url)
+ {
+ $this->baseUrl = $url;
+
+ return $this;
+ }
+
+ public function setUserAgent($userAgent, $includeDefault = false)
+ {
+ if ($includeDefault) {
+ $userAgent .= ' ' . $this->getDefaultUserAgent();
+ }
+ $this->userAgent = $userAgent;
+
+ return $this;
+ }
+
+ /**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+ public function getDefaultUserAgent()
+ {
+ return 'Guzzle/' . Version::VERSION
+ . ' curl/' . CurlVersion::getInstance()->get('version')
+ . ' PHP/' . PHP_VERSION;
+ }
+
+ public function get($uri = null, $headers = null, $options = array())
+ {
+ // BC compat: $options can be a string, resource, etc to specify where the response body is downloaded
+ return is_array($options)
+ ? $this->createRequest('GET', $uri, $headers, null, $options)
+ : $this->createRequest('GET', $uri, $headers, $options);
+ }
+
+ public function head($uri = null, $headers = null, array $options = array())
+ {
+ return $this->createRequest('HEAD', $uri, $headers, null, $options);
+ }
+
+ public function delete($uri = null, $headers = null, $body = null, array $options = array())
+ {
+ return $this->createRequest('DELETE', $uri, $headers, $body, $options);
+ }
+
+ public function put($uri = null, $headers = null, $body = null, array $options = array())
+ {
+ return $this->createRequest('PUT', $uri, $headers, $body, $options);
+ }
+
+ public function patch($uri = null, $headers = null, $body = null, array $options = array())
+ {
+ return $this->createRequest('PATCH', $uri, $headers, $body, $options);
+ }
+
+ public function post($uri = null, $headers = null, $postBody = null, array $options = array())
+ {
+ return $this->createRequest('POST', $uri, $headers, $postBody, $options);
+ }
+
+ public function options($uri = null, array $options = array())
+ {
+ return $this->createRequest('OPTIONS', $uri, $options);
+ }
+
+ public function send($requests)
+ {
+ if (!($requests instanceof RequestInterface)) {
+ return $this->sendMultiple($requests);
+ }
+
+ try {
+ /** @var $requests RequestInterface */
+ $this->getCurlMulti()->add($requests)->send();
+ return $requests->getResponse();
+ } catch (ExceptionCollection $e) {
+ throw $e->getFirst();
+ }
+ }
+
+ /**
+ * Set a curl multi object to be used internally by the client for transferring requests.
+ *
+ * @param CurlMultiInterface $curlMulti Multi object
+ *
+ * @return self
+ */
+ public function setCurlMulti(CurlMultiInterface $curlMulti)
+ {
+ $this->curlMulti = $curlMulti;
+
+ return $this;
+ }
+
+ /**
+ * @return CurlMultiInterface|CurlMultiProxy
+ */
+ public function getCurlMulti()
+ {
+ if (!$this->curlMulti) {
+ $this->curlMulti = new CurlMultiProxy(
+ self::MAX_HANDLES,
+ $this->getConfig('select_timeout') ?: self::DEFAULT_SELECT_TIMEOUT
+ );
+ }
+
+ return $this->curlMulti;
+ }
+
+ public function setRequestFactory(RequestFactoryInterface $factory)
+ {
+ $this->requestFactory = $factory;
+
+ return $this;
+ }
+
+ /**
+ * Set the URI template expander to use with the client
+ *
+ * @param UriTemplateInterface $uriTemplate URI template expander
+ *
+ * @return self
+ */
+ public function setUriTemplate(UriTemplateInterface $uriTemplate)
+ {
+ $this->uriTemplate = $uriTemplate;
+
+ return $this;
+ }
+
+ /**
+ * Expand a URI template while merging client config settings into the template variables
+ *
+ * @param string $template Template to expand
+ * @param array $variables Variables to inject
+ *
+ * @return string
+ */
+ protected function expandTemplate($template, array $variables = null)
+ {
+ $expansionVars = $this->getConfig()->toArray();
+ if ($variables) {
+ $expansionVars = $variables + $expansionVars;
+ }
+
+ return $this->getUriTemplate()->expand($template, $expansionVars);
+ }
+
+ /**
+ * Get the URI template expander used by the client
+ *
+ * @return UriTemplateInterface
+ */
+ protected function getUriTemplate()
+ {
+ if (!$this->uriTemplate) {
+ $this->uriTemplate = ParserRegistry::getInstance()->getParser('uri_template');
+ }
+
+ return $this->uriTemplate;
+ }
+
+ /**
+ * Send multiple requests in parallel
+ *
+ * @param array $requests Array of RequestInterface objects
+ *
+ * @return array Returns an array of Response objects
+ */
+ protected function sendMultiple(array $requests)
+ {
+ $curlMulti = $this->getCurlMulti();
+ foreach ($requests as $request) {
+ $curlMulti->add($request);
+ }
+ $curlMulti->send();
+
+ /** @var $request RequestInterface */
+ $result = array();
+ foreach ($requests as $request) {
+ $result[] = $request->getResponse();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Prepare a request to be sent from the Client by adding client specific behaviors and properties to the request.
+ *
+ * @param RequestInterface $request Request to prepare for the client
+ * @param array $options Options to apply to the request
+ *
+ * @return RequestInterface
+ */
+ protected function prepareRequest(RequestInterface $request, array $options = array())
+ {
+ $request->setClient($this)->setEventDispatcher(clone $this->getEventDispatcher());
+
+ if ($curl = $this->config[self::CURL_OPTIONS]) {
+ $request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($curl));
+ }
+
+ if ($params = $this->config[self::REQUEST_PARAMS]) {
+ Version::warn('request.params is deprecated. Use request.options to add default request options.');
+ $request->getParams()->overwriteWith($params);
+ }
+
+ if ($this->userAgent && !$request->hasHeader('User-Agent')) {
+ $request->setHeader('User-Agent', $this->userAgent);
+ }
+
+ if ($defaults = $this->config[self::REQUEST_OPTIONS]) {
+ $this->requestFactory->applyOptions($request, $defaults, RequestFactoryInterface::OPTIONS_AS_DEFAULTS);
+ }
+
+ if ($options) {
+ $this->requestFactory->applyOptions($request, $options);
+ }
+
+ $this->dispatch('client.create_request', array('client' => $this, 'request' => $request));
+
+ return $request;
+ }
+
+ /**
+ * Initializes SSL settings
+ */
+ protected function initSsl()
+ {
+ $authority = $this->config[self::SSL_CERT_AUTHORITY];
+
+ if ($authority === 'system') {
+ return;
+ }
+
+ if ($authority === null) {
+ $authority = true;
+ }
+
+ if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') {
+ $authority = self::extractPharCacert(__DIR__ . '/Resources/cacert.pem');
+ }
+
+ $this->setSslVerification($authority);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function getDefaultHeaders()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to retrieve default request options');
+ return $this->defaultHeaders;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function setDefaultHeaders($headers)
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to specify default request options');
+ if ($headers instanceof Collection) {
+ $this->defaultHeaders = $headers;
+ } elseif (is_array($headers)) {
+ $this->defaultHeaders = new Collection($headers);
+ } else {
+ throw new InvalidArgumentException('Headers must be an array or Collection');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function preparePharCacert($md5Check = true)
+ {
+ return sys_get_temp_dir() . '/guzzle-cacert.pem';
+ }
+
+ /**
+ * Copies the phar cacert from a phar into the temp directory.
+ *
+ * @param string $pharCacertPath Path to the phar cacert. For example:
+ * 'phar://aws.phar/Guzzle/Http/Resources/cacert.pem'
+ *
+ * @return string Returns the path to the extracted cacert file.
+ * @throws \RuntimeException Throws if the phar cacert cannot be found or
+ * the file cannot be copied to the temp dir.
+ */
+ public static function extractPharCacert($pharCacertPath)
+ {
+ // Copy the cacert.pem file from the phar if it is not in the temp
+ // folder.
+ $certFile = sys_get_temp_dir() . '/guzzle-cacert.pem';
+
+ if (!file_exists($pharCacertPath)) {
+ throw new \RuntimeException("Could not find $pharCacertPath");
+ }
+
+ if (!file_exists($certFile) ||
+ filesize($certFile) != filesize($pharCacertPath)
+ ) {
+ if (!copy($pharCacertPath, $certFile)) {
+ throw new \RuntimeException(
+ "Could not copy {$pharCacertPath} to {$certFile}: "
+ . var_export(error_get_last(), true)
+ );
+ }
+ }
+
+ return $certFile;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php
new file mode 100644
index 0000000..10e4de2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\HasDispatcherInterface;
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Client interface for send HTTP requests
+ */
+interface ClientInterface extends HasDispatcherInterface
+{
+ const CREATE_REQUEST = 'client.create_request';
+
+ /** @var string RFC 1123 HTTP-Date */
+ const HTTP_DATE = 'D, d M Y H:i:s \G\M\T';
+
+ /**
+ * Set the configuration object to use with the client
+ *
+ * @param array|Collection $config Parameters that define how the client behaves
+ *
+ * @return self
+ */
+ public function setConfig($config);
+
+ /**
+ * Get a configuration setting or all of the configuration settings. The Collection result of this method can be
+ * modified to change the configuration settings of a client.
+ *
+ * A client should honor the following special values:
+ *
+ * - request.options: Associative array of default RequestFactory options to apply to each request
+ * - request.params: Associative array of request parameters (data values) to apply to each request
+ * - curl.options: Associative array of cURL configuration settings to apply to each request
+ * - ssl.certificate_authority: Path a CAINFO, CAPATH, true to use strict defaults, or false to disable verification
+ * - redirect.disable: Set to true to disable redirects
+ *
+ * @param bool|string $key Configuration value to retrieve. Set to FALSE to retrieve all values of the client.
+ * The object return can be modified, and modifications will affect the client's config.
+ * @return mixed|Collection
+ * @see \Guzzle\Http\Message\RequestFactoryInterface::applyOptions for a full list of request.options options
+ */
+ public function getConfig($key = false);
+
+ /**
+ * Create and return a new {@see RequestInterface} configured for the client.
+ *
+ * Use an absolute path to override the base path of the client, or a relative path to append to the base path of
+ * the client. The URI can contain the query string as well. Use an array to provide a URI template and additional
+ * variables to use in the URI template expansion.
+ *
+ * @param string $method HTTP method. Defaults to GET
+ * @param string|array $uri Resource URI.
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|array|EntityBodyInterface $body Entity body of request (POST/PUT) or response (GET)
+ * @param array $options Array of options to apply to the request
+ *
+ * @return RequestInterface
+ * @throws InvalidArgumentException if a URI array is passed that does not contain exactly two elements: the URI
+ * followed by template variables
+ */
+ public function createRequest(
+ $method = RequestInterface::GET,
+ $uri = null,
+ $headers = null,
+ $body = null,
+ array $options = array()
+ );
+
+ /**
+ * Create a GET request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param array $options Options to apply to the request. For BC compatibility, you can also pass a
+ * string to tell Guzzle to download the body of the response to a particular
+ * location. Use the 'body' option instead for forward compatibility.
+ * @return RequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function get($uri = null, $headers = null, $options = array());
+
+ /**
+ * Create a HEAD request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param array $options Options to apply to the request
+ *
+ * @return RequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function head($uri = null, $headers = null, array $options = array());
+
+ /**
+ * Create a DELETE request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|EntityBodyInterface $body Body to send in the request
+ * @param array $options Options to apply to the request
+ *
+ * @return EntityEnclosingRequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function delete($uri = null, $headers = null, $body = null, array $options = array());
+
+ /**
+ * Create a PUT request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|EntityBodyInterface $body Body to send in the request
+ * @param array $options Options to apply to the request
+ *
+ * @return EntityEnclosingRequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function put($uri = null, $headers = null, $body = null, array $options = array());
+
+ /**
+ * Create a PATCH request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|EntityBodyInterface $body Body to send in the request
+ * @param array $options Options to apply to the request
+ *
+ * @return EntityEnclosingRequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function patch($uri = null, $headers = null, $body = null, array $options = array());
+
+ /**
+ * Create a POST request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array|Collection $headers HTTP headers
+ * @param array|Collection|string|EntityBodyInterface $postBody POST body. Can be a string, EntityBody, or
+ * associative array of POST fields to send in the body of the
+ * request. Prefix a value in the array with the @ symbol to
+ * reference a file.
+ * @param array $options Options to apply to the request
+ *
+ * @return EntityEnclosingRequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function post($uri = null, $headers = null, $postBody = null, array $options = array());
+
+ /**
+ * Create an OPTIONS request for the client
+ *
+ * @param string|array $uri Resource URI
+ * @param array $options Options to apply to the request
+ *
+ * @return RequestInterface
+ * @see Guzzle\Http\ClientInterface::createRequest()
+ */
+ public function options($uri = null, array $options = array());
+
+ /**
+ * Sends a single request or an array of requests in parallel
+ *
+ * @param array|RequestInterface $requests One or more RequestInterface objects to send
+ *
+ * @return \Guzzle\Http\Message\Response|array Returns a single Response or an array of Response objects
+ */
+ public function send($requests);
+
+ /**
+ * Get the client's base URL as either an expanded or raw URI template
+ *
+ * @param bool $expand Set to FALSE to get the raw base URL without URI template expansion
+ *
+ * @return string|null
+ */
+ public function getBaseUrl($expand = true);
+
+ /**
+ * Set the base URL of the client
+ *
+ * @param string $url The base service endpoint URL of the webservice
+ *
+ * @return self
+ */
+ public function setBaseUrl($url);
+
+ /**
+ * Set the User-Agent header to be used on all requests from the client
+ *
+ * @param string $userAgent User agent string
+ * @param bool $includeDefault Set to true to prepend the value to Guzzle's default user agent string
+ *
+ * @return self
+ */
+ public function setUserAgent($userAgent, $includeDefault = false);
+
+ /**
+ * Set SSL verification options.
+ *
+ * Setting $certificateAuthority to TRUE will result in the bundled cacert.pem being used to verify against the
+ * remote host.
+ *
+ * Alternate certificates to verify against can be specified with the $certificateAuthority option set to the full
+ * path to a certificate file, or the path to a directory containing certificates.
+ *
+ * Setting $certificateAuthority to FALSE will turn off peer verification, unset the bundled cacert.pem, and
+ * disable host verification. Please don't do this unless you really know what you're doing, and why you're doing
+ * it.
+ *
+ * @param string|bool $certificateAuthority bool, file path, or directory path
+ * @param bool $verifyPeer FALSE to stop from verifying the peer's certificate.
+ * @param int $verifyHost Set to 1 to check the existence of a common name in the SSL peer
+ * certificate. 2 to check the existence of a common name and also verify
+ * that it matches the hostname provided.
+ * @return self
+ */
+ public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php
new file mode 100644
index 0000000..efba5d1
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php
@@ -0,0 +1,464 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Common\Collection;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Parser\ParserRegistry;
+use Guzzle\Http\Url;
+
+/**
+ * Immutable wrapper for a cURL handle
+ */
+class CurlHandle
+{
+ const BODY_AS_STRING = 'body_as_string';
+ const PROGRESS = 'progress';
+ const DEBUG = 'debug';
+
+ /** @var Collection Curl options */
+ protected $options;
+
+ /** @var resource Curl resource handle */
+ protected $handle;
+
+ /** @var int CURLE_* error */
+ protected $errorNo = CURLE_OK;
+
+ /**
+ * Factory method to create a new curl handle based on an HTTP request.
+ *
+ * There are some helpful options you can set to enable specific behavior:
+ * - debug: Set to true to enable cURL debug functionality to track the actual headers sent over the wire.
+ * - progress: Set to true to enable progress function callbacks.
+ *
+ * @param RequestInterface $request Request
+ *
+ * @return CurlHandle
+ * @throws RuntimeException
+ */
+ public static function factory(RequestInterface $request)
+ {
+ $requestCurlOptions = $request->getCurlOptions();
+ $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
+ $tempContentLength = null;
+ $method = $request->getMethod();
+ $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
+
+ // Prepare url
+ $url = (string)$request->getUrl();
+ if(($pos = strpos($url, '#')) !== false ){
+ // strip fragment from url
+ $url = substr($url, 0, $pos);
+ }
+
+ // Array of default cURL options.
+ $curlOptions = array(
+ CURLOPT_URL => $url,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_PORT => $request->getPort(),
+ CURLOPT_HTTPHEADER => array(),
+ CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'),
+ CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
+ CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
+ ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
+ // Verifies the authenticity of the peer's certificate
+ CURLOPT_SSL_VERIFYPEER => 1,
+ // Certificate must indicate that the server is the server to which you meant to connect
+ CURLOPT_SSL_VERIFYHOST => 2
+ );
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ // Allow only HTTP and HTTPS protocols
+ $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ // Add CURLOPT_ENCODING if Accept-Encoding header is provided
+ if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
+ $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
+ // Let cURL set the Accept-Encoding header, prevents duplicate values
+ $request->removeHeader('Accept-Encoding');
+ }
+
+ // Enable curl debug information if the 'debug' param was set
+ if ($requestCurlOptions->get('debug')) {
+ $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
+ // @codeCoverageIgnoreStart
+ if (false === $curlOptions[CURLOPT_STDERR]) {
+ throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
+ }
+ // @codeCoverageIgnoreEnd
+ $curlOptions[CURLOPT_VERBOSE] = true;
+ }
+
+ // Specify settings according to the HTTP method
+ if ($method == 'GET') {
+ $curlOptions[CURLOPT_HTTPGET] = true;
+ } elseif ($method == 'HEAD') {
+ $curlOptions[CURLOPT_NOBODY] = true;
+ // HEAD requests do not use a write function
+ unset($curlOptions[CURLOPT_WRITEFUNCTION]);
+ } elseif (!($request instanceof EntityEnclosingRequest)) {
+ $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
+ } else {
+
+ $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
+
+ // Handle sending raw bodies in a request
+ if ($request->getBody()) {
+ // You can send the body as a string using curl's CURLOPT_POSTFIELDS
+ if ($bodyAsString) {
+ $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Allow curl to add the Content-Length for us to account for the times when
+ // POST redirects are followed by GET requests
+ if ($tempContentLength = $request->getHeader('Content-Length')) {
+ $tempContentLength = (int) (string) $tempContentLength;
+ }
+ // Remove the curl generated Content-Type header if none was set manually
+ if (!$request->hasHeader('Content-Type')) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ } else {
+ $curlOptions[CURLOPT_UPLOAD] = true;
+ // Let cURL handle setting the Content-Length header
+ if ($tempContentLength = $request->getHeader('Content-Length')) {
+ $tempContentLength = (int) (string) $tempContentLength;
+ $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
+ }
+ // Add a callback for curl to read data to send with the request only if a body was specified
+ $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
+ // Attempt to seek to the start of the stream
+ $request->getBody()->seek(0);
+ }
+
+ } else {
+
+ // Special handling for POST specific fields and files
+ $postFields = false;
+ if (count($request->getPostFiles())) {
+ $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
+ foreach ($request->getPostFiles() as $key => $data) {
+ $prefixKeys = count($data) > 1;
+ foreach ($data as $index => $file) {
+ // Allow multiple files in the same key
+ $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
+ $postFields[$fieldKey] = $file->getCurlValue();
+ }
+ }
+ } elseif (count($request->getPostFields())) {
+ $postFields = (string) $request->getPostFields()->useUrlEncoding(true);
+ }
+
+ if ($postFields !== false) {
+ if ($method == 'POST') {
+ unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
+ $curlOptions[CURLOPT_POST] = true;
+ }
+ $curlOptions[CURLOPT_POSTFIELDS] = $postFields;
+ $request->removeHeader('Content-Length');
+ }
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+ }
+
+ // If a Content-Length header was specified but we want to allow curl to set one for us
+ if (null !== $tempContentLength) {
+ $request->removeHeader('Content-Length');
+ }
+
+ // Set custom cURL options
+ foreach ($requestCurlOptions->toArray() as $key => $value) {
+ if (is_numeric($key)) {
+ $curlOptions[$key] = $value;
+ }
+ }
+
+ // Do not set an Accept header by default
+ if (!isset($curlOptions[CURLOPT_ENCODING])) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+
+ // Add any custom headers to the request. Empty headers will cause curl to not send the header at all.
+ foreach ($request->getHeaderLines() as $line) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = $line;
+ }
+
+ // Add the content-length header back if it was temporarily removed
+ if (null !== $tempContentLength) {
+ $request->setHeader('Content-Length', $tempContentLength);
+ }
+
+ // Apply the options to a new cURL handle.
+ $handle = curl_init();
+
+ // Enable the progress function if the 'progress' param was set
+ if ($requestCurlOptions->get('progress')) {
+ // Wrap the function in a function that provides the curl handle to the mediator's progress function
+ // Using this rather than injecting the handle into the mediator prevents a circular reference
+ $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
+ $args = func_get_args();
+ $args[] = $handle;
+
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+
+ call_user_func_array(array($mediator, 'progress'), $args);
+ };
+ $curlOptions[CURLOPT_NOPROGRESS] = false;
+ }
+
+ curl_setopt_array($handle, $curlOptions);
+
+ return new static($handle, $curlOptions);
+ }
+
+ /**
+ * Construct a new CurlHandle object that wraps a cURL handle
+ *
+ * @param resource $handle Configured cURL handle resource
+ * @param Collection|array $options Curl options to use with the handle
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($handle, $options)
+ {
+ if (!is_resource($handle)) {
+ throw new InvalidArgumentException('Invalid handle provided');
+ }
+ if (is_array($options)) {
+ $this->options = new Collection($options);
+ } elseif ($options instanceof Collection) {
+ $this->options = $options;
+ } else {
+ throw new InvalidArgumentException('Expected array or Collection');
+ }
+ $this->handle = $handle;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * Close the curl handle
+ */
+ public function close()
+ {
+ if (is_resource($this->handle)) {
+ curl_close($this->handle);
+ }
+ $this->handle = null;
+ }
+
+ /**
+ * Check if the handle is available and still OK
+ *
+ * @return bool
+ */
+ public function isAvailable()
+ {
+ return is_resource($this->handle);
+ }
+
+ /**
+ * Get the last error that occurred on the cURL handle
+ *
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->isAvailable() ? curl_error($this->handle) : '';
+ }
+
+ /**
+ * Get the last error number that occurred on the cURL handle
+ *
+ * @return int
+ */
+ public function getErrorNo()
+ {
+ if ($this->errorNo) {
+ return $this->errorNo;
+ }
+
+ return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
+ }
+
+ /**
+ * Set the curl error number
+ *
+ * @param int $error Error number to set
+ *
+ * @return CurlHandle
+ */
+ public function setErrorNo($error)
+ {
+ $this->errorNo = $error;
+
+ return $this;
+ }
+
+ /**
+ * Get cURL curl_getinfo data
+ *
+ * @param int $option Option to retrieve. Pass null to retrieve all data as an array.
+ *
+ * @return array|mixed
+ */
+ public function getInfo($option = null)
+ {
+ if (!is_resource($this->handle)) {
+ return null;
+ }
+
+ if (null !== $option) {
+ return curl_getinfo($this->handle, $option) ?: null;
+ }
+
+ return curl_getinfo($this->handle) ?: array();
+ }
+
+ /**
+ * Get the stderr output
+ *
+ * @param bool $asResource Set to TRUE to get an fopen resource
+ *
+ * @return string|resource|null
+ */
+ public function getStderr($asResource = false)
+ {
+ $stderr = $this->getOptions()->get(CURLOPT_STDERR);
+ if (!$stderr) {
+ return null;
+ }
+
+ if ($asResource) {
+ return $stderr;
+ }
+
+ fseek($stderr, 0);
+ $e = stream_get_contents($stderr);
+ fseek($stderr, 0, SEEK_END);
+
+ return $e;
+ }
+
+ /**
+ * Get the URL that this handle is connecting to
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return Url::factory($this->options->get(CURLOPT_URL));
+ }
+
+ /**
+ * Get the wrapped curl handle
+ *
+ * @return resource|null Returns the cURL handle or null if it was closed
+ */
+ public function getHandle()
+ {
+ return $this->isAvailable() ? $this->handle : null;
+ }
+
+ /**
+ * Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl
+ * handle after it is created.
+ *
+ * @return Collection
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Update a request based on the log messages of the CurlHandle
+ *
+ * @param RequestInterface $request Request to update
+ */
+ public function updateRequestFromTransfer(RequestInterface $request)
+ {
+ if (!$request->getResponse()) {
+ return;
+ }
+
+ // Update the transfer stats of the response
+ $request->getResponse()->setInfo($this->getInfo());
+
+ if (!$log = $this->getStderr(true)) {
+ return;
+ }
+
+ // Parse the cURL stderr output for outgoing requests
+ $headers = '';
+ fseek($log, 0);
+ while (($line = fgets($log)) !== false) {
+ if ($line && $line[0] == '>') {
+ $headers = substr(trim($line), 2) . "\r\n";
+ while (($line = fgets($log)) !== false) {
+ if ($line[0] == '*' || $line[0] == '<') {
+ break;
+ } else {
+ $headers .= trim($line) . "\r\n";
+ }
+ }
+ }
+ }
+
+ // Add request headers to the request exactly as they were sent
+ if ($headers) {
+ $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
+ if (!empty($parsed['headers'])) {
+ $request->setHeaders(array());
+ foreach ($parsed['headers'] as $name => $value) {
+ $request->setHeader($name, $value);
+ }
+ }
+ if (!empty($parsed['version'])) {
+ $request->setProtocolVersion($parsed['version']);
+ }
+ }
+ }
+
+ /**
+ * Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere
+ *
+ * @param array|Collection $config The configuration we want to parse
+ *
+ * @return array
+ */
+ public static function parseCurlConfig($config)
+ {
+ $curlOptions = array();
+ foreach ($config as $key => $value) {
+ if (is_string($key) && defined($key)) {
+ // Convert constants represented as string to constant int values
+ $key = constant($key);
+ }
+ if (is_string($value) && defined($value)) {
+ $value = constant($value);
+ }
+ $curlOptions[$key] = $value;
+ }
+
+ return $curlOptions;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php
new file mode 100644
index 0000000..9e4e637
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php
@@ -0,0 +1,423 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Common\Event;
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Exception\RequestException;
+
+/**
+ * Send {@see RequestInterface} objects in parallel using curl_multi
+ */
+class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
+{
+ /** @var resource cURL multi handle. */
+ protected $multiHandle;
+
+ /** @var array Attached {@see RequestInterface} objects. */
+ protected $requests;
+
+ /** @var \SplObjectStorage RequestInterface to CurlHandle hash */
+ protected $handles;
+
+ /** @var array Hash mapping curl handle resource IDs to request objects */
+ protected $resourceHash;
+
+ /** @var array Queued exceptions */
+ protected $exceptions = array();
+
+ /** @var array Requests that succeeded */
+ protected $successful = array();
+
+ /** @var array cURL multi error values and codes */
+ protected $multiErrors = array(
+ CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'),
+ CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."),
+ CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'),
+ CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!')
+ );
+
+ /** @var float */
+ protected $selectTimeout;
+
+ public function __construct($selectTimeout = 1.0)
+ {
+ $this->selectTimeout = $selectTimeout;
+ $this->multiHandle = curl_multi_init();
+ // @codeCoverageIgnoreStart
+ if ($this->multiHandle === false) {
+ throw new CurlException('Unable to create multi handle');
+ }
+ // @codeCoverageIgnoreEnd
+ $this->reset();
+ }
+
+ public function __destruct()
+ {
+ if (is_resource($this->multiHandle)) {
+ curl_multi_close($this->multiHandle);
+ }
+ }
+
+ public function add(RequestInterface $request)
+ {
+ $this->requests[] = $request;
+ // If requests are currently transferring and this is async, then the
+ // request must be prepared now as the send() method is not called.
+ $this->beforeSend($request);
+ $this->dispatch(self::ADD_REQUEST, array('request' => $request));
+
+ return $this;
+ }
+
+ public function all()
+ {
+ return $this->requests;
+ }
+
+ public function remove(RequestInterface $request)
+ {
+ $this->removeHandle($request);
+ if (($index = array_search($request, $this->requests, true)) !== false) {
+ $request = $this->requests[$index];
+ unset($this->requests[$index]);
+ $this->requests = array_values($this->requests);
+ $this->dispatch(self::REMOVE_REQUEST, array('request' => $request));
+ return true;
+ }
+
+ return false;
+ }
+
+ public function reset($hard = false)
+ {
+ // Remove each request
+ if ($this->requests) {
+ foreach ($this->requests as $request) {
+ $this->remove($request);
+ }
+ }
+
+ $this->handles = new \SplObjectStorage();
+ $this->requests = $this->resourceHash = $this->exceptions = $this->successful = array();
+ }
+
+ public function send()
+ {
+ $this->perform();
+ $exceptions = $this->exceptions;
+ $successful = $this->successful;
+ $this->reset();
+
+ if ($exceptions) {
+ $this->throwMultiException($exceptions, $successful);
+ }
+ }
+
+ public function count()
+ {
+ return count($this->requests);
+ }
+
+ /**
+ * Build and throw a MultiTransferException
+ *
+ * @param array $exceptions Exceptions encountered
+ * @param array $successful Successful requests
+ * @throws MultiTransferException
+ */
+ protected function throwMultiException(array $exceptions, array $successful)
+ {
+ $multiException = new MultiTransferException('Errors during multi transfer');
+
+ while ($e = array_shift($exceptions)) {
+ $multiException->addFailedRequestWithException($e['request'], $e['exception']);
+ }
+
+ // Add successful requests
+ foreach ($successful as $request) {
+ if (!$multiException->containsRequest($request)) {
+ $multiException->addSuccessfulRequest($request);
+ }
+ }
+
+ throw $multiException;
+ }
+
+ /**
+ * Prepare for sending
+ *
+ * @param RequestInterface $request Request to prepare
+ * @throws \Exception on error preparing the request
+ */
+ protected function beforeSend(RequestInterface $request)
+ {
+ try {
+ $state = $request->setState(RequestInterface::STATE_TRANSFER);
+ if ($state == RequestInterface::STATE_TRANSFER) {
+ $this->addHandle($request);
+ } else {
+ // Requests might decide they don't need to be sent just before
+ // transfer (e.g. CachePlugin)
+ $this->remove($request);
+ if ($state == RequestInterface::STATE_COMPLETE) {
+ $this->successful[] = $request;
+ }
+ }
+ } catch (\Exception $e) {
+ // Queue the exception to be thrown when sent
+ $this->removeErroredRequest($request, $e);
+ }
+ }
+
+ private function addHandle(RequestInterface $request)
+ {
+ $handle = $this->createCurlHandle($request)->getHandle();
+ $this->checkCurlResult(
+ curl_multi_add_handle($this->multiHandle, $handle)
+ );
+ }
+
+ /**
+ * Create a curl handle for a request
+ *
+ * @param RequestInterface $request Request
+ *
+ * @return CurlHandle
+ */
+ protected function createCurlHandle(RequestInterface $request)
+ {
+ $wrapper = CurlHandle::factory($request);
+ $this->handles[$request] = $wrapper;
+ $this->resourceHash[(int) $wrapper->getHandle()] = $request;
+
+ return $wrapper;
+ }
+
+ /**
+ * Get the data from the multi handle
+ */
+ protected function perform()
+ {
+ $event = new Event(array('curl_multi' => $this));
+
+ while ($this->requests) {
+ // Notify each request as polling
+ $blocking = $total = 0;
+ foreach ($this->requests as $request) {
+ ++$total;
+ $event['request'] = $request;
+ $request->getEventDispatcher()->dispatch(self::POLLING_REQUEST, $event);
+ // The blocking variable just has to be non-falsey to block the loop
+ if ($request->getParams()->hasKey(self::BLOCKING)) {
+ ++$blocking;
+ }
+ }
+ if ($blocking == $total) {
+ // Sleep to prevent eating CPU because no requests are actually pending a select call
+ usleep(500);
+ } else {
+ $this->executeHandles();
+ }
+ }
+ }
+
+ /**
+ * Execute and select curl handles
+ */
+ private function executeHandles()
+ {
+ // The first curl_multi_select often times out no matter what, but is usually required for fast transfers
+ $selectTimeout = 0.001;
+ $active = false;
+ do {
+ while (($mrc = curl_multi_exec($this->multiHandle, $active)) == CURLM_CALL_MULTI_PERFORM);
+ $this->checkCurlResult($mrc);
+ $this->processMessages();
+ if ($active && curl_multi_select($this->multiHandle, $selectTimeout) === -1) {
+ // Perform a usleep if a select returns -1: https://bugs.php.net/bug.php?id=61141
+ usleep(150);
+ }
+ $selectTimeout = $this->selectTimeout;
+ } while ($active);
+ }
+
+ /**
+ * Process any received curl multi messages
+ */
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->multiHandle)) {
+ $request = $this->resourceHash[(int) $done['handle']];
+ try {
+ $this->processResponse($request, $this->handles[$request], $done);
+ $this->successful[] = $request;
+ } catch (\Exception $e) {
+ $this->removeErroredRequest($request, $e);
+ }
+ }
+ }
+
+ /**
+ * Remove a request that encountered an exception
+ *
+ * @param RequestInterface $request Request to remove
+ * @param \Exception $e Exception encountered
+ */
+ protected function removeErroredRequest(RequestInterface $request, \Exception $e = null)
+ {
+ $this->exceptions[] = array('request' => $request, 'exception' => $e);
+ $this->remove($request);
+ $this->dispatch(self::MULTI_EXCEPTION, array('exception' => $e, 'all_exceptions' => $this->exceptions));
+ }
+
+ /**
+ * Check for errors and fix headers of a request based on a curl response
+ *
+ * @param RequestInterface $request Request to process
+ * @param CurlHandle $handle Curl handle object
+ * @param array $curl Array returned from curl_multi_info_read
+ *
+ * @throws CurlException on Curl error
+ */
+ protected function processResponse(RequestInterface $request, CurlHandle $handle, array $curl)
+ {
+ // Set the transfer stats on the response
+ $handle->updateRequestFromTransfer($request);
+ // Check if a cURL exception occurred, and if so, notify things
+ $curlException = $this->isCurlException($request, $handle, $curl);
+
+ // Always remove completed curl handles. They can be added back again
+ // via events if needed (e.g. ExponentialBackoffPlugin)
+ $this->removeHandle($request);
+
+ if (!$curlException) {
+ if ($this->validateResponseWasSet($request)) {
+ $state = $request->setState(
+ RequestInterface::STATE_COMPLETE,
+ array('handle' => $handle)
+ );
+ // Only remove the request if it wasn't resent as a result of
+ // the state change
+ if ($state != RequestInterface::STATE_TRANSFER) {
+ $this->remove($request);
+ }
+ }
+ return;
+ }
+
+ // Set the state of the request to an error
+ $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException));
+ // Allow things to ignore the error if possible
+ if ($state != RequestInterface::STATE_TRANSFER) {
+ $this->remove($request);
+ }
+
+ // The error was not handled, so fail
+ if ($state == RequestInterface::STATE_ERROR) {
+ /** @var CurlException $curlException */
+ throw $curlException;
+ }
+ }
+
+ /**
+ * Remove a curl handle from the curl multi object
+ *
+ * @param RequestInterface $request Request that owns the handle
+ */
+ protected function removeHandle(RequestInterface $request)
+ {
+ if (isset($this->handles[$request])) {
+ $handle = $this->handles[$request];
+ curl_multi_remove_handle($this->multiHandle, $handle->getHandle());
+ unset($this->handles[$request]);
+ unset($this->resourceHash[(int) $handle->getHandle()]);
+ $handle->close();
+ }
+ }
+
+ /**
+ * Check if a cURL transfer resulted in what should be an exception
+ *
+ * @param RequestInterface $request Request to check
+ * @param CurlHandle $handle Curl handle object
+ * @param array $curl Array returned from curl_multi_info_read
+ *
+ * @return CurlException|bool
+ */
+ private function isCurlException(RequestInterface $request, CurlHandle $handle, array $curl)
+ {
+ if (CURLM_OK == $curl['result'] || CURLM_CALL_MULTI_PERFORM == $curl['result']) {
+ return false;
+ }
+
+ $handle->setErrorNo($curl['result']);
+ $e = new CurlException(sprintf('[curl] %s: %s [url] %s',
+ $handle->getErrorNo(), $handle->getError(), $handle->getUrl()));
+ $e->setCurlHandle($handle)
+ ->setRequest($request)
+ ->setCurlInfo($handle->getInfo())
+ ->setError($handle->getError(), $handle->getErrorNo());
+
+ return $e;
+ }
+
+ /**
+ * Throw an exception for a cURL multi response if needed
+ *
+ * @param int $code Curl response code
+ * @throws CurlException
+ */
+ private function checkCurlResult($code)
+ {
+ if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) {
+ throw new CurlException(isset($this->multiErrors[$code])
+ ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}"
+ : 'Unexpected cURL error: ' . $code
+ );
+ }
+ }
+
+ /**
+ * @link https://github.com/guzzle/guzzle/issues/710
+ */
+ private function validateResponseWasSet(RequestInterface $request)
+ {
+ if ($request->getResponse()) {
+ return true;
+ }
+
+ $body = $request instanceof EntityEnclosingRequestInterface
+ ? $request->getBody()
+ : null;
+
+ if (!$body) {
+ $rex = new RequestException(
+ 'No response was received for a request with no body. This'
+ . ' could mean that you are saturating your network.'
+ );
+ $rex->setRequest($request);
+ $this->removeErroredRequest($request, $rex);
+ } elseif (!$body->isSeekable() || !$body->seek(0)) {
+ // Nothing we can do with this. Sorry!
+ $rex = new RequestException(
+ 'The connection was unexpectedly closed. The request would'
+ . ' have been retried, but attempting to rewind the'
+ . ' request body failed.'
+ );
+ $rex->setRequest($request);
+ $this->removeErroredRequest($request, $rex);
+ } else {
+ $this->remove($request);
+ // Add the request back to the batch to retry automatically.
+ $this->requests[] = $request;
+ $this->addHandle($request);
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php
new file mode 100644
index 0000000..0ead757
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Common\HasDispatcherInterface;
+use Guzzle\Common\Exception\ExceptionCollection;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Interface for sending a pool of {@see RequestInterface} objects in parallel
+ */
+interface CurlMultiInterface extends \Countable, HasDispatcherInterface
+{
+ const POLLING_REQUEST = 'curl_multi.polling_request';
+ const ADD_REQUEST = 'curl_multi.add_request';
+ const REMOVE_REQUEST = 'curl_multi.remove_request';
+ const MULTI_EXCEPTION = 'curl_multi.exception';
+ const BLOCKING = 'curl_multi.blocking';
+
+ /**
+ * Add a request to the pool.
+ *
+ * @param RequestInterface $request Request to add
+ *
+ * @return CurlMultiInterface
+ */
+ public function add(RequestInterface $request);
+
+ /**
+ * Get an array of attached {@see RequestInterface} objects
+ *
+ * @return array
+ */
+ public function all();
+
+ /**
+ * Remove a request from the pool.
+ *
+ * @param RequestInterface $request Request to remove
+ *
+ * @return bool Returns true on success or false on failure
+ */
+ public function remove(RequestInterface $request);
+
+ /**
+ * Reset the state and remove any attached RequestInterface objects
+ *
+ * @param bool $hard Set to true to close and reopen any open multi handles
+ */
+ public function reset($hard = false);
+
+ /**
+ * Send a pool of {@see RequestInterface} requests.
+ *
+ * @throws ExceptionCollection if any requests threw exceptions during the transfer.
+ */
+ public function send();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiProxy.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiProxy.php
new file mode 100644
index 0000000..c5b80a7
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiProxy.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Proxies requests and connections to a pool of internal curl_multi handles. Each recursive call will add requests
+ * to the next available CurlMulti handle.
+ */
+class CurlMultiProxy extends AbstractHasDispatcher implements CurlMultiInterface
+{
+ protected $handles = array();
+ protected $groups = array();
+ protected $queued = array();
+ protected $maxHandles;
+ protected $selectTimeout;
+
+ /**
+ * @param int $maxHandles The maximum number of idle CurlMulti handles to allow to remain open
+ * @param float $selectTimeout timeout for curl_multi_select
+ */
+ public function __construct($maxHandles = 3, $selectTimeout = 1.0)
+ {
+ $this->maxHandles = $maxHandles;
+ $this->selectTimeout = $selectTimeout;
+ // You can get some weird "Too many open files" errors when sending a large amount of requests in parallel.
+ // These two statements autoload classes before a system runs out of file descriptors so that you can get back
+ // valuable error messages if you run out.
+ class_exists('Guzzle\Http\Message\Response');
+ class_exists('Guzzle\Http\Exception\CurlException');
+ }
+
+ public function add(RequestInterface $request)
+ {
+ $this->queued[] = $request;
+
+ return $this;
+ }
+
+ public function all()
+ {
+ $requests = $this->queued;
+ foreach ($this->handles as $handle) {
+ $requests = array_merge($requests, $handle->all());
+ }
+
+ return $requests;
+ }
+
+ public function remove(RequestInterface $request)
+ {
+ foreach ($this->queued as $i => $r) {
+ if ($request === $r) {
+ unset($this->queued[$i]);
+ return true;
+ }
+ }
+
+ foreach ($this->handles as $handle) {
+ if ($handle->remove($request)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function reset($hard = false)
+ {
+ $this->queued = array();
+ $this->groups = array();
+ foreach ($this->handles as $handle) {
+ $handle->reset();
+ }
+ if ($hard) {
+ $this->handles = array();
+ }
+
+ return $this;
+ }
+
+ public function send()
+ {
+ if ($this->queued) {
+ $group = $this->getAvailableHandle();
+ // Add this handle to a list of handles than is claimed
+ $this->groups[] = $group;
+ while ($request = array_shift($this->queued)) {
+ $group->add($request);
+ }
+ try {
+ $group->send();
+ array_pop($this->groups);
+ $this->cleanupHandles();
+ } catch (\Exception $e) {
+ // Remove the group and cleanup if an exception was encountered and no more requests in group
+ if (!$group->count()) {
+ array_pop($this->groups);
+ $this->cleanupHandles();
+ }
+ throw $e;
+ }
+ }
+ }
+
+ public function count()
+ {
+ return count($this->all());
+ }
+
+ /**
+ * Get an existing available CurlMulti handle or create a new one
+ *
+ * @return CurlMulti
+ */
+ protected function getAvailableHandle()
+ {
+ // Grab a handle that is not claimed
+ foreach ($this->handles as $h) {
+ if (!in_array($h, $this->groups, true)) {
+ return $h;
+ }
+ }
+
+ // All are claimed, so create one
+ $handle = new CurlMulti($this->selectTimeout);
+ $handle->setEventDispatcher($this->getEventDispatcher());
+ $this->handles[] = $handle;
+
+ return $handle;
+ }
+
+ /**
+ * Trims down unused CurlMulti handles to limit the number of open connections
+ */
+ protected function cleanupHandles()
+ {
+ if ($diff = max(0, count($this->handles) - $this->maxHandles)) {
+ for ($i = count($this->handles) - 1; $i > 0 && $diff > 0; $i--) {
+ if (!count($this->handles[$i])) {
+ unset($this->handles[$i]);
+ $diff--;
+ }
+ }
+ $this->handles = array_values($this->handles);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php
new file mode 100644
index 0000000..c3f99dd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+/**
+ * Class used for querying curl_version data
+ */
+class CurlVersion
+{
+ /** @var array curl_version() information */
+ protected $version;
+
+ /** @var CurlVersion */
+ protected static $instance;
+
+ /** @var string Default user agent */
+ protected $userAgent;
+
+ /**
+ * @return CurlVersion
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Get all of the curl_version() data
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ if (!$this->version) {
+ $this->version = curl_version();
+ }
+
+ return $this->version;
+ }
+
+ /**
+ * Get a specific type of curl information
+ *
+ * @param string $type Version information to retrieve. This value is one of:
+ * - version_number: cURL 24 bit version number
+ * - version: cURL version number, as a string
+ * - ssl_version_number: OpenSSL 24 bit version number
+ * - ssl_version: OpenSSL version number, as a string
+ * - libz_version: zlib version number, as a string
+ * - host: Information about the host where cURL was built
+ * - features: A bitmask of the CURL_VERSION_XXX constants
+ * - protocols: An array of protocols names supported by cURL
+ *
+ * @return string|float|bool if the $type is found, and false if not found
+ */
+ public function get($type)
+ {
+ $version = $this->getAll();
+
+ return isset($version[$type]) ? $version[$type] : false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php
new file mode 100644
index 0000000..5d1a0cd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Mediator between curl handles and request objects
+ */
+class RequestMediator
+{
+ /** @var RequestInterface */
+ protected $request;
+
+ /** @var bool Whether or not to emit read/write events */
+ protected $emitIo;
+
+ /**
+ * @param RequestInterface $request Request to mediate
+ * @param bool $emitIo Set to true to dispatch events on input and output
+ */
+ public function __construct(RequestInterface $request, $emitIo = false)
+ {
+ $this->request = $request;
+ $this->emitIo = $emitIo;
+ }
+
+ /**
+ * Receive a response header from curl
+ *
+ * @param resource $curl Curl handle
+ * @param string $header Received header
+ *
+ * @return int
+ */
+ public function receiveResponseHeader($curl, $header)
+ {
+ static $normalize = array("\r", "\n");
+ $length = strlen($header);
+ $header = str_replace($normalize, '', $header);
+
+ if (strpos($header, 'HTTP/') === 0) {
+
+ $startLine = explode(' ', $header, 3);
+ $code = $startLine[1];
+ $status = isset($startLine[2]) ? $startLine[2] : '';
+
+ // Only download the body of the response to the specified response
+ // body when a successful response is received.
+ if ($code >= 200 && $code < 300) {
+ $body = $this->request->getResponseBody();
+ } else {
+ $body = EntityBody::factory();
+ }
+
+ $response = new Response($code, null, $body);
+ $response->setStatus($code, $status);
+ $this->request->startResponse($response);
+
+ $this->request->dispatch('request.receive.status_line', array(
+ 'request' => $this,
+ 'line' => $header,
+ 'status_code' => $code,
+ 'reason_phrase' => $status
+ ));
+
+ } elseif ($pos = strpos($header, ':')) {
+ $this->request->getResponse()->addHeader(
+ trim(substr($header, 0, $pos)),
+ trim(substr($header, $pos + 1))
+ );
+ }
+
+ return $length;
+ }
+
+ /**
+ * Received a progress notification
+ *
+ * @param int $downloadSize Total download size
+ * @param int $downloaded Amount of bytes downloaded
+ * @param int $uploadSize Total upload size
+ * @param int $uploaded Amount of bytes uploaded
+ * @param resource $handle CurlHandle object
+ */
+ public function progress($downloadSize, $downloaded, $uploadSize, $uploaded, $handle = null)
+ {
+ $this->request->dispatch('curl.callback.progress', array(
+ 'request' => $this->request,
+ 'handle' => $handle,
+ 'download_size' => $downloadSize,
+ 'downloaded' => $downloaded,
+ 'upload_size' => $uploadSize,
+ 'uploaded' => $uploaded
+ ));
+ }
+
+ /**
+ * Write data to the response body of a request
+ *
+ * @param resource $curl Curl handle
+ * @param string $write Data that was received
+ *
+ * @return int
+ */
+ public function writeResponseBody($curl, $write)
+ {
+ if ($this->emitIo) {
+ $this->request->dispatch('curl.callback.write', array(
+ 'request' => $this->request,
+ 'write' => $write
+ ));
+ }
+
+ if ($response = $this->request->getResponse()) {
+ return $response->getBody()->write($write);
+ } else {
+ // Unexpected data received before response headers - abort transfer
+ return 0;
+ }
+ }
+
+ /**
+ * Read data from the request body and send it to curl
+ *
+ * @param resource $ch Curl handle
+ * @param resource $fd File descriptor
+ * @param int $length Amount of data to read
+ *
+ * @return string
+ */
+ public function readRequestBody($ch, $fd, $length)
+ {
+ if (!($body = $this->request->getBody())) {
+ return '';
+ }
+
+ $read = (string) $body->read($length);
+ if ($this->emitIo) {
+ $this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read));
+ }
+
+ return $read;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php b/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php
new file mode 100644
index 0000000..b60d170
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php
@@ -0,0 +1,201 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Version;
+use Guzzle\Stream\Stream;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Mimetypes;
+
+/**
+ * Entity body used with an HTTP request or response
+ */
+class EntityBody extends Stream implements EntityBodyInterface
+{
+ /** @var bool Content-Encoding of the entity body if known */
+ protected $contentEncoding = false;
+
+ /** @var callable Method to invoke for rewinding a stream */
+ protected $rewindFunction;
+
+ /**
+ * Create a new EntityBody based on the input type
+ *
+ * @param resource|string|EntityBody $resource Entity body data
+ * @param int $size Size of the data contained in the resource
+ *
+ * @return EntityBody
+ * @throws InvalidArgumentException if the $resource arg is not a resource or string
+ */
+ public static function factory($resource = '', $size = null)
+ {
+ if ($resource instanceof EntityBodyInterface) {
+ return $resource;
+ }
+
+ switch (gettype($resource)) {
+ case 'string':
+ return self::fromString($resource);
+ case 'resource':
+ return new static($resource, $size);
+ case 'object':
+ if (method_exists($resource, '__toString')) {
+ return self::fromString((string) $resource);
+ }
+ break;
+ case 'array':
+ return self::fromString(http_build_query($resource));
+ }
+
+ throw new InvalidArgumentException('Invalid resource type');
+ }
+
+ public function setRewindFunction($callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Must specify a callable');
+ }
+
+ $this->rewindFunction = $callable;
+
+ return $this;
+ }
+
+ public function rewind()
+ {
+ return $this->rewindFunction ? call_user_func($this->rewindFunction, $this) : parent::rewind();
+ }
+
+ /**
+ * Create a new EntityBody from a string
+ *
+ * @param string $string String of data
+ *
+ * @return EntityBody
+ */
+ public static function fromString($string)
+ {
+ $stream = fopen('php://temp', 'r+');
+ if ($string !== '') {
+ fwrite($stream, $string);
+ rewind($stream);
+ }
+
+ return new static($stream);
+ }
+
+ public function compress($filter = 'zlib.deflate')
+ {
+ $result = $this->handleCompression($filter);
+ $this->contentEncoding = $result ? $filter : false;
+
+ return $result;
+ }
+
+ public function uncompress($filter = 'zlib.inflate')
+ {
+ $offsetStart = 0;
+
+ // When inflating gzipped data, the first 10 bytes must be stripped
+ // if a gzip header is present
+ if ($filter == 'zlib.inflate') {
+ // @codeCoverageIgnoreStart
+ if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) {
+ return false;
+ }
+ // @codeCoverageIgnoreEnd
+ if (stream_get_contents($this->stream, 3, 0) === "\x1f\x8b\x08") {
+ $offsetStart = 10;
+ }
+ }
+
+ $this->contentEncoding = false;
+
+ return $this->handleCompression($filter, $offsetStart);
+ }
+
+ public function getContentLength()
+ {
+ return $this->getSize();
+ }
+
+ public function getContentType()
+ {
+ return $this->getUri() ? Mimetypes::getInstance()->fromFilename($this->getUri()) : null;
+ }
+
+ public function getContentMd5($rawOutput = false, $base64Encode = false)
+ {
+ if ($hash = self::getHash($this, 'md5', $rawOutput)) {
+ return $hash && $base64Encode ? base64_encode($hash) : $hash;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Calculate the MD5 hash of an entity body
+ *
+ * @param EntityBodyInterface $body Entity body to calculate the hash for
+ * @param bool $rawOutput Whether or not to use raw output
+ * @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true)
+ *
+ * @return bool|string Returns an MD5 string on success or FALSE on failure
+ * @deprecated This will be deprecated soon
+ * @codeCoverageIgnore
+ */
+ public static function calculateMd5(EntityBodyInterface $body, $rawOutput = false, $base64Encode = false)
+ {
+ Version::warn(__CLASS__ . ' is deprecated. Use getContentMd5()');
+ return $body->getContentMd5($rawOutput, $base64Encode);
+ }
+
+ public function setStreamFilterContentEncoding($streamFilterContentEncoding)
+ {
+ $this->contentEncoding = $streamFilterContentEncoding;
+
+ return $this;
+ }
+
+ public function getContentEncoding()
+ {
+ return strtr($this->contentEncoding, array(
+ 'zlib.deflate' => 'gzip',
+ 'bzip2.compress' => 'compress'
+ )) ?: false;
+ }
+
+ protected function handleCompression($filter, $offsetStart = 0)
+ {
+ // @codeCoverageIgnoreStart
+ if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) {
+ return false;
+ }
+ // @codeCoverageIgnoreEnd
+
+ $handle = fopen('php://temp', 'r+');
+ $filter = @stream_filter_append($handle, $filter, STREAM_FILTER_WRITE);
+ if (!$filter) {
+ return false;
+ }
+
+ // Seek to the offset start if possible
+ $this->seek($offsetStart);
+ while ($data = fread($this->stream, 8096)) {
+ fwrite($handle, $data);
+ }
+
+ fclose($this->stream);
+ $this->stream = $handle;
+ stream_filter_remove($filter);
+ $stat = fstat($this->stream);
+ $this->size = $stat['size'];
+ $this->rebuildCache();
+ $this->seek(0);
+
+ // Remove any existing rewind function as the underlying stream has been replaced
+ $this->rewindFunction = null;
+
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php
new file mode 100644
index 0000000..e640f57
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Stream\StreamInterface;
+
+/**
+ * Entity body used with an HTTP request or response
+ */
+interface EntityBodyInterface extends StreamInterface
+{
+ /**
+ * Specify a custom callback used to rewind a non-seekable stream. This can be useful entity enclosing requests
+ * that are redirected.
+ *
+ * @param mixed $callable Callable to invoke to rewind a non-seekable stream. The callback must accept an
+ * EntityBodyInterface object, perform the rewind if possible, and return a boolean
+ * representing whether or not the rewind was successful.
+ * @return self
+ */
+ public function setRewindFunction($callable);
+
+ /**
+ * If the stream is readable, compress the data in the stream using deflate compression. The uncompressed stream is
+ * then closed, and the compressed stream then becomes the wrapped stream.
+ *
+ * @param string $filter Compression filter
+ *
+ * @return bool Returns TRUE on success or FALSE on failure
+ */
+ public function compress($filter = 'zlib.deflate');
+
+ /**
+ * Decompress a deflated string. Once uncompressed, the uncompressed string is then used as the wrapped stream.
+ *
+ * @param string $filter De-compression filter
+ *
+ * @return bool Returns TRUE on success or FALSE on failure
+ */
+ public function uncompress($filter = 'zlib.inflate');
+
+ /**
+ * Get the Content-Length of the entity body if possible (alias of getSize)
+ *
+ * @return int|bool Returns the Content-Length or false on failure
+ */
+ public function getContentLength();
+
+ /**
+ * Guess the Content-Type of a local stream
+ *
+ * @return string|null
+ * @see http://www.php.net/manual/en/function.finfo-open.php
+ */
+ public function getContentType();
+
+ /**
+ * Get an MD5 checksum of the stream's contents
+ *
+ * @param bool $rawOutput Whether or not to use raw output
+ * @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true)
+ *
+ * @return bool|string Returns an MD5 string on success or FALSE on failure
+ */
+ public function getContentMd5($rawOutput = false, $base64Encode = false);
+
+ /**
+ * Get the Content-Encoding of the EntityBody
+ *
+ * @return bool|string
+ */
+ public function getContentEncoding();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/BadResponseException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/BadResponseException.php
new file mode 100644
index 0000000..0ed0b47
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/BadResponseException.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Http request exception thrown when a bad response is received
+ */
+class BadResponseException extends RequestException
+{
+ /** @var Response */
+ private $response;
+
+ /**
+ * Factory method to create a new response exception based on the response code.
+ *
+ * @param RequestInterface $request Request
+ * @param Response $response Response received
+ *
+ * @return BadResponseException
+ */
+ public static function factory(RequestInterface $request, Response $response)
+ {
+ if ($response->isClientError()) {
+ $label = 'Client error response';
+ $class = __NAMESPACE__ . '\\ClientErrorResponseException';
+ } elseif ($response->isServerError()) {
+ $label = 'Server error response';
+ $class = __NAMESPACE__ . '\\ServerErrorResponseException';
+ } else {
+ $label = 'Unsuccessful response';
+ $class = __CLASS__;
+ }
+
+ $message = $label . PHP_EOL . implode(PHP_EOL, array(
+ '[status code] ' . $response->getStatusCode(),
+ '[reason phrase] ' . $response->getReasonPhrase(),
+ '[url] ' . $request->getUrl(),
+ ));
+
+ $e = new $class($message);
+ $e->setResponse($response);
+ $e->setRequest($request);
+
+ return $e;
+ }
+
+ /**
+ * Set the response that caused the exception
+ *
+ * @param Response $response Response to set
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ }
+
+ /**
+ * Get the response that caused the exception
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php
new file mode 100644
index 0000000..04d7ddc
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+/**
+ * Exception when a client error is encountered (4xx codes)
+ */
+class ClientErrorResponseException extends BadResponseException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CouldNotRewindStreamException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CouldNotRewindStreamException.php
new file mode 100644
index 0000000..63e4ec7
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CouldNotRewindStreamException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class CouldNotRewindStreamException extends RuntimeException implements HttpException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CurlException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CurlException.php
new file mode 100644
index 0000000..a6a744a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/CurlException.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Http\Curl\CurlHandle;
+
+/**
+ * cURL request exception
+ */
+class CurlException extends RequestException
+{
+ private $curlError;
+ private $curlErrorNo;
+ private $handle;
+ private $curlInfo = array();
+
+ /**
+ * Set the cURL error message
+ *
+ * @param string $error Curl error
+ * @param int $number Curl error number
+ *
+ * @return self
+ */
+ public function setError($error, $number)
+ {
+ $this->curlError = $error;
+ $this->curlErrorNo = $number;
+
+ return $this;
+ }
+
+ /**
+ * Set the associated curl handle
+ *
+ * @param CurlHandle $handle Curl handle
+ *
+ * @return self
+ */
+ public function setCurlHandle(CurlHandle $handle)
+ {
+ $this->handle = $handle;
+
+ return $this;
+ }
+
+ /**
+ * Get the associated cURL handle
+ *
+ * @return CurlHandle|null
+ */
+ public function getCurlHandle()
+ {
+ return $this->handle;
+ }
+
+ /**
+ * Get the associated cURL error message
+ *
+ * @return string|null
+ */
+ public function getError()
+ {
+ return $this->curlError;
+ }
+
+ /**
+ * Get the associated cURL error number
+ *
+ * @return int|null
+ */
+ public function getErrorNo()
+ {
+ return $this->curlErrorNo;
+ }
+
+ /**
+ * Returns curl information about the transfer
+ *
+ * @return array
+ */
+ public function getCurlInfo()
+ {
+ return $this->curlInfo;
+ }
+
+ /**
+ * Set curl transfer information
+ *
+ * @param array $info Array of curl transfer information
+ *
+ * @return self
+ * @link http://php.net/manual/en/function.curl-getinfo.php
+ */
+ public function setCurlInfo(array $info)
+ {
+ $this->curlInfo = $info;
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php
new file mode 100644
index 0000000..ee87295
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Common\Exception\GuzzleException;
+
+/**
+ * Http exception interface
+ */
+interface HttpException extends GuzzleException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/MultiTransferException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/MultiTransferException.php
new file mode 100644
index 0000000..91e384d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/MultiTransferException.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Common\Exception\ExceptionCollection;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Exception encountered during a multi transfer
+ */
+class MultiTransferException extends ExceptionCollection
+{
+ protected $successfulRequests = array();
+ protected $failedRequests = array();
+ protected $exceptionForRequest = array();
+
+ /**
+ * Get all of the requests in the transfer
+ *
+ * @return array
+ */
+ public function getAllRequests()
+ {
+ return array_merge($this->successfulRequests, $this->failedRequests);
+ }
+
+ /**
+ * Add to the array of successful requests
+ *
+ * @param RequestInterface $request Successful request
+ *
+ * @return self
+ */
+ public function addSuccessfulRequest(RequestInterface $request)
+ {
+ $this->successfulRequests[] = $request;
+
+ return $this;
+ }
+
+ /**
+ * Add to the array of failed requests
+ *
+ * @param RequestInterface $request Failed request
+ *
+ * @return self
+ */
+ public function addFailedRequest(RequestInterface $request)
+ {
+ $this->failedRequests[] = $request;
+
+ return $this;
+ }
+
+ /**
+ * Add to the array of failed requests and associate with exceptions
+ *
+ * @param RequestInterface $request Failed request
+ * @param \Exception $exception Exception to add and associate with
+ *
+ * @return self
+ */
+ public function addFailedRequestWithException(RequestInterface $request, \Exception $exception)
+ {
+ $this->add($exception)
+ ->addFailedRequest($request)
+ ->exceptionForRequest[spl_object_hash($request)] = $exception;
+
+ return $this;
+ }
+
+ /**
+ * Get the Exception that caused the given $request to fail
+ *
+ * @param RequestInterface $request Failed command
+ *
+ * @return \Exception|null
+ */
+ public function getExceptionForFailedRequest(RequestInterface $request)
+ {
+ $oid = spl_object_hash($request);
+
+ return isset($this->exceptionForRequest[$oid]) ? $this->exceptionForRequest[$oid] : null;
+ }
+
+ /**
+ * Set all of the successful requests
+ *
+ * @param array Array of requests
+ *
+ * @return self
+ */
+ public function setSuccessfulRequests(array $requests)
+ {
+ $this->successfulRequests = $requests;
+
+ return $this;
+ }
+
+ /**
+ * Set all of the failed requests
+ *
+ * @param array Array of requests
+ *
+ * @return self
+ */
+ public function setFailedRequests(array $requests)
+ {
+ $this->failedRequests = $requests;
+
+ return $this;
+ }
+
+ /**
+ * Get an array of successful requests sent in the multi transfer
+ *
+ * @return array
+ */
+ public function getSuccessfulRequests()
+ {
+ return $this->successfulRequests;
+ }
+
+ /**
+ * Get an array of failed requests sent in the multi transfer
+ *
+ * @return array
+ */
+ public function getFailedRequests()
+ {
+ return $this->failedRequests;
+ }
+
+ /**
+ * Check if the exception object contains a request
+ *
+ * @param RequestInterface $request Request to check
+ *
+ * @return bool
+ */
+ public function containsRequest(RequestInterface $request)
+ {
+ return in_array($request, $this->failedRequests, true) || in_array($request, $this->successfulRequests, true);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php
new file mode 100644
index 0000000..274df2c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Http request exception
+ */
+class RequestException extends RuntimeException implements HttpException
+{
+ /** @var RequestInterface */
+ protected $request;
+
+ /**
+ * Set the request that caused the exception
+ *
+ * @param RequestInterface $request Request to set
+ *
+ * @return RequestException
+ */
+ public function setRequest(RequestInterface $request)
+ {
+ $this->request = $request;
+
+ return $this;
+ }
+
+ /**
+ * Get the request that caused the exception
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php
new file mode 100644
index 0000000..f0f7cfe
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+/**
+ * Exception when a server error is encountered (5xx codes)
+ */
+class ServerErrorResponseException extends BadResponseException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/TooManyRedirectsException.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/TooManyRedirectsException.php
new file mode 100644
index 0000000..2aa43d1
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/TooManyRedirectsException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Http\Exception;
+
+class TooManyRedirectsException extends BadResponseException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/IoEmittingEntityBody.php b/vendor/guzzle/guzzle/src/Guzzle/Http/IoEmittingEntityBody.php
new file mode 100644
index 0000000..4cc17a8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/IoEmittingEntityBody.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\HasDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * EntityBody decorator that emits events for read and write methods
+ */
+class IoEmittingEntityBody extends AbstractEntityBodyDecorator implements HasDispatcherInterface
+{
+ /** @var EventDispatcherInterface */
+ protected $eventDispatcher;
+
+ public static function getAllEvents()
+ {
+ return array('body.read', 'body.write');
+ }
+
+ /**
+ * {@inheritdoc}
+ * @codeCoverageIgnore
+ */
+ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+
+ return $this;
+ }
+
+ public function getEventDispatcher()
+ {
+ if (!$this->eventDispatcher) {
+ $this->eventDispatcher = new EventDispatcher();
+ }
+
+ return $this->eventDispatcher;
+ }
+
+ public function dispatch($eventName, array $context = array())
+ {
+ return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @codeCoverageIgnore
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->getEventDispatcher()->addSubscriber($subscriber);
+
+ return $this;
+ }
+
+ public function read($length)
+ {
+ $event = array(
+ 'body' => $this,
+ 'length' => $length,
+ 'read' => $this->body->read($length)
+ );
+ $this->dispatch('body.read', $event);
+
+ return $event['read'];
+ }
+
+ public function write($string)
+ {
+ $event = array(
+ 'body' => $this,
+ 'write' => $string,
+ 'result' => $this->body->write($string)
+ );
+ $this->dispatch('body.write', $event);
+
+ return $event['result'];
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php
new file mode 100644
index 0000000..0d066ff
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Version;
+use Guzzle\Common\Collection;
+use Guzzle\Http\Message\Header\HeaderCollection;
+use Guzzle\Http\Message\Header\HeaderFactory;
+use Guzzle\Http\Message\Header\HeaderFactoryInterface;
+use Guzzle\Http\Message\Header\HeaderInterface;
+
+/**
+ * Abstract HTTP request/response message
+ */
+abstract class AbstractMessage implements MessageInterface
+{
+ /** @var array HTTP header collection */
+ protected $headers;
+
+ /** @var HeaderFactoryInterface $headerFactory */
+ protected $headerFactory;
+
+ /** @var Collection Custom message parameters that are extendable by plugins */
+ protected $params;
+
+ /** @var string Message protocol */
+ protected $protocol = 'HTTP';
+
+ /** @var string HTTP protocol version of the message */
+ protected $protocolVersion = '1.1';
+
+ public function __construct()
+ {
+ $this->params = new Collection();
+ $this->headerFactory = new HeaderFactory();
+ $this->headers = new HeaderCollection();
+ }
+
+ /**
+ * Set the header factory to use to create headers
+ *
+ * @param HeaderFactoryInterface $factory
+ *
+ * @return self
+ */
+ public function setHeaderFactory(HeaderFactoryInterface $factory)
+ {
+ $this->headerFactory = $factory;
+
+ return $this;
+ }
+
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ public function addHeader($header, $value)
+ {
+ if (isset($this->headers[$header])) {
+ $this->headers[$header]->add($value);
+ } elseif ($value instanceof HeaderInterface) {
+ $this->headers[$header] = $value;
+ } else {
+ $this->headers[$header] = $this->headerFactory->createHeader($header, $value);
+ }
+
+ return $this;
+ }
+
+ public function addHeaders(array $headers)
+ {
+ foreach ($headers as $key => $value) {
+ $this->addHeader($key, $value);
+ }
+
+ return $this;
+ }
+
+ public function getHeader($header)
+ {
+ return $this->headers[$header];
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function getHeaderLines()
+ {
+ $headers = array();
+ foreach ($this->headers as $value) {
+ $headers[] = $value->getName() . ': ' . $value;
+ }
+
+ return $headers;
+ }
+
+ public function setHeader($header, $value)
+ {
+ unset($this->headers[$header]);
+ $this->addHeader($header, $value);
+
+ return $this;
+ }
+
+ public function setHeaders(array $headers)
+ {
+ $this->headers->clear();
+ foreach ($headers as $key => $value) {
+ $this->addHeader($key, $value);
+ }
+
+ return $this;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headers[$header]);
+ }
+
+ public function removeHeader($header)
+ {
+ unset($this->headers[$header]);
+
+ return $this;
+ }
+
+ /**
+ * @deprecated Use $message->getHeader()->parseParams()
+ * @codeCoverageIgnore
+ */
+ public function getTokenizedHeader($header, $token = ';')
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader()->parseParams()');
+ if ($this->hasHeader($header)) {
+ $data = new Collection();
+ foreach ($this->getHeader($header)->parseParams() as $values) {
+ foreach ($values as $key => $value) {
+ if ($value === '') {
+ $data->set($data->count(), $key);
+ } else {
+ $data->add($key, $value);
+ }
+ }
+ }
+ return $data;
+ }
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function setTokenizedHeader($header, $data, $token = ';')
+ {
+ Version::warn(__METHOD__ . ' is deprecated.');
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function getCacheControlDirective($directive)
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->getDirective()');
+ if (!($header = $this->getHeader('Cache-Control'))) {
+ return null;
+ }
+
+ return $header->getDirective($directive);
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function hasCacheControlDirective($directive)
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->hasDirective()');
+ if ($header = $this->getHeader('Cache-Control')) {
+ return $header->hasDirective($directive);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function addCacheControlDirective($directive, $value = true)
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->addDirective()');
+ if (!($header = $this->getHeader('Cache-Control'))) {
+ $this->addHeader('Cache-Control', '');
+ $header = $this->getHeader('Cache-Control');
+ }
+
+ $header->addDirective($directive, $value);
+
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function removeCacheControlDirective($directive)
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->removeDirective()');
+ if ($header = $this->getHeader('Cache-Control')) {
+ $header->removeDirective($directive);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php
new file mode 100644
index 0000000..212850a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\QueryString;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Http\Exception\RequestException;
+
+/**
+ * HTTP request that sends an entity-body in the request message (POST, PUT, PATCH, DELETE)
+ */
+class EntityEnclosingRequest extends Request implements EntityEnclosingRequestInterface
+{
+ /** @var int When the size of the body is greater than 1MB, then send Expect: 100-Continue */
+ protected $expectCutoff = 1048576;
+
+ /** @var EntityBodyInterface $body Body of the request */
+ protected $body;
+
+ /** @var QueryString POST fields to use in the EntityBody */
+ protected $postFields;
+
+ /** @var array POST files to send with the request */
+ protected $postFiles = array();
+
+ public function __construct($method, $url, $headers = array())
+ {
+ $this->postFields = new QueryString();
+ parent::__construct($method, $url, $headers);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ // Only attempt to include the POST data if it's only fields
+ if (count($this->postFields) && empty($this->postFiles)) {
+ return parent::__toString() . (string) $this->postFields;
+ }
+
+ return parent::__toString() . $this->body;
+ }
+
+ public function setState($state, array $context = array())
+ {
+ parent::setState($state, $context);
+ if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) {
+ $this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding');
+ }
+
+ return $this->state;
+ }
+
+ public function setBody($body, $contentType = null)
+ {
+ $this->body = EntityBody::factory($body);
+
+ // Auto detect the Content-Type from the path of the request if possible
+ if ($contentType === null && !$this->hasHeader('Content-Type')) {
+ $contentType = $this->body->getContentType();
+ }
+
+ if ($contentType) {
+ $this->setHeader('Content-Type', $contentType);
+ }
+
+ // Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects.
+ if (!$this->body->isSeekable() && $this->expectCutoff !== false) {
+ $this->setHeader('Expect', '100-Continue');
+ }
+
+ // Set the Content-Length header if it can be determined
+ $size = $this->body->getContentLength();
+ if ($size !== null && $size !== false) {
+ $this->setHeader('Content-Length', $size);
+ if ($size > $this->expectCutoff) {
+ $this->setHeader('Expect', '100-Continue');
+ }
+ } elseif (!$this->hasHeader('Content-Length')) {
+ if ('1.1' == $this->protocolVersion) {
+ $this->setHeader('Transfer-Encoding', 'chunked');
+ } else {
+ throw new RequestException(
+ 'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0'
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header.
+ *
+ * @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data)
+ *
+ * @return self
+ */
+ public function setExpectHeaderCutoff($size)
+ {
+ $this->expectCutoff = $size;
+ if ($size === false || !$this->body) {
+ $this->removeHeader('Expect');
+ } elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) {
+ $this->setHeader('Expect', '100-Continue');
+ }
+
+ return $this;
+ }
+
+ public function configureRedirects($strict = false, $maxRedirects = 5)
+ {
+ $this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict);
+ if ($maxRedirects == 0) {
+ $this->getParams()->set(RedirectPlugin::DISABLE, true);
+ } else {
+ $this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects);
+ }
+
+ return $this;
+ }
+
+ public function getPostField($field)
+ {
+ return $this->postFields->get($field);
+ }
+
+ public function getPostFields()
+ {
+ return $this->postFields;
+ }
+
+ public function setPostField($key, $value)
+ {
+ $this->postFields->set($key, $value);
+ $this->processPostFields();
+
+ return $this;
+ }
+
+ public function addPostFields($fields)
+ {
+ $this->postFields->merge($fields);
+ $this->processPostFields();
+
+ return $this;
+ }
+
+ public function removePostField($field)
+ {
+ $this->postFields->remove($field);
+ $this->processPostFields();
+
+ return $this;
+ }
+
+ public function getPostFiles()
+ {
+ return $this->postFiles;
+ }
+
+ public function getPostFile($fieldName)
+ {
+ return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null;
+ }
+
+ public function removePostFile($fieldName)
+ {
+ unset($this->postFiles[$fieldName]);
+ $this->processPostFields();
+
+ return $this;
+ }
+
+ public function addPostFile($field, $filename = null, $contentType = null, $postname = null)
+ {
+ $data = null;
+
+ if ($field instanceof PostFileInterface) {
+ $data = $field;
+ } elseif (is_array($filename)) {
+ // Allow multiple values to be set in a single key
+ foreach ($filename as $file) {
+ $this->addPostFile($field, $file, $contentType);
+ }
+ return $this;
+ } elseif (!is_string($filename)) {
+ throw new RequestException('The path to a file must be a string');
+ } elseif (!empty($filename)) {
+ // Adding an empty file will cause cURL to error out
+ $data = new PostFile($field, $filename, $contentType, $postname);
+ }
+
+ if ($data) {
+ if (!isset($this->postFiles[$data->getFieldName()])) {
+ $this->postFiles[$data->getFieldName()] = array($data);
+ } else {
+ $this->postFiles[$data->getFieldName()][] = $data;
+ }
+ $this->processPostFields();
+ }
+
+ return $this;
+ }
+
+ public function addPostFiles(array $files)
+ {
+ foreach ($files as $key => $file) {
+ if ($file instanceof PostFileInterface) {
+ $this->addPostFile($file, null, null, false);
+ } elseif (is_string($file)) {
+ // Convert non-associative array keys into 'file'
+ if (is_numeric($key)) {
+ $key = 'file';
+ }
+ $this->addPostFile($key, $file, null, false);
+ } else {
+ throw new RequestException('File must be a string or instance of PostFileInterface');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Determine what type of request should be sent based on post fields
+ */
+ protected function processPostFields()
+ {
+ if (!$this->postFiles) {
+ $this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED);
+ } else {
+ $this->setHeader('Content-Type', self::MULTIPART);
+ if ($this->expectCutoff !== false) {
+ $this->setHeader('Expect', '100-Continue');
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php
new file mode 100644
index 0000000..49ad459
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\QueryString;
+
+/**
+ * HTTP request that sends an entity-body in the request message (POST, PUT)
+ */
+interface EntityEnclosingRequestInterface extends RequestInterface
+{
+ const URL_ENCODED = 'application/x-www-form-urlencoded; charset=utf-8';
+ const MULTIPART = 'multipart/form-data';
+
+ /**
+ * Set the body of the request
+ *
+ * @param string|resource|EntityBodyInterface $body Body to use in the entity body of the request
+ * @param string $contentType Content-Type to set. Leave null to use an existing
+ * Content-Type or to guess the Content-Type
+ * @return self
+ * @throws RequestException if the protocol is < 1.1 and Content-Length can not be determined
+ */
+ public function setBody($body, $contentType = null);
+
+ /**
+ * Get the body of the request if set
+ *
+ * @return EntityBodyInterface|null
+ */
+ public function getBody();
+
+ /**
+ * Get a POST field from the request
+ *
+ * @param string $field Field to retrieve
+ *
+ * @return mixed|null
+ */
+ public function getPostField($field);
+
+ /**
+ * Get the post fields that will be used in the request
+ *
+ * @return QueryString
+ */
+ public function getPostFields();
+
+ /**
+ * Set a POST field value
+ *
+ * @param string $key Key to set
+ * @param string $value Value to set
+ *
+ * @return self
+ */
+ public function setPostField($key, $value);
+
+ /**
+ * Add POST fields to use in the request
+ *
+ * @param QueryString|array $fields POST fields
+ *
+ * @return self
+ */
+ public function addPostFields($fields);
+
+ /**
+ * Remove a POST field or file by name
+ *
+ * @param string $field Name of the POST field or file to remove
+ *
+ * @return self
+ */
+ public function removePostField($field);
+
+ /**
+ * Returns an associative array of POST field names to PostFileInterface objects
+ *
+ * @return array
+ */
+ public function getPostFiles();
+
+ /**
+ * Get a POST file from the request
+ *
+ * @param string $fieldName POST fields to retrieve
+ *
+ * @return array|null Returns an array wrapping an array of PostFileInterface objects
+ */
+ public function getPostFile($fieldName);
+
+ /**
+ * Remove a POST file from the request
+ *
+ * @param string $fieldName POST file field name to remove
+ *
+ * @return self
+ */
+ public function removePostFile($fieldName);
+
+ /**
+ * Add a POST file to the upload
+ *
+ * @param string $field POST field to use (e.g. file). Used to reference content from the server.
+ * @param string $filename Full path to the file. Do not include the @ symbol.
+ * @param string $contentType Optional Content-Type to add to the Content-Disposition.
+ * Default behavior is to guess. Set to false to not specify.
+ * @param string $postname The name of the file, when posted. (e.g. rename the file)
+ * @return self
+ */
+ public function addPostFile($field, $filename = null, $contentType = null, $postname = null);
+
+ /**
+ * Add POST files to use in the upload
+ *
+ * @param array $files An array of POST fields => filenames where filename can be a string or PostFileInterface
+ *
+ * @return self
+ */
+ public function addPostFiles(array $files);
+
+ /**
+ * Configure how redirects are handled for the request
+ *
+ * @param bool $strict Set to true to follow strict RFC compliance when redirecting POST requests. Most
+ * browsers with follow a 301-302 redirect for a POST request with a GET request. This is
+ * the default behavior of Guzzle. Enable strict redirects to redirect these responses
+ * with a POST rather than a GET request.
+ * @param int $maxRedirects Specify the maximum number of allowed redirects. Set to 0 to disable redirects.
+ *
+ * @return self
+ */
+ public function configureRedirects($strict = false, $maxRedirects = 5);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php
new file mode 100644
index 0000000..50597b2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Version;
+use Guzzle\Http\Message\Header\HeaderInterface;
+
+/**
+ * Represents a header and all of the values stored by that header
+ */
+class Header implements HeaderInterface
+{
+ protected $values = array();
+ protected $header;
+ protected $glue;
+
+ /**
+ * @param string $header Name of the header
+ * @param array|string $values Values of the header as an array or a scalar
+ * @param string $glue Glue used to combine multiple values into a string
+ */
+ public function __construct($header, $values = array(), $glue = ',')
+ {
+ $this->header = trim($header);
+ $this->glue = $glue;
+
+ foreach ((array) $values as $value) {
+ foreach ((array) $value as $v) {
+ $this->values[] = $v;
+ }
+ }
+ }
+
+ public function __toString()
+ {
+ return implode($this->glue . ' ', $this->toArray());
+ }
+
+ public function add($value)
+ {
+ $this->values[] = $value;
+
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->header;
+ }
+
+ public function setName($name)
+ {
+ $this->header = $name;
+
+ return $this;
+ }
+
+ public function setGlue($glue)
+ {
+ $this->glue = $glue;
+
+ return $this;
+ }
+
+ public function getGlue()
+ {
+ return $this->glue;
+ }
+
+ /**
+ * Normalize the header to be a single header with an array of values.
+ *
+ * If any values of the header contains the glue string value (e.g. ","), then the value will be exploded into
+ * multiple entries in the header.
+ *
+ * @return self
+ */
+ public function normalize()
+ {
+ $values = $this->toArray();
+
+ for ($i = 0, $total = count($values); $i < $total; $i++) {
+ if (strpos($values[$i], $this->glue) !== false) {
+ // Explode on glue when the glue is not inside of a comma
+ foreach (preg_split('/' . preg_quote($this->glue) . '(?=([^"]*"[^"]*")*[^"]*$)/', $values[$i]) as $v) {
+ $values[] = trim($v);
+ }
+ unset($values[$i]);
+ }
+ }
+
+ $this->values = array_values($values);
+
+ return $this;
+ }
+
+ public function hasValue($searchValue)
+ {
+ return in_array($searchValue, $this->toArray());
+ }
+
+ public function removeValue($searchValue)
+ {
+ $this->values = array_values(array_filter($this->values, function ($value) use ($searchValue) {
+ return $value != $searchValue;
+ }));
+
+ return $this;
+ }
+
+ public function toArray()
+ {
+ return $this->values;
+ }
+
+ public function count()
+ {
+ return count($this->toArray());
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->toArray());
+ }
+
+ public function parseParams()
+ {
+ $params = $matches = array();
+ $callback = array($this, 'trimHeader');
+
+ // Normalize the header into a single array and iterate over all values
+ foreach ($this->normalize()->toArray() as $val) {
+ $part = array();
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (!preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ continue;
+ }
+ $pieces = array_map($callback, $matches[0]);
+ $part[$pieces[0]] = isset($pieces[1]) ? $pieces[1] : '';
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function hasExactHeader($header)
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ return $this->header == $header;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function raw()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use toArray()');
+ return $this->toArray();
+ }
+
+ /**
+ * Trim a header by removing excess spaces and wrapping quotes
+ *
+ * @param $str
+ *
+ * @return string
+ */
+ protected function trimHeader($str)
+ {
+ static $trimmed = "\"' \n\t";
+
+ return trim($str, $trimmed);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php
new file mode 100644
index 0000000..77789e5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+use Guzzle\Http\Message\Header;
+
+/**
+ * Provides helpful functionality for Cache-Control headers
+ */
+class CacheControl extends Header
+{
+ /** @var array */
+ protected $directives;
+
+ public function add($value)
+ {
+ parent::add($value);
+ $this->directives = null;
+ }
+
+ public function removeValue($searchValue)
+ {
+ parent::removeValue($searchValue);
+ $this->directives = null;
+ }
+
+ /**
+ * Check if a specific cache control directive exists
+ *
+ * @param string $param Directive to retrieve
+ *
+ * @return bool
+ */
+ public function hasDirective($param)
+ {
+ $directives = $this->getDirectives();
+
+ return isset($directives[$param]);
+ }
+
+ /**
+ * Get a specific cache control directive
+ *
+ * @param string $param Directive to retrieve
+ *
+ * @return string|bool|null
+ */
+ public function getDirective($param)
+ {
+ $directives = $this->getDirectives();
+
+ return isset($directives[$param]) ? $directives[$param] : null;
+ }
+
+ /**
+ * Add a cache control directive
+ *
+ * @param string $param Directive to add
+ * @param string $value Value to set
+ *
+ * @return self
+ */
+ public function addDirective($param, $value)
+ {
+ $directives = $this->getDirectives();
+ $directives[$param] = $value;
+ $this->updateFromDirectives($directives);
+
+ return $this;
+ }
+
+ /**
+ * Remove a cache control directive by name
+ *
+ * @param string $param Directive to remove
+ *
+ * @return self
+ */
+ public function removeDirective($param)
+ {
+ $directives = $this->getDirectives();
+ unset($directives[$param]);
+ $this->updateFromDirectives($directives);
+
+ return $this;
+ }
+
+ /**
+ * Get an associative array of cache control directives
+ *
+ * @return array
+ */
+ public function getDirectives()
+ {
+ if ($this->directives === null) {
+ $this->directives = array();
+ foreach ($this->parseParams() as $collection) {
+ foreach ($collection as $key => $value) {
+ $this->directives[$key] = $value === '' ? true : $value;
+ }
+ }
+ }
+
+ return $this->directives;
+ }
+
+ /**
+ * Updates the header value based on the parsed directives
+ *
+ * @param array $directives Array of cache control directives
+ */
+ protected function updateFromDirectives(array $directives)
+ {
+ $this->directives = $directives;
+ $this->values = array();
+
+ foreach ($directives as $key => $value) {
+ $this->values[] = $value === true ? $key : "{$key}={$value}";
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php
new file mode 100644
index 0000000..8c7f6ae
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * Provides a case-insensitive collection of headers
+ */
+class HeaderCollection implements \IteratorAggregate, \Countable, \ArrayAccess, ToArrayInterface
+{
+ /** @var array */
+ protected $headers;
+
+ public function __construct($headers = array())
+ {
+ $this->headers = $headers;
+ }
+
+ public function __clone()
+ {
+ foreach ($this->headers as &$header) {
+ $header = clone $header;
+ }
+ }
+
+ /**
+ * Clears the header collection
+ */
+ public function clear()
+ {
+ $this->headers = array();
+ }
+
+ /**
+ * Set a header on the collection
+ *
+ * @param HeaderInterface $header Header to add
+ *
+ * @return self
+ */
+ public function add(HeaderInterface $header)
+ {
+ $this->headers[strtolower($header->getName())] = $header;
+
+ return $this;
+ }
+
+ /**
+ * Get an array of header objects
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Alias of offsetGet
+ */
+ public function get($key)
+ {
+ return $this->offsetGet($key);
+ }
+
+ public function count()
+ {
+ return count($this->headers);
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->headers[strtolower($offset)]);
+ }
+
+ public function offsetGet($offset)
+ {
+ $l = strtolower($offset);
+
+ return isset($this->headers[$l]) ? $this->headers[$l] : null;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->add($value);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->headers[strtolower($offset)]);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->headers);
+ }
+
+ public function toArray()
+ {
+ $result = array();
+ foreach ($this->headers as $header) {
+ $result[$header->getName()] = $header->toArray();
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php
new file mode 100644
index 0000000..0273be5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+use Guzzle\Http\Message\Header;
+
+/**
+ * Default header factory implementation
+ */
+class HeaderFactory implements HeaderFactoryInterface
+{
+ /** @var array */
+ protected $mapping = array(
+ 'cache-control' => 'Guzzle\Http\Message\Header\CacheControl',
+ 'link' => 'Guzzle\Http\Message\Header\Link',
+ );
+
+ public function createHeader($header, $value = null)
+ {
+ $lowercase = strtolower($header);
+
+ return isset($this->mapping[$lowercase])
+ ? new $this->mapping[$lowercase]($header, $value)
+ : new Header($header, $value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php
new file mode 100644
index 0000000..9457cf6
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+/**
+ * Interface for creating headers
+ */
+interface HeaderFactoryInterface
+{
+ /**
+ * Create a header from a header name and a single value
+ *
+ * @param string $header Name of the header to create
+ * @param string $value Value to set on the header
+ *
+ * @return HeaderInterface
+ */
+ public function createHeader($header, $value = null);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderInterface.php
new file mode 100644
index 0000000..adcc78e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+use Guzzle\Common\ToArrayInterface;
+
+interface HeaderInterface extends ToArrayInterface, \Countable, \IteratorAggregate
+{
+ /**
+ * Convert the header to a string
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Add a value to the list of header values
+ *
+ * @param string $value Value to add to the header
+ *
+ * @return self
+ */
+ public function add($value);
+
+ /**
+ * Get the name of the header
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Change the name of the header
+ *
+ * @param string $name Name to change to
+ *
+ * @return self
+ */
+ public function setName($name);
+
+ /**
+ * Change the glue used to implode the values
+ *
+ * @param string $glue Glue used to implode multiple values
+ *
+ * @return self
+ */
+ public function setGlue($glue);
+
+ /**
+ * Get the glue used to implode multiple values into a string
+ *
+ * @return string
+ */
+ public function getGlue();
+
+ /**
+ * Check if the collection of headers has a particular value
+ *
+ * @param string $searchValue Value to search for
+ *
+ * @return bool
+ */
+ public function hasValue($searchValue);
+
+ /**
+ * Remove a specific value from the header
+ *
+ * @param string $searchValue Value to remove
+ *
+ * @return self
+ */
+ public function removeValue($searchValue);
+
+ /**
+ * Parse a header containing ";" separated data into an array of associative arrays representing the header
+ * key value pair data of the header. When a parameter does not contain a value, but just contains a key, this
+ * function will inject a key with a '' string value.
+ *
+ * @return array
+ */
+ public function parseParams();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/Link.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/Link.php
new file mode 100644
index 0000000..a9fb961
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/Link.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Guzzle\Http\Message\Header;
+
+use Guzzle\Http\Message\Header;
+
+/**
+ * Provides helpful functionality for link headers
+ */
+class Link extends Header
+{
+ /**
+ * Add a link to the header
+ *
+ * @param string $url Link URL
+ * @param string $rel Link rel
+ * @param array $params Other link parameters
+ *
+ * @return self
+ */
+ public function addLink($url, $rel, array $params = array())
+ {
+ $values = array("<{$url}>", "rel=\"{$rel}\"");
+
+ foreach ($params as $k => $v) {
+ $values[] = "{$k}=\"{$v}\"";
+ }
+
+ return $this->add(implode('; ', $values));
+ }
+
+ /**
+ * Check if a specific link exists for a given rel attribute
+ *
+ * @param string $rel rel value
+ *
+ * @return bool
+ */
+ public function hasLink($rel)
+ {
+ return $this->getLink($rel) !== null;
+ }
+
+ /**
+ * Get a specific link for a given rel attribute
+ *
+ * @param string $rel Rel value
+ *
+ * @return array|null
+ */
+ public function getLink($rel)
+ {
+ foreach ($this->getLinks() as $link) {
+ if (isset($link['rel']) && $link['rel'] == $rel) {
+ return $link;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an associative array of links
+ *
+ * For example:
+ * Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"
+ *
+ * <code>
+ * var_export($response->getLinks());
+ * array(
+ * array(
+ * 'url' => 'http:/.../front.jpeg',
+ * 'rel' => 'back',
+ * 'type' => 'image/jpeg',
+ * )
+ * )
+ * </code>
+ *
+ * @return array
+ */
+ public function getLinks()
+ {
+ $links = $this->parseParams();
+
+ foreach ($links as &$link) {
+ $key = key($link);
+ unset($link[$key]);
+ $link['url'] = trim($key, '<> ');
+ }
+
+ return $links;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php
new file mode 100644
index 0000000..62bcd43
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+/**
+ * Request and response message interface
+ */
+interface MessageInterface
+{
+ /**
+ * Get application and plugin specific parameters set on the message.
+ *
+ * @return \Guzzle\Common\Collection
+ */
+ public function getParams();
+
+ /**
+ * Add a header to an existing collection of headers.
+ *
+ * @param string $header Header name to add
+ * @param string $value Value of the header
+ *
+ * @return self
+ */
+ public function addHeader($header, $value);
+
+ /**
+ * Add and merge in an array of HTTP headers.
+ *
+ * @param array $headers Associative array of header data.
+ *
+ * @return self
+ */
+ public function addHeaders(array $headers);
+
+ /**
+ * Retrieve an HTTP header by name. Performs a case-insensitive search of all headers.
+ *
+ * @param string $header Header to retrieve.
+ *
+ * @return Header|null
+ */
+ public function getHeader($header);
+
+ /**
+ * Get all headers as a collection
+ *
+ * @return \Guzzle\Http\Message\Header\HeaderCollection
+ */
+ public function getHeaders();
+
+ /**
+ * Check if the specified header is present.
+ *
+ * @param string $header The header to check.
+ *
+ * @return bool
+ */
+ public function hasHeader($header);
+
+ /**
+ * Remove a specific HTTP header.
+ *
+ * @param string $header HTTP header to remove.
+ *
+ * @return self
+ */
+ public function removeHeader($header);
+
+ /**
+ * Set an HTTP header and overwrite any existing value for the header
+ *
+ * @param string $header Name of the header to set.
+ * @param mixed $value Value to set.
+ *
+ * @return self
+ */
+ public function setHeader($header, $value);
+
+ /**
+ * Overwrite all HTTP headers with the supplied array of headers
+ *
+ * @param array $headers Associative array of header data.
+ *
+ * @return self
+ */
+ public function setHeaders(array $headers);
+
+ /**
+ * Get an array of message header lines (e.g. ["Host: example.com", ...])
+ *
+ * @return array
+ */
+ public function getHeaderLines();
+
+ /**
+ * Get the raw message headers as a string
+ *
+ * @return string
+ */
+ public function getRawHeaders();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFile.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFile.php
new file mode 100644
index 0000000..141e66d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFile.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Version;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Mimetypes;
+
+/**
+ * POST file upload
+ */
+class PostFile implements PostFileInterface
+{
+ protected $fieldName;
+ protected $contentType;
+ protected $filename;
+ protected $postname;
+
+ /**
+ * @param string $fieldName Name of the field
+ * @param string $filename Local path to the file
+ * @param string $postname Remote post file name
+ * @param string $contentType Content-Type of the upload
+ */
+ public function __construct($fieldName, $filename, $contentType = null, $postname = null)
+ {
+ $this->fieldName = $fieldName;
+ $this->setFilename($filename);
+ $this->postname = $postname ? $postname : basename($filename);
+ $this->contentType = $contentType ?: $this->guessContentType();
+ }
+
+ public function setFieldName($name)
+ {
+ $this->fieldName = $name;
+
+ return $this;
+ }
+
+ public function getFieldName()
+ {
+ return $this->fieldName;
+ }
+
+ public function setFilename($filename)
+ {
+ // Remove leading @ symbol
+ if (strpos($filename, '@') === 0) {
+ $filename = substr($filename, 1);
+ }
+
+ if (!is_readable($filename)) {
+ throw new InvalidArgumentException("Unable to open {$filename} for reading");
+ }
+
+ $this->filename = $filename;
+
+ return $this;
+ }
+
+ public function setPostname($postname)
+ {
+ $this->postname = $postname;
+
+ return $this;
+ }
+
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+
+ public function getPostname()
+ {
+ return $this->postname;
+ }
+
+ public function setContentType($type)
+ {
+ $this->contentType = $type;
+
+ return $this;
+ }
+
+ public function getContentType()
+ {
+ return $this->contentType;
+ }
+
+ public function getCurlValue()
+ {
+ // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
+ // See: https://wiki.php.net/rfc/curl-file-upload
+ if (function_exists('curl_file_create')) {
+ return curl_file_create($this->filename, $this->contentType, $this->postname);
+ }
+
+ // Use the old style if using an older version of PHP
+ $value = "@{$this->filename};filename=" . $this->postname;
+ if ($this->contentType) {
+ $value .= ';type=' . $this->contentType;
+ }
+
+ return $value;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function getCurlString()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use getCurlValue()');
+ return $this->getCurlValue();
+ }
+
+ /**
+ * Determine the Content-Type of the file
+ */
+ protected function guessContentType()
+ {
+ return Mimetypes::getInstance()->fromFilename($this->filename) ?: 'application/octet-stream';
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php
new file mode 100644
index 0000000..7f0779d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * POST file upload
+ */
+interface PostFileInterface
+{
+ /**
+ * Set the name of the field
+ *
+ * @param string $name Field name
+ *
+ * @return self
+ */
+ public function setFieldName($name);
+
+ /**
+ * Get the name of the field
+ *
+ * @return string
+ */
+ public function getFieldName();
+
+ /**
+ * Set the path to the file
+ *
+ * @param string $path Full path to the file
+ *
+ * @return self
+ * @throws InvalidArgumentException if the file cannot be read
+ */
+ public function setFilename($path);
+
+ /**
+ * Set the post name of the file
+ *
+ * @param string $name The new name of the file
+ *
+ * @return self
+ */
+ public function setPostname($name);
+
+ /**
+ * Get the full path to the file
+ *
+ * @return string
+ */
+ public function getFilename();
+
+ /**
+ * Get the post name of the file
+ *
+ * @return string
+ */
+ public function getPostname();
+
+ /**
+ * Set the Content-Type of the file
+ *
+ * @param string $type Content type
+ *
+ * @return self
+ */
+ public function setContentType($type);
+
+ /**
+ * Get the Content-Type of the file
+ *
+ * @return string
+ */
+ public function getContentType();
+
+ /**
+ * Get a cURL ready string or CurlFile object for the upload
+ *
+ * @return string
+ */
+ public function getCurlValue();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Request.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Request.php
new file mode 100644
index 0000000..f218cd5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Request.php
@@ -0,0 +1,638 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Version;
+use Guzzle\Common\Event;
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\ClientInterface;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\Message\Header\HeaderInterface;
+use Guzzle\Http\Url;
+use Guzzle\Parser\ParserRegistry;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * HTTP request class to send requests
+ */
+class Request extends AbstractMessage implements RequestInterface
+{
+ /** @var EventDispatcherInterface */
+ protected $eventDispatcher;
+
+ /** @var Url HTTP Url */
+ protected $url;
+
+ /** @var string HTTP method (GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE) */
+ protected $method;
+
+ /** @var ClientInterface */
+ protected $client;
+
+ /** @var Response Response of the request */
+ protected $response;
+
+ /** @var EntityBodyInterface Response body */
+ protected $responseBody;
+
+ /** @var string State of the request object */
+ protected $state;
+
+ /** @var string Authentication username */
+ protected $username;
+
+ /** @var string Auth password */
+ protected $password;
+
+ /** @var Collection cURL specific transfer options */
+ protected $curlOptions;
+
+ /** @var bool */
+ protected $isRedirect = false;
+
+ public static function getAllEvents()
+ {
+ return array(
+ // Called when receiving or uploading data through cURL
+ 'curl.callback.read', 'curl.callback.write', 'curl.callback.progress',
+ // Cloning a request
+ 'request.clone',
+ // About to send the request, sent request, completed transaction
+ 'request.before_send', 'request.sent', 'request.complete',
+ // A request received a successful response
+ 'request.success',
+ // A request received an unsuccessful response
+ 'request.error',
+ // An exception is being thrown because of an unsuccessful response
+ 'request.exception',
+ // Received response status line
+ 'request.receive.status_line'
+ );
+ }
+
+ /**
+ * @param string $method HTTP method
+ * @param string|Url $url HTTP URL to connect to. The URI scheme, host header, and URI are parsed from the
+ * full URL. If query string parameters are present they will be parsed as well.
+ * @param array|Collection $headers HTTP headers
+ */
+ public function __construct($method, $url, $headers = array())
+ {
+ parent::__construct();
+ $this->method = strtoupper($method);
+ $this->curlOptions = new Collection();
+ $this->setUrl($url);
+
+ if ($headers) {
+ // Special handling for multi-value headers
+ foreach ($headers as $key => $value) {
+ // Deal with collisions with Host and Authorization
+ if ($key == 'host' || $key == 'Host') {
+ $this->setHeader($key, $value);
+ } elseif ($value instanceof HeaderInterface) {
+ $this->addHeader($key, $value);
+ } else {
+ foreach ((array) $value as $v) {
+ $this->addHeader($key, $v);
+ }
+ }
+ }
+ }
+
+ $this->setState(self::STATE_NEW);
+ }
+
+ public function __clone()
+ {
+ if ($this->eventDispatcher) {
+ $this->eventDispatcher = clone $this->eventDispatcher;
+ }
+ $this->curlOptions = clone $this->curlOptions;
+ $this->params = clone $this->params;
+ $this->url = clone $this->url;
+ $this->response = $this->responseBody = null;
+ $this->headers = clone $this->headers;
+
+ $this->setState(RequestInterface::STATE_NEW);
+ $this->dispatch('request.clone', array('request' => $this));
+ }
+
+ /**
+ * Get the HTTP request as a string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getRawHeaders() . "\r\n\r\n";
+ }
+
+ /**
+ * Default method that will throw exceptions if an unsuccessful response is received.
+ *
+ * @param Event $event Received
+ * @throws BadResponseException if the response is not successful
+ */
+ public static function onRequestError(Event $event)
+ {
+ $e = BadResponseException::factory($event['request'], $event['response']);
+ $event['request']->setState(self::STATE_ERROR, array('exception' => $e) + $event->toArray());
+ throw $e;
+ }
+
+ public function setClient(ClientInterface $client)
+ {
+ $this->client = $client;
+
+ return $this;
+ }
+
+ public function getClient()
+ {
+ return $this->client;
+ }
+
+ public function getRawHeaders()
+ {
+ $protocolVersion = $this->protocolVersion ?: '1.1';
+
+ return trim($this->method . ' ' . $this->getResource()) . ' '
+ . strtoupper(str_replace('https', 'http', $this->url->getScheme()))
+ . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this->getHeaderLines());
+ }
+
+ public function setUrl($url)
+ {
+ if ($url instanceof Url) {
+ $this->url = $url;
+ } else {
+ $this->url = Url::factory($url);
+ }
+
+ // Update the port and host header
+ $this->setPort($this->url->getPort());
+
+ if ($this->url->getUsername() || $this->url->getPassword()) {
+ $this->setAuth($this->url->getUsername(), $this->url->getPassword());
+ // Remove the auth info from the URL
+ $this->url->setUsername(null);
+ $this->url->setPassword(null);
+ }
+
+ return $this;
+ }
+
+ public function send()
+ {
+ if (!$this->client) {
+ throw new RuntimeException('A client must be set on the request');
+ }
+
+ return $this->client->send($this);
+ }
+
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ public function getQuery($asString = false)
+ {
+ return $asString
+ ? (string) $this->url->getQuery()
+ : $this->url->getQuery();
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function getScheme()
+ {
+ return $this->url->getScheme();
+ }
+
+ public function setScheme($scheme)
+ {
+ $this->url->setScheme($scheme);
+
+ return $this;
+ }
+
+ public function getHost()
+ {
+ return $this->url->getHost();
+ }
+
+ public function setHost($host)
+ {
+ $this->url->setHost($host);
+ $this->setPort($this->url->getPort());
+
+ return $this;
+ }
+
+ public function getProtocolVersion()
+ {
+ return $this->protocolVersion;
+ }
+
+ public function setProtocolVersion($protocol)
+ {
+ $this->protocolVersion = $protocol;
+
+ return $this;
+ }
+
+ public function getPath()
+ {
+ return '/' . ltrim($this->url->getPath(), '/');
+ }
+
+ public function setPath($path)
+ {
+ $this->url->setPath($path);
+
+ return $this;
+ }
+
+ public function getPort()
+ {
+ return $this->url->getPort();
+ }
+
+ public function setPort($port)
+ {
+ $this->url->setPort($port);
+
+ // Include the port in the Host header if it is not the default port for the scheme of the URL
+ $scheme = $this->url->getScheme();
+ if ($port && (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443))) {
+ $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost() . ':' . $port);
+ } else {
+ $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost());
+ }
+
+ return $this;
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC)
+ {
+ static $authMap = array(
+ 'basic' => CURLAUTH_BASIC,
+ 'digest' => CURLAUTH_DIGEST,
+ 'ntlm' => CURLAUTH_NTLM,
+ 'any' => CURLAUTH_ANY
+ );
+
+ // If we got false or null, disable authentication
+ if (!$user) {
+ $this->password = $this->username = null;
+ $this->removeHeader('Authorization');
+ $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
+ return $this;
+ }
+
+ if (!is_numeric($scheme)) {
+ $scheme = strtolower($scheme);
+ if (!isset($authMap[$scheme])) {
+ throw new InvalidArgumentException($scheme . ' is not a valid authentication type');
+ }
+ $scheme = $authMap[$scheme];
+ }
+
+ $this->username = $user;
+ $this->password = $password;
+
+ // Bypass CURL when using basic auth to promote connection reuse
+ if ($scheme == CURLAUTH_BASIC) {
+ $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
+ $this->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password));
+ } else {
+ $this->getCurlOptions()
+ ->set(CURLOPT_HTTPAUTH, $scheme)
+ ->set(CURLOPT_USERPWD, $this->username . ':' . $this->password);
+ }
+
+ return $this;
+ }
+
+ public function getResource()
+ {
+ $resource = $this->getPath();
+ if ($query = (string) $this->url->getQuery()) {
+ $resource .= '?' . $query;
+ }
+
+ return $resource;
+ }
+
+ public function getUrl($asObject = false)
+ {
+ return $asObject ? clone $this->url : (string) $this->url;
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function setState($state, array $context = array())
+ {
+ $oldState = $this->state;
+ $this->state = $state;
+
+ switch ($state) {
+ case self::STATE_NEW:
+ $this->response = null;
+ break;
+ case self::STATE_TRANSFER:
+ if ($oldState !== $state) {
+ // Fix Content-Length and Transfer-Encoding collisions
+ if ($this->hasHeader('Transfer-Encoding') && $this->hasHeader('Content-Length')) {
+ $this->removeHeader('Transfer-Encoding');
+ }
+ $this->dispatch('request.before_send', array('request' => $this));
+ }
+ break;
+ case self::STATE_COMPLETE:
+ if ($oldState !== $state) {
+ $this->processResponse($context);
+ $this->responseBody = null;
+ }
+ break;
+ case self::STATE_ERROR:
+ if (isset($context['exception'])) {
+ $this->dispatch('request.exception', array(
+ 'request' => $this,
+ 'response' => isset($context['response']) ? $context['response'] : $this->response,
+ 'exception' => isset($context['exception']) ? $context['exception'] : null
+ ));
+ }
+ }
+
+ return $this->state;
+ }
+
+ public function getCurlOptions()
+ {
+ return $this->curlOptions;
+ }
+
+ public function startResponse(Response $response)
+ {
+ $this->state = self::STATE_TRANSFER;
+ $response->setEffectiveUrl((string) $this->getUrl());
+ $this->response = $response;
+
+ return $this;
+ }
+
+ public function setResponse(Response $response, $queued = false)
+ {
+ $response->setEffectiveUrl((string) $this->url);
+
+ if ($queued) {
+ $ed = $this->getEventDispatcher();
+ $ed->addListener('request.before_send', $f = function ($e) use ($response, &$f, $ed) {
+ $e['request']->setResponse($response);
+ $ed->removeListener('request.before_send', $f);
+ }, -9999);
+ } else {
+ $this->response = $response;
+ // If a specific response body is specified, then use it instead of the response's body
+ if ($this->responseBody && !$this->responseBody->getCustomData('default') && !$response->isRedirect()) {
+ $this->getResponseBody()->write((string) $this->response->getBody());
+ } else {
+ $this->responseBody = $this->response->getBody();
+ }
+ $this->setState(self::STATE_COMPLETE);
+ }
+
+ return $this;
+ }
+
+ public function setResponseBody($body)
+ {
+ // Attempt to open a file for writing if a string was passed
+ if (is_string($body)) {
+ // @codeCoverageIgnoreStart
+ if (!($body = fopen($body, 'w+'))) {
+ throw new InvalidArgumentException('Could not open ' . $body . ' for writing');
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ $this->responseBody = EntityBody::factory($body);
+
+ return $this;
+ }
+
+ public function getResponseBody()
+ {
+ if ($this->responseBody === null) {
+ $this->responseBody = EntityBody::factory()->setCustomData('default', true);
+ }
+
+ return $this->responseBody;
+ }
+
+ /**
+ * Determine if the response body is repeatable (readable + seekable)
+ *
+ * @return bool
+ * @deprecated Use getResponseBody()->isSeekable()
+ * @codeCoverageIgnore
+ */
+ public function isResponseBodyRepeatable()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use $request->getResponseBody()->isRepeatable()');
+ return !$this->responseBody ? true : $this->responseBody->isRepeatable();
+ }
+
+ public function getCookies()
+ {
+ if ($cookie = $this->getHeader('Cookie')) {
+ $data = ParserRegistry::getInstance()->getParser('cookie')->parseCookie($cookie);
+ return $data['cookies'];
+ }
+
+ return array();
+ }
+
+ public function getCookie($name)
+ {
+ $cookies = $this->getCookies();
+
+ return isset($cookies[$name]) ? $cookies[$name] : null;
+ }
+
+ public function addCookie($name, $value)
+ {
+ if (!$this->hasHeader('Cookie')) {
+ $this->setHeader('Cookie', "{$name}={$value}");
+ } else {
+ $this->getHeader('Cookie')->add("{$name}={$value}");
+ }
+
+ // Always use semicolons to separate multiple cookie headers
+ $this->getHeader('Cookie')->setGlue(';');
+
+ return $this;
+ }
+
+ public function removeCookie($name)
+ {
+ if ($cookie = $this->getHeader('Cookie')) {
+ foreach ($cookie as $cookieValue) {
+ if (strpos($cookieValue, $name . '=') === 0) {
+ $cookie->removeValue($cookieValue);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+ $this->eventDispatcher->addListener('request.error', array(__CLASS__, 'onRequestError'), -255);
+
+ return $this;
+ }
+
+ public function getEventDispatcher()
+ {
+ if (!$this->eventDispatcher) {
+ $this->setEventDispatcher(new EventDispatcher());
+ }
+
+ return $this->eventDispatcher;
+ }
+
+ public function dispatch($eventName, array $context = array())
+ {
+ $context['request'] = $this;
+
+ return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
+ }
+
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->getEventDispatcher()->addSubscriber($subscriber);
+
+ return $this;
+ }
+
+ /**
+ * Get an array containing the request and response for event notifications
+ *
+ * @return array
+ */
+ protected function getEventArray()
+ {
+ return array(
+ 'request' => $this,
+ 'response' => $this->response
+ );
+ }
+
+ /**
+ * Process a received response
+ *
+ * @param array $context Contextual information
+ * @throws RequestException|BadResponseException on unsuccessful responses
+ */
+ protected function processResponse(array $context = array())
+ {
+ if (!$this->response) {
+ // If no response, then processResponse shouldn't have been called
+ $e = new RequestException('Error completing request');
+ $e->setRequest($this);
+ throw $e;
+ }
+
+ $this->state = self::STATE_COMPLETE;
+
+ // A request was sent, but we don't know if we'll send more or if the final response will be successful
+ $this->dispatch('request.sent', $this->getEventArray() + $context);
+
+ // Some response processors will remove the response or reset the state (example: ExponentialBackoffPlugin)
+ if ($this->state == RequestInterface::STATE_COMPLETE) {
+
+ // The request completed, so the HTTP transaction is complete
+ $this->dispatch('request.complete', $this->getEventArray());
+
+ // If the response is bad, allow listeners to modify it or throw exceptions. You can change the response by
+ // modifying the Event object in your listeners or calling setResponse() on the request
+ if ($this->response->isError()) {
+ $event = new Event($this->getEventArray());
+ $this->getEventDispatcher()->dispatch('request.error', $event);
+ // Allow events of request.error to quietly change the response
+ if ($event['response'] !== $this->response) {
+ $this->response = $event['response'];
+ }
+ }
+
+ // If a successful response was received, dispatch an event
+ if ($this->response->isSuccessful()) {
+ $this->dispatch('request.success', $this->getEventArray());
+ }
+ }
+ }
+
+ /**
+ * @deprecated Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy
+ * @codeCoverageIgnore
+ */
+ public function canCache()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy.');
+ if (class_exists('Guzzle\Plugin\Cache\DefaultCanCacheStrategy')) {
+ $canCache = new \Guzzle\Plugin\Cache\DefaultCanCacheStrategy();
+ return $canCache->canCacheRequest($this);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @deprecated Use the history plugin (not emitting a warning as this is built-into the RedirectPlugin for now)
+ * @codeCoverageIgnore
+ */
+ public function setIsRedirect($isRedirect)
+ {
+ $this->isRedirect = $isRedirect;
+
+ return $this;
+ }
+
+ /**
+ * @deprecated Use the history plugin
+ * @codeCoverageIgnore
+ */
+ public function isRedirect()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin to track this.');
+ return $this->isRedirect;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php
new file mode 100644
index 0000000..ba00a76
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php
@@ -0,0 +1,359 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Http\Url;
+use Guzzle\Parser\ParserRegistry;
+
+/**
+ * Default HTTP request factory used to create the default {@see Request} and {@see EntityEnclosingRequest} objects.
+ */
+class RequestFactory implements RequestFactoryInterface
+{
+ /** @var RequestFactory Singleton instance of the default request factory */
+ protected static $instance;
+
+ /** @var array Hash of methods available to the class (provides fast isset() lookups) */
+ protected $methods;
+
+ /** @var string Class to instantiate for requests with no body */
+ protected $requestClass = 'Guzzle\\Http\\Message\\Request';
+
+ /** @var string Class to instantiate for requests with a body */
+ protected $entityEnclosingRequestClass = 'Guzzle\\Http\\Message\\EntityEnclosingRequest';
+
+ /**
+ * Get a cached instance of the default request factory
+ *
+ * @return RequestFactory
+ */
+ public static function getInstance()
+ {
+ // @codeCoverageIgnoreStart
+ if (!static::$instance) {
+ static::$instance = new static();
+ }
+ // @codeCoverageIgnoreEnd
+
+ return static::$instance;
+ }
+
+ public function __construct()
+ {
+ $this->methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ public function fromMessage($message)
+ {
+ $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($message);
+
+ if (!$parsed) {
+ return false;
+ }
+
+ $request = $this->fromParts($parsed['method'], $parsed['request_url'],
+ $parsed['headers'], $parsed['body'], $parsed['protocol'],
+ $parsed['version']);
+
+ // EntityEnclosingRequest adds an "Expect: 100-Continue" header when using a raw request body for PUT or POST
+ // requests. This factory method should accurately reflect the message, so here we are removing the Expect
+ // header if one was not supplied in the message.
+ if (!isset($parsed['headers']['Expect']) && !isset($parsed['headers']['expect'])) {
+ $request->removeHeader('Expect');
+ }
+
+ return $request;
+ }
+
+ public function fromParts(
+ $method,
+ array $urlParts,
+ $headers = null,
+ $body = null,
+ $protocol = 'HTTP',
+ $protocolVersion = '1.1'
+ ) {
+ return $this->create($method, Url::buildUrl($urlParts), $headers, $body)
+ ->setProtocolVersion($protocolVersion);
+ }
+
+ public function create($method, $url, $headers = null, $body = null, array $options = array())
+ {
+ $method = strtoupper($method);
+
+ if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') {
+ // Handle non-entity-enclosing request methods
+ $request = new $this->requestClass($method, $url, $headers);
+ if ($body) {
+ // The body is where the response body will be stored
+ $type = gettype($body);
+ if ($type == 'string' || $type == 'resource' || $type == 'object') {
+ $request->setResponseBody($body);
+ }
+ }
+ } else {
+ // Create an entity enclosing request by default
+ $request = new $this->entityEnclosingRequestClass($method, $url, $headers);
+ if ($body || $body === '0') {
+ // Add POST fields and files to an entity enclosing request if an array is used
+ if (is_array($body) || $body instanceof Collection) {
+ // Normalize PHP style cURL uploads with a leading '@' symbol
+ foreach ($body as $key => $value) {
+ if (is_string($value) && substr($value, 0, 1) == '@') {
+ $request->addPostFile($key, $value);
+ unset($body[$key]);
+ }
+ }
+ // Add the fields if they are still present and not all files
+ $request->addPostFields($body);
+ } else {
+ // Add a raw entity body body to the request
+ $request->setBody($body, (string) $request->getHeader('Content-Type'));
+ if ((string) $request->getHeader('Transfer-Encoding') == 'chunked') {
+ $request->removeHeader('Content-Length');
+ }
+ }
+ }
+ }
+
+ if ($options) {
+ $this->applyOptions($request, $options);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Clone a request while changing the method. Emulates the behavior of
+ * {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method.
+ *
+ * @param RequestInterface $request Request to clone
+ * @param string $method Method to set
+ *
+ * @return RequestInterface
+ */
+ public function cloneRequestWithMethod(RequestInterface $request, $method)
+ {
+ // Create the request with the same client if possible
+ if ($request->getClient()) {
+ $cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders());
+ } else {
+ $cloned = $this->create($method, $request->getUrl(), $request->getHeaders());
+ }
+
+ $cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray());
+ $cloned->setEventDispatcher(clone $request->getEventDispatcher());
+ // Ensure that that the Content-Length header is not copied if changing to GET or HEAD
+ if (!($cloned instanceof EntityEnclosingRequestInterface)) {
+ $cloned->removeHeader('Content-Length');
+ } elseif ($request instanceof EntityEnclosingRequestInterface) {
+ $cloned->setBody($request->getBody());
+ }
+ $cloned->getParams()->replace($request->getParams()->toArray());
+ $cloned->dispatch('request.clone', array('request' => $cloned));
+
+ return $cloned;
+ }
+
+ public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE)
+ {
+ // Iterate over each key value pair and attempt to apply a config using function visitors
+ foreach ($options as $key => $value) {
+ $method = "visit_{$key}";
+ if (isset($this->methods[$method])) {
+ $this->{$method}($request, $value, $flags);
+ }
+ }
+ }
+
+ protected function visit_headers(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('headers value must be an array');
+ }
+
+ if ($flags & self::OPTIONS_AS_DEFAULTS) {
+ // Merge headers in but do not overwrite existing values
+ foreach ($value as $key => $header) {
+ if (!$request->hasHeader($key)) {
+ $request->setHeader($key, $header);
+ }
+ }
+ } else {
+ $request->addHeaders($value);
+ }
+ }
+
+ protected function visit_body(RequestInterface $request, $value, $flags)
+ {
+ if ($request instanceof EntityEnclosingRequestInterface) {
+ $request->setBody($value);
+ } else {
+ throw new InvalidArgumentException('Attempting to set a body on a non-entity-enclosing request');
+ }
+ }
+
+ protected function visit_allow_redirects(RequestInterface $request, $value, $flags)
+ {
+ if ($value === false) {
+ $request->getParams()->set(RedirectPlugin::DISABLE, true);
+ }
+ }
+
+ protected function visit_auth(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('auth value must be an array');
+ }
+
+ $request->setAuth($value[0], isset($value[1]) ? $value[1] : null, isset($value[2]) ? $value[2] : 'basic');
+ }
+
+ protected function visit_query(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('query value must be an array');
+ }
+
+ if ($flags & self::OPTIONS_AS_DEFAULTS) {
+ // Merge query string values in but do not overwrite existing values
+ $query = $request->getQuery();
+ $query->overwriteWith(array_diff_key($value, $query->toArray()));
+ } else {
+ $request->getQuery()->overwriteWith($value);
+ }
+ }
+
+ protected function visit_cookies(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('cookies value must be an array');
+ }
+
+ foreach ($value as $name => $v) {
+ $request->addCookie($name, $v);
+ }
+ }
+
+ protected function visit_events(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('events value must be an array');
+ }
+
+ foreach ($value as $name => $method) {
+ if (is_array($method)) {
+ $request->getEventDispatcher()->addListener($name, $method[0], $method[1]);
+ } else {
+ $request->getEventDispatcher()->addListener($name, $method);
+ }
+ }
+ }
+
+ protected function visit_plugins(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('plugins value must be an array');
+ }
+
+ foreach ($value as $plugin) {
+ $request->addSubscriber($plugin);
+ }
+ }
+
+ protected function visit_exceptions(RequestInterface $request, $value, $flags)
+ {
+ if ($value === false || $value === 0) {
+ $dispatcher = $request->getEventDispatcher();
+ foreach ($dispatcher->getListeners('request.error') as $listener) {
+ if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') {
+ $dispatcher->removeListener('request.error', $listener);
+ break;
+ }
+ }
+ }
+ }
+
+ protected function visit_save_to(RequestInterface $request, $value, $flags)
+ {
+ $request->setResponseBody($value);
+ }
+
+ protected function visit_params(RequestInterface $request, $value, $flags)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('params value must be an array');
+ }
+
+ $request->getParams()->overwriteWith($value);
+ }
+
+ protected function visit_timeout(RequestInterface $request, $value, $flags)
+ {
+ if (defined('CURLOPT_TIMEOUT_MS')) {
+ $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000);
+ } else {
+ $request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value);
+ }
+ }
+
+ protected function visit_connect_timeout(RequestInterface $request, $value, $flags)
+ {
+ if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
+ $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000);
+ } else {
+ $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value);
+ }
+ }
+
+ protected function visit_debug(RequestInterface $request, $value, $flags)
+ {
+ if ($value) {
+ $request->getCurlOptions()->set(CURLOPT_VERBOSE, true);
+ }
+ }
+
+ protected function visit_verify(RequestInterface $request, $value, $flags)
+ {
+ $curl = $request->getCurlOptions();
+ if ($value === true || is_string($value)) {
+ $curl[CURLOPT_SSL_VERIFYHOST] = 2;
+ $curl[CURLOPT_SSL_VERIFYPEER] = true;
+ if ($value !== true) {
+ $curl[CURLOPT_CAINFO] = $value;
+ }
+ } elseif ($value === false) {
+ unset($curl[CURLOPT_CAINFO]);
+ $curl[CURLOPT_SSL_VERIFYHOST] = 0;
+ $curl[CURLOPT_SSL_VERIFYPEER] = false;
+ }
+ }
+
+ protected function visit_proxy(RequestInterface $request, $value, $flags)
+ {
+ $request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags);
+ }
+
+ protected function visit_cert(RequestInterface $request, $value, $flags)
+ {
+ if (is_array($value)) {
+ $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]);
+ $request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]);
+ } else {
+ $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value);
+ }
+ }
+
+ protected function visit_ssl_key(RequestInterface $request, $value, $flags)
+ {
+ if (is_array($value)) {
+ $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]);
+ $request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]);
+ } else {
+ $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php
new file mode 100644
index 0000000..6088f10
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\Url;
+
+/**
+ * Request factory used to create HTTP requests
+ */
+interface RequestFactoryInterface
+{
+ const OPTIONS_NONE = 0;
+ const OPTIONS_AS_DEFAULTS = 1;
+
+ /**
+ * Create a new request based on an HTTP message
+ *
+ * @param string $message HTTP message as a string
+ *
+ * @return RequestInterface
+ */
+ public function fromMessage($message);
+
+ /**
+ * Create a request from URL parts as returned from parse_url()
+ *
+ * @param string $method HTTP method (GET, POST, PUT, HEAD, DELETE, etc)
+ *
+ * @param array $urlParts URL parts containing the same keys as parse_url()
+ * - scheme: e.g. http
+ * - host: e.g. www.guzzle-project.com
+ * - port: e.g. 80
+ * - user: e.g. michael
+ * - pass: e.g. rocks
+ * - path: e.g. / OR /index.html
+ * - query: after the question mark ?
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|array|EntityBodyInterface $body Body to send in the request
+ * @param string $protocol Protocol (HTTP, SPYDY, etc)
+ * @param string $protocolVersion 1.0, 1.1, etc
+ *
+ * @return RequestInterface
+ */
+ public function fromParts(
+ $method,
+ array $urlParts,
+ $headers = null,
+ $body = null,
+ $protocol = 'HTTP',
+ $protocolVersion = '1.1'
+ );
+
+ /**
+ * Create a new request based on the HTTP method
+ *
+ * @param string $method HTTP method (GET, POST, PUT, PATCH, HEAD, DELETE, ...)
+ * @param string|Url $url HTTP URL to connect to
+ * @param array|Collection $headers HTTP headers
+ * @param string|resource|array|EntityBodyInterface $body Body to send in the request
+ * @param array $options Array of options to apply to the request
+ *
+ * @return RequestInterface
+ */
+ public function create($method, $url, $headers = null, $body = null, array $options = array());
+
+ /**
+ * Apply an associative array of options to the request
+ *
+ * @param RequestInterface $request Request to update
+ * @param array $options Options to use with the request. Available options are:
+ * "headers": Associative array of headers
+ * "query": Associative array of query string values to add to the request
+ * "body": Body of a request, including an EntityBody, string, or array when sending POST requests.
+ * "auth": Array of HTTP authentication parameters to use with the request. The array must contain the
+ * username in index [0], the password in index [2], and can optionally contain the authentication type
+ * in index [3]. The authentication types are: "Basic", "Digest", "NTLM", "Any" (defaults to "Basic").
+ * "cookies": Associative array of cookies
+ * "allow_redirects": Set to false to disable redirects
+ * "save_to": String, fopen resource, or EntityBody object used to store the body of the response
+ * "events": Associative array mapping event names to a closure or array of (priority, closure)
+ * "plugins": Array of plugins to add to the request
+ * "exceptions": Set to false to disable throwing exceptions on an HTTP level error (e.g. 404, 500, etc)
+ * "params": Set custom request data parameters on a request. (Note: these are not query string parameters)
+ * "timeout": Float describing the timeout of the request in seconds
+ * "connect_timeout": Float describing the number of seconds to wait while trying to connect. Use 0 to wait
+ * indefinitely.
+ * "verify": Set to true to enable SSL cert validation (the default), false to disable, or supply the path
+ * to a CA bundle to enable verification using a custom certificate.
+ * "cert": Set to a string to specify the path to a file containing a PEM formatted certificate. If a
+ * password is required, then set an array containing the path to the PEM file followed by the the
+ * password required for the certificate.
+ * "ssl_key": Specify the path to a file containing a private SSL key in PEM format. If a password is
+ * required, then set an array containing the path to the SSL key followed by the password required for
+ * the certificate.
+ * "proxy": Specify an HTTP proxy (e.g. "http://username:password@192.168.16.1:10")
+ * "debug": Set to true to display all data sent over the wire
+ * @param int $flags Bitwise flags to apply when applying the options to the request. Defaults to no special
+ * options. `1` (OPTIONS_AS_DEFAULTS): When specified, options will only update a request when
+ * the value does not already exist on the request. This is only supported by "query" and
+ * "headers". Other bitwise options may be added in the future.
+ */
+ public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php
new file mode 100644
index 0000000..2f6b3c8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php
@@ -0,0 +1,318 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\HasDispatcherInterface;
+use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\ClientInterface;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\Url;
+use Guzzle\Http\QueryString;
+
+/**
+ * Generic HTTP request interface
+ */
+interface RequestInterface extends MessageInterface, HasDispatcherInterface
+{
+ const STATE_NEW = 'new';
+ const STATE_COMPLETE = 'complete';
+ const STATE_TRANSFER = 'transfer';
+ const STATE_ERROR = 'error';
+
+ const GET = 'GET';
+ const PUT = 'PUT';
+ const POST = 'POST';
+ const DELETE = 'DELETE';
+ const HEAD = 'HEAD';
+ const CONNECT = 'CONNECT';
+ const OPTIONS = 'OPTIONS';
+ const TRACE = 'TRACE';
+ const PATCH = 'PATCH';
+
+ /**
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Send the request
+ *
+ * @return Response
+ * @throws RequestException on a request error
+ */
+ public function send();
+
+ /**
+ * Set the client used to transport the request
+ *
+ * @param ClientInterface $client
+ *
+ * @return self
+ */
+ public function setClient(ClientInterface $client);
+
+ /**
+ * Get the client used to transport the request
+ *
+ * @return ClientInterface $client
+ */
+ public function getClient();
+
+ /**
+ * Set the URL of the request
+ *
+ * @param string $url|Url Full URL to set including query string
+ *
+ * @return self
+ */
+ public function setUrl($url);
+
+ /**
+ * Get the full URL of the request (e.g. 'http://www.guzzle-project.com/')
+ *
+ * @param bool $asObject Set to TRUE to retrieve the URL as a clone of the URL object owned by the request.
+ *
+ * @return string|Url
+ */
+ public function getUrl($asObject = false);
+
+ /**
+ * Get the resource part of the the request, including the path, query string, and fragment
+ *
+ * @return string
+ */
+ public function getResource();
+
+ /**
+ * Get the collection of key value pairs that will be used as the query string in the request
+ *
+ * @return QueryString
+ */
+ public function getQuery();
+
+ /**
+ * Get the HTTP method of the request
+ *
+ * @return string
+ */
+ public function getMethod();
+
+ /**
+ * Get the URI scheme of the request (http, https, ftp, etc)
+ *
+ * @return string
+ */
+ public function getScheme();
+
+ /**
+ * Set the URI scheme of the request (http, https, ftp, etc)
+ *
+ * @param string $scheme Scheme to set
+ *
+ * @return self
+ */
+ public function setScheme($scheme);
+
+ /**
+ * Get the host of the request
+ *
+ * @return string
+ */
+ public function getHost();
+
+ /**
+ * Set the host of the request. Including a port in the host will modify the port of the request.
+ *
+ * @param string $host Host to set (e.g. www.yahoo.com, www.yahoo.com:80)
+ *
+ * @return self
+ */
+ public function setHost($host);
+
+ /**
+ * Get the path of the request (e.g. '/', '/index.html')
+ *
+ * @return string
+ */
+ public function getPath();
+
+ /**
+ * Set the path of the request (e.g. '/', '/index.html')
+ *
+ * @param string|array $path Path to set or array of segments to implode
+ *
+ * @return self
+ */
+ public function setPath($path);
+
+ /**
+ * Get the port that the request will be sent on if it has been set
+ *
+ * @return int|null
+ */
+ public function getPort();
+
+ /**
+ * Set the port that the request will be sent on
+ *
+ * @param int $port Port number to set
+ *
+ * @return self
+ */
+ public function setPort($port);
+
+ /**
+ * Get the username to pass in the URL if set
+ *
+ * @return string|null
+ */
+ public function getUsername();
+
+ /**
+ * Get the password to pass in the URL if set
+ *
+ * @return string|null
+ */
+ public function getPassword();
+
+ /**
+ * Set HTTP authorization parameters
+ *
+ * @param string|bool $user User name or false disable authentication
+ * @param string $password Password
+ * @param string $scheme Authentication scheme ('Basic', 'Digest', or a CURLAUTH_* constant (deprecated))
+ *
+ * @return self
+ * @link http://www.ietf.org/rfc/rfc2617.txt
+ * @link http://php.net/manual/en/function.curl-setopt.php See the available options for CURLOPT_HTTPAUTH
+ * @throws RequestException
+ */
+ public function setAuth($user, $password = '', $scheme = 'Basic');
+
+ /**
+ * Get the HTTP protocol version of the request
+ *
+ * @return string
+ */
+ public function getProtocolVersion();
+
+ /**
+ * Set the HTTP protocol version of the request (e.g. 1.1 or 1.0)
+ *
+ * @param string $protocol HTTP protocol version to use with the request
+ *
+ * @return self
+ */
+ public function setProtocolVersion($protocol);
+
+ /**
+ * Get the previously received {@see Response} or NULL if the request has not been sent
+ *
+ * @return Response|null
+ */
+ public function getResponse();
+
+ /**
+ * Manually set a response for the request.
+ *
+ * This method is useful for specifying a mock response for the request or setting the response using a cache.
+ * Manually setting a response will bypass the actual sending of a request.
+ *
+ * @param Response $response Response object to set
+ * @param bool $queued Set to TRUE to keep the request in a state of not having been sent, but queue the
+ * response for send()
+ *
+ * @return self Returns a reference to the object.
+ */
+ public function setResponse(Response $response, $queued = false);
+
+ /**
+ * The start of a response has been received for a request and the request is still in progress
+ *
+ * @param Response $response Response that has been received so far
+ *
+ * @return self
+ */
+ public function startResponse(Response $response);
+
+ /**
+ * Set the EntityBody that will hold a successful response message's entity body.
+ *
+ * This method should be invoked when you need to send the response's entity body somewhere other than the normal
+ * php://temp buffer. For example, you can send the entity body to a socket, file, or some other custom stream.
+ *
+ * @param EntityBodyInterface|string|resource $body Response body object. Pass a string to attempt to store the
+ * response body in a local file.
+ * @return Request
+ */
+ public function setResponseBody($body);
+
+ /**
+ * Get the EntityBody that will hold the resulting response message's entity body. This response body will only
+ * be used for successful responses. Intermediate responses (e.g. redirects) will not use the targeted response
+ * body.
+ *
+ * @return EntityBodyInterface
+ */
+ public function getResponseBody();
+
+ /**
+ * Get the state of the request. One of 'complete', 'transfer', 'new', 'error'
+ *
+ * @return string
+ */
+ public function getState();
+
+ /**
+ * Set the state of the request
+ *
+ * @param string $state State of the request ('complete', 'transfer', 'new', 'error')
+ * @param array $context Contextual information about the state change
+ *
+ * @return string Returns the current state of the request (which may have changed due to events being fired)
+ */
+ public function setState($state, array $context = array());
+
+ /**
+ * Get the cURL options that will be applied when the cURL handle is created
+ *
+ * @return Collection
+ */
+ public function getCurlOptions();
+
+ /**
+ * Get an array of Cookies
+ *
+ * @return array
+ */
+ public function getCookies();
+
+ /**
+ * Get a cookie value by name
+ *
+ * @param string $name Cookie to retrieve
+ *
+ * @return null|string
+ */
+ public function getCookie($name);
+
+ /**
+ * Add a Cookie value by name to the Cookie header
+ *
+ * @param string $name Name of the cookie to add
+ * @param string $value Value to set
+ *
+ * @return self
+ */
+ public function addCookie($name, $value);
+
+ /**
+ * Remove a specific cookie value by name
+ *
+ * @param string $name Cookie to remove by name
+ *
+ * @return self
+ */
+ public function removeCookie($name);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Response.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Response.php
new file mode 100644
index 0000000..153e2dd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Response.php
@@ -0,0 +1,968 @@
+<?php
+
+namespace Guzzle\Http\Message;
+
+use Guzzle\Common\Version;
+use Guzzle\Common\ToArrayInterface;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Parser\ParserRegistry;
+
+/**
+ * Guzzle HTTP response object
+ */
+class Response extends AbstractMessage implements \Serializable
+{
+ /**
+ * @var array Array of reason phrases and their corresponding status codes
+ */
+ private static $statusTexts = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 226 => 'IM Used',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Reserved for WebDAV advanced collections expired proposal',
+ 426 => 'Upgrade required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates (Experimental)',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ );
+
+ /** @var EntityBodyInterface The response body */
+ protected $body;
+
+ /** @var string The reason phrase of the response (human readable code) */
+ protected $reasonPhrase;
+
+ /** @var string The status code of the response */
+ protected $statusCode;
+
+ /** @var array Information about the request */
+ protected $info = array();
+
+ /** @var string The effective URL that returned this response */
+ protected $effectiveUrl;
+
+ /** @var array Cacheable response codes (see RFC 2616:13.4) */
+ protected static $cacheResponseCodes = array(200, 203, 206, 300, 301, 410);
+
+ /**
+ * Create a new Response based on a raw response message
+ *
+ * @param string $message Response message
+ *
+ * @return self|bool Returns false on error
+ */
+ public static function fromMessage($message)
+ {
+ $data = ParserRegistry::getInstance()->getParser('message')->parseResponse($message);
+ if (!$data) {
+ return false;
+ }
+
+ $response = new static($data['code'], $data['headers'], $data['body']);
+ $response->setProtocol($data['protocol'], $data['version'])
+ ->setStatus($data['code'], $data['reason_phrase']);
+
+ // Set the appropriate Content-Length if the one set is inaccurate (e.g. setting to X)
+ $contentLength = (string) $response->getHeader('Content-Length');
+ $actualLength = strlen($data['body']);
+ if (strlen($data['body']) > 0 && $contentLength != $actualLength) {
+ $response->setHeader('Content-Length', $actualLength);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Construct the response
+ *
+ * @param string $statusCode The response status code (e.g. 200, 404, etc)
+ * @param ToArrayInterface|array $headers The response headers
+ * @param string|resource|EntityBodyInterface $body The body of the response
+ *
+ * @throws BadResponseException if an invalid response code is given
+ */
+ public function __construct($statusCode, $headers = null, $body = null)
+ {
+ parent::__construct();
+ $this->setStatus($statusCode);
+ $this->body = EntityBody::factory($body !== null ? $body : '');
+
+ if ($headers) {
+ if (is_array($headers)) {
+ $this->setHeaders($headers);
+ } elseif ($headers instanceof ToArrayInterface) {
+ $this->setHeaders($headers->toArray());
+ } else {
+ throw new BadResponseException('Invalid headers argument received');
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getMessage();
+ }
+
+ public function serialize()
+ {
+ return json_encode(array(
+ 'status' => $this->statusCode,
+ 'body' => (string) $this->body,
+ 'headers' => $this->headers->toArray()
+ ));
+ }
+
+ public function unserialize($serialize)
+ {
+ $data = json_decode($serialize, true);
+ $this->__construct($data['status'], $data['headers'], $data['body']);
+ }
+
+ /**
+ * Get the response entity body
+ *
+ * @param bool $asString Set to TRUE to return a string of the body rather than a full body object
+ *
+ * @return EntityBodyInterface|string
+ */
+ public function getBody($asString = false)
+ {
+ return $asString ? (string) $this->body : $this->body;
+ }
+
+ /**
+ * Set the response entity body
+ *
+ * @param EntityBodyInterface|string $body Body to set
+ *
+ * @return self
+ */
+ public function setBody($body)
+ {
+ $this->body = EntityBody::factory($body);
+
+ return $this;
+ }
+
+ /**
+ * Set the protocol and protocol version of the response
+ *
+ * @param string $protocol Response protocol
+ * @param string $version Protocol version
+ *
+ * @return self
+ */
+ public function setProtocol($protocol, $version)
+ {
+ $this->protocol = $protocol;
+ $this->protocolVersion = $version;
+
+ return $this;
+ }
+
+ /**
+ * Get the protocol used for the response (e.g. HTTP)
+ *
+ * @return string
+ */
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ /**
+ * Get the HTTP protocol version
+ *
+ * @return string
+ */
+ public function getProtocolVersion()
+ {
+ return $this->protocolVersion;
+ }
+
+ /**
+ * Get a cURL transfer information
+ *
+ * @param string $key A single statistic to check
+ *
+ * @return array|string|null Returns all stats if no key is set, a single stat if a key is set, or null if a key
+ * is set and not found
+ * @link http://www.php.net/manual/en/function.curl-getinfo.php
+ */
+ public function getInfo($key = null)
+ {
+ if ($key === null) {
+ return $this->info;
+ } elseif (array_key_exists($key, $this->info)) {
+ return $this->info[$key];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the transfer information
+ *
+ * @param array $info Array of cURL transfer stats
+ *
+ * @return self
+ */
+ public function setInfo(array $info)
+ {
+ $this->info = $info;
+
+ return $this;
+ }
+
+ /**
+ * Set the response status
+ *
+ * @param int $statusCode Response status code to set
+ * @param string $reasonPhrase Response reason phrase
+ *
+ * @return self
+ * @throws BadResponseException when an invalid response code is received
+ */
+ public function setStatus($statusCode, $reasonPhrase = '')
+ {
+ $this->statusCode = (int) $statusCode;
+
+ if (!$reasonPhrase && isset(self::$statusTexts[$this->statusCode])) {
+ $this->reasonPhrase = self::$statusTexts[$this->statusCode];
+ } else {
+ $this->reasonPhrase = $reasonPhrase;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the response status code
+ *
+ * @return integer
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Get the entire response as a string
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ $message = $this->getRawHeaders();
+
+ // Only include the body in the message if the size is < 2MB
+ $size = $this->body->getSize();
+ if ($size < 2097152) {
+ $message .= (string) $this->body;
+ }
+
+ return $message;
+ }
+
+ /**
+ * Get the the raw message headers as a string
+ *
+ * @return string
+ */
+ public function getRawHeaders()
+ {
+ $headers = 'HTTP/1.1 ' . $this->statusCode . ' ' . $this->reasonPhrase . "\r\n";
+ $lines = $this->getHeaderLines();
+ if (!empty($lines)) {
+ $headers .= implode("\r\n", $lines) . "\r\n";
+ }
+
+ return $headers . "\r\n";
+ }
+
+ /**
+ * Get the response reason phrase- a human readable version of the numeric
+ * status code
+ *
+ * @return string
+ */
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ /**
+ * Get the Accept-Ranges HTTP header
+ *
+ * @return string Returns what partial content range types this server supports.
+ */
+ public function getAcceptRanges()
+ {
+ return (string) $this->getHeader('Accept-Ranges');
+ }
+
+ /**
+ * Calculate the age of the response
+ *
+ * @return integer
+ */
+ public function calculateAge()
+ {
+ $age = $this->getHeader('Age');
+
+ if ($age === null && $this->getDate()) {
+ $age = time() - strtotime($this->getDate());
+ }
+
+ return $age === null ? null : (int) (string) $age;
+ }
+
+ /**
+ * Get the Age HTTP header
+ *
+ * @return integer|null Returns the age the object has been in a proxy cache in seconds.
+ */
+ public function getAge()
+ {
+ return (string) $this->getHeader('Age');
+ }
+
+ /**
+ * Get the Allow HTTP header
+ *
+ * @return string|null Returns valid actions for a specified resource. To be used for a 405 Method not allowed.
+ */
+ public function getAllow()
+ {
+ return (string) $this->getHeader('Allow');
+ }
+
+ /**
+ * Check if an HTTP method is allowed by checking the Allow response header
+ *
+ * @param string $method Method to check
+ *
+ * @return bool
+ */
+ public function isMethodAllowed($method)
+ {
+ $allow = $this->getHeader('Allow');
+ if ($allow) {
+ foreach (explode(',', $allow) as $allowable) {
+ if (!strcasecmp(trim($allowable), $method)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the Cache-Control HTTP header
+ *
+ * @return string
+ */
+ public function getCacheControl()
+ {
+ return (string) $this->getHeader('Cache-Control');
+ }
+
+ /**
+ * Get the Connection HTTP header
+ *
+ * @return string
+ */
+ public function getConnection()
+ {
+ return (string) $this->getHeader('Connection');
+ }
+
+ /**
+ * Get the Content-Encoding HTTP header
+ *
+ * @return string|null
+ */
+ public function getContentEncoding()
+ {
+ return (string) $this->getHeader('Content-Encoding');
+ }
+
+ /**
+ * Get the Content-Language HTTP header
+ *
+ * @return string|null Returns the language the content is in.
+ */
+ public function getContentLanguage()
+ {
+ return (string) $this->getHeader('Content-Language');
+ }
+
+ /**
+ * Get the Content-Length HTTP header
+ *
+ * @return integer Returns the length of the response body in bytes
+ */
+ public function getContentLength()
+ {
+ return (int) (string) $this->getHeader('Content-Length');
+ }
+
+ /**
+ * Get the Content-Location HTTP header
+ *
+ * @return string|null Returns an alternate location for the returned data (e.g /index.htm)
+ */
+ public function getContentLocation()
+ {
+ return (string) $this->getHeader('Content-Location');
+ }
+
+ /**
+ * Get the Content-Disposition HTTP header
+ *
+ * @return string|null Returns the Content-Disposition header
+ */
+ public function getContentDisposition()
+ {
+ return (string) $this->getHeader('Content-Disposition');
+ }
+
+ /**
+ * Get the Content-MD5 HTTP header
+ *
+ * @return string|null Returns a Base64-encoded binary MD5 sum of the content of the response.
+ */
+ public function getContentMd5()
+ {
+ return (string) $this->getHeader('Content-MD5');
+ }
+
+ /**
+ * Get the Content-Range HTTP header
+ *
+ * @return string Returns where in a full body message this partial message belongs (e.g. bytes 21010-47021/47022).
+ */
+ public function getContentRange()
+ {
+ return (string) $this->getHeader('Content-Range');
+ }
+
+ /**
+ * Get the Content-Type HTTP header
+ *
+ * @return string Returns the mime type of this content.
+ */
+ public function getContentType()
+ {
+ return (string) $this->getHeader('Content-Type');
+ }
+
+ /**
+ * Checks if the Content-Type is of a certain type. This is useful if the
+ * Content-Type header contains charset information and you need to know if
+ * the Content-Type matches a particular type.
+ *
+ * @param string $type Content type to check against
+ *
+ * @return bool
+ */
+ public function isContentType($type)
+ {
+ return stripos($this->getHeader('Content-Type'), $type) !== false;
+ }
+
+ /**
+ * Get the Date HTTP header
+ *
+ * @return string|null Returns the date and time that the message was sent.
+ */
+ public function getDate()
+ {
+ return (string) $this->getHeader('Date');
+ }
+
+ /**
+ * Get the ETag HTTP header
+ *
+ * @return string|null Returns an identifier for a specific version of a resource, often a Message digest.
+ */
+ public function getEtag()
+ {
+ return (string) $this->getHeader('ETag');
+ }
+
+ /**
+ * Get the Expires HTTP header
+ *
+ * @return string|null Returns the date/time after which the response is considered stale.
+ */
+ public function getExpires()
+ {
+ return (string) $this->getHeader('Expires');
+ }
+
+ /**
+ * Get the Last-Modified HTTP header
+ *
+ * @return string|null Returns the last modified date for the requested object, in RFC 2822 format
+ * (e.g. Tue, 15 Nov 1994 12:45:26 GMT)
+ */
+ public function getLastModified()
+ {
+ return (string) $this->getHeader('Last-Modified');
+ }
+
+ /**
+ * Get the Location HTTP header
+ *
+ * @return string|null Used in redirection, or when a new resource has been created.
+ */
+ public function getLocation()
+ {
+ return (string) $this->getHeader('Location');
+ }
+
+ /**
+ * Get the Pragma HTTP header
+ *
+ * @return Header|null Returns the implementation-specific headers that may have various effects anywhere along
+ * the request-response chain.
+ */
+ public function getPragma()
+ {
+ return (string) $this->getHeader('Pragma');
+ }
+
+ /**
+ * Get the Proxy-Authenticate HTTP header
+ *
+ * @return string|null Authentication to access the proxy (e.g. Basic)
+ */
+ public function getProxyAuthenticate()
+ {
+ return (string) $this->getHeader('Proxy-Authenticate');
+ }
+
+ /**
+ * Get the Retry-After HTTP header
+ *
+ * @return int|null If an entity is temporarily unavailable, this instructs the client to try again after a
+ * specified period of time.
+ */
+ public function getRetryAfter()
+ {
+ return (string) $this->getHeader('Retry-After');
+ }
+
+ /**
+ * Get the Server HTTP header
+ *
+ * @return string|null A name for the server
+ */
+ public function getServer()
+ {
+ return (string) $this->getHeader('Server');
+ }
+
+ /**
+ * Get the Set-Cookie HTTP header
+ *
+ * @return string|null An HTTP cookie.
+ */
+ public function getSetCookie()
+ {
+ return (string) $this->getHeader('Set-Cookie');
+ }
+
+ /**
+ * Get the Trailer HTTP header
+ *
+ * @return string|null The Trailer general field value indicates that the given set of header fields is present in
+ * the trailer of a message encoded with chunked transfer-coding.
+ */
+ public function getTrailer()
+ {
+ return (string) $this->getHeader('Trailer');
+ }
+
+ /**
+ * Get the Transfer-Encoding HTTP header
+ *
+ * @return string|null The form of encoding used to safely transfer the entity to the user
+ */
+ public function getTransferEncoding()
+ {
+ return (string) $this->getHeader('Transfer-Encoding');
+ }
+
+ /**
+ * Get the Vary HTTP header
+ *
+ * @return string|null Tells downstream proxies how to match future request headers to decide whether the cached
+ * response can be used rather than requesting a fresh one from the origin server.
+ */
+ public function getVary()
+ {
+ return (string) $this->getHeader('Vary');
+ }
+
+ /**
+ * Get the Via HTTP header
+ *
+ * @return string|null Informs the client of proxies through which the response was sent.
+ */
+ public function getVia()
+ {
+ return (string) $this->getHeader('Via');
+ }
+
+ /**
+ * Get the Warning HTTP header
+ *
+ * @return string|null A general warning about possible problems with the entity body
+ */
+ public function getWarning()
+ {
+ return (string) $this->getHeader('Warning');
+ }
+
+ /**
+ * Get the WWW-Authenticate HTTP header
+ *
+ * @return string|null Indicates the authentication scheme that should be used to access the requested entity
+ */
+ public function getWwwAuthenticate()
+ {
+ return (string) $this->getHeader('WWW-Authenticate');
+ }
+
+ /**
+ * Checks if HTTP Status code is a Client Error (4xx)
+ *
+ * @return bool
+ */
+ public function isClientError()
+ {
+ return $this->statusCode >= 400 && $this->statusCode < 500;
+ }
+
+ /**
+ * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx)
+ *
+ * @return boolean
+ */
+ public function isError()
+ {
+ return $this->isClientError() || $this->isServerError();
+ }
+
+ /**
+ * Checks if HTTP Status code is Information (1xx)
+ *
+ * @return bool
+ */
+ public function isInformational()
+ {
+ return $this->statusCode < 200;
+ }
+
+ /**
+ * Checks if HTTP Status code is a Redirect (3xx)
+ *
+ * @return bool
+ */
+ public function isRedirect()
+ {
+ return $this->statusCode >= 300 && $this->statusCode < 400;
+ }
+
+ /**
+ * Checks if HTTP Status code is Server Error (5xx)
+ *
+ * @return bool
+ */
+ public function isServerError()
+ {
+ return $this->statusCode >= 500 && $this->statusCode < 600;
+ }
+
+ /**
+ * Checks if HTTP Status code is Successful (2xx | 304)
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return ($this->statusCode >= 200 && $this->statusCode < 300) || $this->statusCode == 304;
+ }
+
+ /**
+ * Check if the response can be cached based on the response headers
+ *
+ * @return bool Returns TRUE if the response can be cached or false if not
+ */
+ public function canCache()
+ {
+ // Check if the response is cacheable based on the code
+ if (!in_array((int) $this->getStatusCode(), self::$cacheResponseCodes)) {
+ return false;
+ }
+
+ // Make sure a valid body was returned and can be cached
+ if ((!$this->getBody()->isReadable() || !$this->getBody()->isSeekable())
+ && ($this->getContentLength() > 0 || $this->getTransferEncoding() == 'chunked')) {
+ return false;
+ }
+
+ // Never cache no-store resources (this is a private cache, so private
+ // can be cached)
+ if ($this->getHeader('Cache-Control') && $this->getHeader('Cache-Control')->hasDirective('no-store')) {
+ return false;
+ }
+
+ return $this->isFresh() || $this->getFreshness() === null || $this->canValidate();
+ }
+
+ /**
+ * Gets the number of seconds from the current time in which this response is still considered fresh
+ *
+ * @return int|null Returns the number of seconds
+ */
+ public function getMaxAge()
+ {
+ if ($header = $this->getHeader('Cache-Control')) {
+ // s-max-age, then max-age, then Expires
+ if ($age = $header->getDirective('s-maxage')) {
+ return $age;
+ }
+ if ($age = $header->getDirective('max-age')) {
+ return $age;
+ }
+ }
+
+ if ($this->getHeader('Expires')) {
+ return strtotime($this->getExpires()) - time();
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if the response is considered fresh.
+ *
+ * A response is considered fresh when its age is less than or equal to the freshness lifetime (maximum age) of the
+ * response.
+ *
+ * @return bool|null
+ */
+ public function isFresh()
+ {
+ $fresh = $this->getFreshness();
+
+ return $fresh === null ? null : $fresh >= 0;
+ }
+
+ /**
+ * Check if the response can be validated against the origin server using a conditional GET request.
+ *
+ * @return bool
+ */
+ public function canValidate()
+ {
+ return $this->getEtag() || $this->getLastModified();
+ }
+
+ /**
+ * Get the freshness of the response by returning the difference of the maximum lifetime of the response and the
+ * age of the response (max-age - age).
+ *
+ * Freshness values less than 0 mean that the response is no longer fresh and is ABS(freshness) seconds expired.
+ * Freshness values of greater than zero is the number of seconds until the response is no longer fresh. A NULL
+ * result means that no freshness information is available.
+ *
+ * @return int
+ */
+ public function getFreshness()
+ {
+ $maxAge = $this->getMaxAge();
+ $age = $this->calculateAge();
+
+ return $maxAge && $age ? ($maxAge - $age) : null;
+ }
+
+ /**
+ * Parse the JSON response body and return an array
+ *
+ * @return array|string|int|bool|float
+ * @throws RuntimeException if the response body is not in JSON format
+ */
+ public function json()
+ {
+ $data = json_decode((string) $this->body, true);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error());
+ }
+
+ return $data === null ? array() : $data;
+ }
+
+ /**
+ * Parse the XML response body and return a \SimpleXMLElement.
+ *
+ * In order to prevent XXE attacks, this method disables loading external
+ * entities. If you rely on external entities, then you must parse the
+ * XML response manually by accessing the response body directly.
+ *
+ * @return \SimpleXMLElement
+ * @throws RuntimeException if the response body is not in XML format
+ * @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html
+ */
+ public function xml()
+ {
+ $errorMessage = null;
+ $internalErrors = libxml_use_internal_errors(true);
+ $disableEntities = libxml_disable_entity_loader(true);
+ libxml_clear_errors();
+
+ try {
+ $xml = new \SimpleXMLElement((string) $this->body ?: '<root />', LIBXML_NONET);
+ if ($error = libxml_get_last_error()) {
+ $errorMessage = $error->message;
+ }
+ } catch (\Exception $e) {
+ $errorMessage = $e->getMessage();
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+ libxml_disable_entity_loader($disableEntities);
+
+ if ($errorMessage) {
+ throw new RuntimeException('Unable to parse response body into XML: ' . $errorMessage);
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Get the redirect count of this response
+ *
+ * @return int
+ */
+ public function getRedirectCount()
+ {
+ return (int) $this->params->get(RedirectPlugin::REDIRECT_COUNT);
+ }
+
+ /**
+ * Set the effective URL that resulted in this response (e.g. the last redirect URL)
+ *
+ * @param string $url The effective URL
+ *
+ * @return self
+ */
+ public function setEffectiveUrl($url)
+ {
+ $this->effectiveUrl = $url;
+
+ return $this;
+ }
+
+ /**
+ * Get the effective URL that resulted in this response (e.g. the last redirect URL)
+ *
+ * @return string
+ */
+ public function getEffectiveUrl()
+ {
+ return $this->effectiveUrl;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function getPreviousResponse()
+ {
+ Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin.');
+ return null;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function setRequest($request)
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function getRequest()
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ return null;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php
new file mode 100644
index 0000000..d71586a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php
@@ -0,0 +1,962 @@
+<?php
+
+namespace Guzzle\Http;
+
+/**
+ * Provides mappings of file extensions to mimetypes
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+class Mimetypes
+{
+ /** @var self */
+ protected static $instance;
+
+ /** @var array Mapping of extension to mimetype */
+ protected $mimetypes = array(
+ '3dml' => 'text/vnd.in3d.3dml',
+ '3g2' => 'video/3gpp2',
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aab' => 'application/x-authorware-bin',
+ 'aac' => 'audio/x-aac',
+ 'aam' => 'application/x-authorware-map',
+ 'aas' => 'application/x-authorware-seg',
+ 'abw' => 'application/x-abiword',
+ 'ac' => 'application/pkix-attr-cert',
+ 'acc' => 'application/vnd.americandynamics.acc',
+ 'ace' => 'application/x-ace-compressed',
+ 'acu' => 'application/vnd.acucobol',
+ 'acutc' => 'application/vnd.acucorp',
+ 'adp' => 'audio/adpcm',
+ 'aep' => 'application/vnd.audiograph',
+ 'afm' => 'application/x-font-type1',
+ 'afp' => 'application/vnd.ibm.modcap',
+ 'ahead' => 'application/vnd.ahead.space',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
+ 'ait' => 'application/vnd.dvb.ait',
+ 'ami' => 'application/vnd.amiga.ami',
+ 'apk' => 'application/vnd.android.package-archive',
+ 'application' => 'application/x-ms-application',
+ 'apr' => 'application/vnd.lotus-approach',
+ 'asa' => 'text/plain',
+ 'asax' => 'application/octet-stream',
+ 'asc' => 'application/pgp-signature',
+ 'ascx' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'ashx' => 'text/plain',
+ 'asm' => 'text/x-asm',
+ 'asmx' => 'text/plain',
+ 'aso' => 'application/vnd.accpac.simply.aso',
+ 'asp' => 'text/plain',
+ 'aspx' => 'text/plain',
+ 'asx' => 'video/x-ms-asf',
+ 'atc' => 'application/vnd.acucorp',
+ 'atom' => 'application/atom+xml',
+ 'atomcat' => 'application/atomcat+xml',
+ 'atomsvc' => 'application/atomsvc+xml',
+ 'atx' => 'application/vnd.antix.game-component',
+ 'au' => 'audio/basic',
+ 'avi' => 'video/x-msvideo',
+ 'aw' => 'application/applixware',
+ 'axd' => 'text/plain',
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
+ 'azw' => 'application/vnd.amazon.ebook',
+ 'bat' => 'application/x-msdownload',
+ 'bcpio' => 'application/x-bcpio',
+ 'bdf' => 'application/x-font-bdf',
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
+ 'bed' => 'application/vnd.realvnc.bed',
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
+ 'bin' => 'application/octet-stream',
+ 'bmi' => 'application/vnd.bmi',
+ 'bmp' => 'image/bmp',
+ 'book' => 'application/vnd.framemaker',
+ 'box' => 'application/vnd.previewsystems.box',
+ 'boz' => 'application/x-bzip2',
+ 'bpk' => 'application/octet-stream',
+ 'btif' => 'image/prs.btif',
+ 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2',
+ 'c' => 'text/x-c',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'c4d' => 'application/vnd.clonk.c4group',
+ 'c4f' => 'application/vnd.clonk.c4group',
+ 'c4g' => 'application/vnd.clonk.c4group',
+ 'c4p' => 'application/vnd.clonk.c4group',
+ 'c4u' => 'application/vnd.clonk.c4group',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+ 'car' => 'application/vnd.curl.car',
+ 'cat' => 'application/vnd.ms-pki.seccat',
+ 'cc' => 'text/x-c',
+ 'cct' => 'application/x-director',
+ 'ccxml' => 'application/ccxml+xml',
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
+ 'cdf' => 'application/x-netcdf',
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
+ 'cdmia' => 'application/cdmi-capability',
+ 'cdmic' => 'application/cdmi-container',
+ 'cdmid' => 'application/cdmi-domain',
+ 'cdmio' => 'application/cdmi-object',
+ 'cdmiq' => 'application/cdmi-queue',
+ 'cdx' => 'chemical/x-cdx',
+ 'cdxml' => 'application/vnd.chemdraw+xml',
+ 'cdy' => 'application/vnd.cinderella',
+ 'cer' => 'application/pkix-cert',
+ 'cfc' => 'application/x-coldfusion',
+ 'cfm' => 'application/x-coldfusion',
+ 'cgm' => 'image/cgm',
+ 'chat' => 'application/x-chat',
+ 'chm' => 'application/vnd.ms-htmlhelp',
+ 'chrt' => 'application/vnd.kde.kchart',
+ 'cif' => 'chemical/x-cif',
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
+ 'cil' => 'application/vnd.ms-artgalry',
+ 'cla' => 'application/vnd.claymore',
+ 'class' => 'application/java-vm',
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
+ 'clkp' => 'application/vnd.crick.clicker.palette',
+ 'clkt' => 'application/vnd.crick.clicker.template',
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
+ 'clkx' => 'application/vnd.crick.clicker',
+ 'clp' => 'application/x-msclip',
+ 'cmc' => 'application/vnd.cosmocaller',
+ 'cmdf' => 'chemical/x-cmdf',
+ 'cml' => 'chemical/x-cml',
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
+ 'cmx' => 'image/x-cmx',
+ 'cod' => 'application/vnd.rim.cod',
+ 'com' => 'application/x-msdownload',
+ 'conf' => 'text/plain',
+ 'cpio' => 'application/x-cpio',
+ 'cpp' => 'text/x-c',
+ 'cpt' => 'application/mac-compactpro',
+ 'crd' => 'application/x-mscardfile',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'cryptonote' => 'application/vnd.rig.cryptonote',
+ 'cs' => 'text/plain',
+ 'csh' => 'application/x-csh',
+ 'csml' => 'chemical/x-csml',
+ 'csp' => 'application/vnd.commonspace',
+ 'css' => 'text/css',
+ 'cst' => 'application/x-director',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'curl' => 'text/vnd.curl',
+ 'cww' => 'application/prs.cww',
+ 'cxt' => 'application/x-director',
+ 'cxx' => 'text/x-c',
+ 'dae' => 'model/vnd.collada+xml',
+ 'daf' => 'application/vnd.mobius.daf',
+ 'dataless' => 'application/vnd.fdsn.seed',
+ 'davmount' => 'application/davmount+xml',
+ 'dcr' => 'application/x-director',
+ 'dcurl' => 'text/vnd.curl.dcurl',
+ 'dd2' => 'application/vnd.oma.dd2+xml',
+ 'ddd' => 'application/vnd.fujixerox.ddd',
+ 'deb' => 'application/x-debian-package',
+ 'def' => 'text/plain',
+ 'deploy' => 'application/octet-stream',
+ 'der' => 'application/x-x509-ca-cert',
+ 'dfac' => 'application/vnd.dreamfactory',
+ 'dic' => 'text/x-c',
+ 'dir' => 'application/x-director',
+ 'dis' => 'application/vnd.mobius.dis',
+ 'dist' => 'application/octet-stream',
+ 'distz' => 'application/octet-stream',
+ 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'dll' => 'application/x-msdownload',
+ 'dmg' => 'application/octet-stream',
+ 'dms' => 'application/octet-stream',
+ 'dna' => 'application/vnd.dna',
+ 'doc' => 'application/msword',
+ 'docm' => 'application/vnd.ms-word.document.macroenabled.12',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dot' => 'application/msword',
+ 'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'dp' => 'application/vnd.osgi.dp',
+ 'dpg' => 'application/vnd.dpgraph',
+ 'dra' => 'audio/vnd.dra',
+ 'dsc' => 'text/prs.lines.tag',
+ 'dssc' => 'application/dssc+der',
+ 'dtb' => 'application/x-dtbook+xml',
+ 'dtd' => 'application/xml-dtd',
+ 'dts' => 'audio/vnd.dts',
+ 'dtshd' => 'audio/vnd.dts.hd',
+ 'dump' => 'application/octet-stream',
+ 'dvi' => 'application/x-dvi',
+ 'dwf' => 'model/vnd.dwf',
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'dxp' => 'application/vnd.spotfire.dxp',
+ 'dxr' => 'application/x-director',
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
+ 'ecma' => 'application/ecmascript',
+ 'edm' => 'application/vnd.novadigm.edm',
+ 'edx' => 'application/vnd.novadigm.edx',
+ 'efif' => 'application/vnd.picsel',
+ 'ei6' => 'application/vnd.pg.osasli',
+ 'elc' => 'application/octet-stream',
+ 'eml' => 'message/rfc822',
+ 'emma' => 'application/emma+xml',
+ 'eol' => 'audio/vnd.digital-winds',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'es3' => 'application/vnd.eszigno3+xml',
+ 'esf' => 'application/vnd.epson.esf',
+ 'et3' => 'application/vnd.eszigno3+xml',
+ 'etx' => 'text/x-setext',
+ 'exe' => 'application/x-msdownload',
+ 'exi' => 'application/exi',
+ 'ext' => 'application/vnd.novadigm.ext',
+ 'ez' => 'application/andrew-inset',
+ 'ez2' => 'application/vnd.ezpix-album',
+ 'ez3' => 'application/vnd.ezpix-package',
+ 'f' => 'text/x-fortran',
+ 'f4v' => 'video/x-f4v',
+ 'f77' => 'text/x-fortran',
+ 'f90' => 'text/x-fortran',
+ 'fbs' => 'image/vnd.fastbidsheet',
+ 'fcs' => 'application/vnd.isac.fcs',
+ 'fdf' => 'application/vnd.fdf',
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
+ 'fgd' => 'application/x-director',
+ 'fh' => 'image/x-freehand',
+ 'fh4' => 'image/x-freehand',
+ 'fh5' => 'image/x-freehand',
+ 'fh7' => 'image/x-freehand',
+ 'fhc' => 'image/x-freehand',
+ 'fig' => 'application/x-xfig',
+ 'fli' => 'video/x-fli',
+ 'flo' => 'application/vnd.micrografx.flo',
+ 'flv' => 'video/x-flv',
+ 'flw' => 'application/vnd.kde.kivio',
+ 'flx' => 'text/vnd.fmi.flexstor',
+ 'fly' => 'text/vnd.fly',
+ 'fm' => 'application/vnd.framemaker',
+ 'fnc' => 'application/vnd.frogans.fnc',
+ 'for' => 'text/x-fortran',
+ 'fpx' => 'image/vnd.fpx',
+ 'frame' => 'application/vnd.framemaker',
+ 'fsc' => 'application/vnd.fsc.weblaunch',
+ 'fst' => 'image/vnd.fst',
+ 'ftc' => 'application/vnd.fluxtime.clip',
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
+ 'fvt' => 'video/vnd.fvt',
+ 'fxp' => 'application/vnd.adobe.fxp',
+ 'fxpl' => 'application/vnd.adobe.fxp',
+ 'fzs' => 'application/vnd.fuzzysheet',
+ 'g2w' => 'application/vnd.geoplan',
+ 'g3' => 'image/g3fax',
+ 'g3w' => 'application/vnd.geospace',
+ 'gac' => 'application/vnd.groove-account',
+ 'gdl' => 'model/vnd.gdl',
+ 'geo' => 'application/vnd.dynageo',
+ 'gex' => 'application/vnd.geometry-explorer',
+ 'ggb' => 'application/vnd.geogebra.file',
+ 'ggt' => 'application/vnd.geogebra.tool',
+ 'ghf' => 'application/vnd.groove-help',
+ 'gif' => 'image/gif',
+ 'gim' => 'application/vnd.groove-identity-message',
+ 'gmx' => 'application/vnd.gmx',
+ 'gnumeric' => 'application/x-gnumeric',
+ 'gph' => 'application/vnd.flographit',
+ 'gqf' => 'application/vnd.grafeq',
+ 'gqs' => 'application/vnd.grafeq',
+ 'gram' => 'application/srgs',
+ 'gre' => 'application/vnd.geometry-explorer',
+ 'grv' => 'application/vnd.groove-injector',
+ 'grxml' => 'application/srgs+xml',
+ 'gsf' => 'application/x-font-ghostscript',
+ 'gtar' => 'application/x-gtar',
+ 'gtm' => 'application/vnd.groove-tool-message',
+ 'gtw' => 'model/vnd.gtw',
+ 'gv' => 'text/vnd.graphviz',
+ 'gxt' => 'application/vnd.geonext',
+ 'h' => 'text/x-c',
+ 'h261' => 'video/h261',
+ 'h263' => 'video/h263',
+ 'h264' => 'video/h264',
+ 'hal' => 'application/vnd.hal+xml',
+ 'hbci' => 'application/vnd.hbci',
+ 'hdf' => 'application/x-hdf',
+ 'hh' => 'text/x-c',
+ 'hlp' => 'application/winhlp',
+ 'hpgl' => 'application/vnd.hp-hpgl',
+ 'hpid' => 'application/vnd.hp-hpid',
+ 'hps' => 'application/vnd.hp-hps',
+ 'hqx' => 'application/mac-binhex40',
+ 'hta' => 'application/octet-stream',
+ 'htc' => 'text/html',
+ 'htke' => 'application/vnd.kenameaapp',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
+ 'hvs' => 'application/vnd.yamaha.hv-script',
+ 'i2g' => 'application/vnd.intergeo',
+ 'icc' => 'application/vnd.iccprofile',
+ 'ice' => 'x-conference/x-cooltalk',
+ 'icm' => 'application/vnd.iccprofile',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ief' => 'image/ief',
+ 'ifb' => 'text/calendar',
+ 'ifm' => 'application/vnd.shana.informed.formdata',
+ 'iges' => 'model/iges',
+ 'igl' => 'application/vnd.igloader',
+ 'igm' => 'application/vnd.insors.igm',
+ 'igs' => 'model/iges',
+ 'igx' => 'application/vnd.micrografx.igx',
+ 'iif' => 'application/vnd.shana.informed.interchange',
+ 'imp' => 'application/vnd.accpac.simply.imp',
+ 'ims' => 'application/vnd.ms-ims',
+ 'in' => 'text/plain',
+ 'ini' => 'text/plain',
+ 'ipfix' => 'application/ipfix',
+ 'ipk' => 'application/vnd.shana.informed.package',
+ 'irm' => 'application/vnd.ibm.rights-management',
+ 'irp' => 'application/vnd.irepository.package+xml',
+ 'iso' => 'application/octet-stream',
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
+ 'ivp' => 'application/vnd.immervision-ivp',
+ 'ivu' => 'application/vnd.immervision-ivu',
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
+ 'jam' => 'application/vnd.jam',
+ 'jar' => 'application/java-archive',
+ 'java' => 'text/x-java-source',
+ 'jisp' => 'application/vnd.jisp',
+ 'jlt' => 'application/vnd.hp-jlyt',
+ 'jnlp' => 'application/x-java-jnlp-file',
+ 'joda' => 'application/vnd.joost.joda-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'jpgm' => 'video/jpm',
+ 'jpgv' => 'video/jpeg',
+ 'jpm' => 'video/jpm',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'kar' => 'audio/midi',
+ 'karbon' => 'application/vnd.kde.karbon',
+ 'kfo' => 'application/vnd.kde.kformula',
+ 'kia' => 'application/vnd.kidspiration',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'kne' => 'application/vnd.kinar',
+ 'knp' => 'application/vnd.kinar',
+ 'kon' => 'application/vnd.kde.kontour',
+ 'kpr' => 'application/vnd.kde.kpresenter',
+ 'kpt' => 'application/vnd.kde.kpresenter',
+ 'ksp' => 'application/vnd.kde.kspread',
+ 'ktr' => 'application/vnd.kahootz',
+ 'ktx' => 'image/ktx',
+ 'ktz' => 'application/vnd.kahootz',
+ 'kwd' => 'application/vnd.kde.kword',
+ 'kwt' => 'application/vnd.kde.kword',
+ 'lasxml' => 'application/vnd.las.las+xml',
+ 'latex' => 'application/x-latex',
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ 'les' => 'application/vnd.hhe.lesson-player',
+ 'lha' => 'application/octet-stream',
+ 'link66' => 'application/vnd.route66.link66+xml',
+ 'list' => 'text/plain',
+ 'list3820' => 'application/vnd.ibm.modcap',
+ 'listafp' => 'application/vnd.ibm.modcap',
+ 'log' => 'text/plain',
+ 'lostxml' => 'application/lost+xml',
+ 'lrf' => 'application/octet-stream',
+ 'lrm' => 'application/vnd.ms-lrm',
+ 'ltf' => 'application/vnd.frogans.ltf',
+ 'lvp' => 'audio/vnd.lucent.voice',
+ 'lwp' => 'application/vnd.lotus-wordpro',
+ 'lzh' => 'application/octet-stream',
+ 'm13' => 'application/x-msmediaview',
+ 'm14' => 'application/x-msmediaview',
+ 'm1v' => 'video/mpeg',
+ 'm21' => 'application/mp21',
+ 'm2a' => 'audio/mpeg',
+ 'm2v' => 'video/mpeg',
+ 'm3a' => 'audio/mpeg',
+ 'm3u' => 'audio/x-mpegurl',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'm4a' => 'audio/mp4',
+ 'm4u' => 'video/vnd.mpegurl',
+ 'm4v' => 'video/mp4',
+ 'ma' => 'application/mathematica',
+ 'mads' => 'application/mads+xml',
+ 'mag' => 'application/vnd.ecowin.chart',
+ 'maker' => 'application/vnd.framemaker',
+ 'man' => 'text/troff',
+ 'mathml' => 'application/mathml+xml',
+ 'mb' => 'application/mathematica',
+ 'mbk' => 'application/vnd.mobius.mbk',
+ 'mbox' => 'application/mbox',
+ 'mc1' => 'application/vnd.medcalcdata',
+ 'mcd' => 'application/vnd.mcd',
+ 'mcurl' => 'text/vnd.curl.mcurl',
+ 'mdb' => 'application/x-msaccess',
+ 'mdi' => 'image/vnd.ms-modi',
+ 'me' => 'text/troff',
+ 'mesh' => 'model/mesh',
+ 'meta4' => 'application/metalink4+xml',
+ 'mets' => 'application/mets+xml',
+ 'mfm' => 'application/vnd.mfmp',
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
+ 'mgz' => 'application/vnd.proteus.magazine',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mif' => 'application/vnd.mif',
+ 'mime' => 'message/rfc822',
+ 'mj2' => 'video/mj2',
+ 'mjp2' => 'video/mj2',
+ 'mlp' => 'application/vnd.dolby.mlp',
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
+ 'mmf' => 'application/vnd.smaf',
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
+ 'mny' => 'application/x-msmoney',
+ 'mobi' => 'application/x-mobipocket-ebook',
+ 'mods' => 'application/mods+xml',
+ 'mov' => 'video/quicktime',
+ 'movie' => 'video/x-sgi-movie',
+ 'mp2' => 'audio/mpeg',
+ 'mp21' => 'application/mp21',
+ 'mp2a' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4s' => 'application/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpc' => 'application/vnd.mophun.certificate',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'mpga' => 'audio/mpeg',
+ 'mpkg' => 'application/vnd.apple.installer+xml',
+ 'mpm' => 'application/vnd.blueice.multipass',
+ 'mpn' => 'application/vnd.mophun.application',
+ 'mpp' => 'application/vnd.ms-project',
+ 'mpt' => 'application/vnd.ms-project',
+ 'mpy' => 'application/vnd.ibm.minipay',
+ 'mqy' => 'application/vnd.mobius.mqy',
+ 'mrc' => 'application/marc',
+ 'mrcx' => 'application/marcxml+xml',
+ 'ms' => 'text/troff',
+ 'mscml' => 'application/mediaservercontrol+xml',
+ 'mseed' => 'application/vnd.fdsn.mseed',
+ 'mseq' => 'application/vnd.mseq',
+ 'msf' => 'application/vnd.epson.msf',
+ 'msh' => 'model/mesh',
+ 'msi' => 'application/x-msdownload',
+ 'msl' => 'application/vnd.mobius.msl',
+ 'msty' => 'application/vnd.muvee.style',
+ 'mts' => 'model/vnd.mts',
+ 'mus' => 'application/vnd.musician',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ 'mvb' => 'application/x-msmediaview',
+ 'mwf' => 'application/vnd.mfer',
+ 'mxf' => 'application/mxf',
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'mxml' => 'application/xv+xml',
+ 'mxs' => 'application/vnd.triscape.mxs',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
+ 'n3' => 'text/n3',
+ 'nb' => 'application/mathematica',
+ 'nbp' => 'application/vnd.wolfram.player',
+ 'nc' => 'application/x-netcdf',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
+ 'nml' => 'application/vnd.enliven',
+ 'nnd' => 'application/vnd.noblenet-directory',
+ 'nns' => 'application/vnd.noblenet-sealer',
+ 'nnw' => 'application/vnd.noblenet-web',
+ 'npx' => 'image/vnd.net-fpx',
+ 'nsf' => 'application/vnd.lotus-notes',
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
+ 'oas' => 'application/vnd.fujitsu.oasys',
+ 'obd' => 'application/x-msbinder',
+ 'oda' => 'application/oda',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'onepkg' => 'application/onenote',
+ 'onetmp' => 'application/onenote',
+ 'onetoc' => 'application/onenote',
+ 'onetoc2' => 'application/onenote',
+ 'opf' => 'application/oebps-package+xml',
+ 'oprc' => 'application/vnd.palm',
+ 'org' => 'application/vnd.lotus-organizer',
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
+ 'otf' => 'application/x-font-otf',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'oxt' => 'application/vnd.openofficeorg.extension',
+ 'p' => 'text/x-pascal',
+ 'p10' => 'application/pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'p7b' => 'application/x-pkcs7-certificates',
+ 'p7c' => 'application/pkcs7-mime',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'p7s' => 'application/pkcs7-signature',
+ 'p8' => 'application/pkcs8',
+ 'pas' => 'text/x-pascal',
+ 'paw' => 'application/vnd.pawaafile',
+ 'pbd' => 'application/vnd.powerbuilder6',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pcf' => 'application/x-font-pcf',
+ 'pcl' => 'application/vnd.hp-pcl',
+ 'pclxl' => 'application/vnd.hp-pclxl',
+ 'pct' => 'image/x-pict',
+ 'pcurl' => 'application/vnd.curl.pcurl',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/vnd.palm',
+ 'pdf' => 'application/pdf',
+ 'pfa' => 'application/x-font-type1',
+ 'pfb' => 'application/x-font-type1',
+ 'pfm' => 'application/x-font-type1',
+ 'pfr' => 'application/font-tdpfr',
+ 'pfx' => 'application/x-pkcs12',
+ 'pgm' => 'image/x-portable-graymap',
+ 'pgn' => 'application/x-chess-pgn',
+ 'pgp' => 'application/pgp-encrypted',
+ 'php' => 'text/x-php',
+ 'phps' => 'application/x-httpd-phps',
+ 'pic' => 'image/x-pict',
+ 'pkg' => 'application/octet-stream',
+ 'pki' => 'application/pkixcmp',
+ 'pkipath' => 'application/pkix-pkipath',
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
+ 'plc' => 'application/vnd.mobius.plc',
+ 'plf' => 'application/vnd.pocketlearn',
+ 'pls' => 'application/pls+xml',
+ 'pml' => 'application/vnd.ctc-posml',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'portpkg' => 'application/vnd.macports.portpkg',
+ 'pot' => 'application/vnd.ms-powerpoint',
+ 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12',
+ 'ppd' => 'application/vnd.cups-ppd',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'pqa' => 'application/vnd.palm',
+ 'prc' => 'application/x-mobipocket-ebook',
+ 'pre' => 'application/vnd.lotus-freelance',
+ 'prf' => 'application/pics-rules',
+ 'ps' => 'application/postscript',
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
+ 'psd' => 'image/vnd.adobe.photoshop',
+ 'psf' => 'application/x-font-linux-psf',
+ 'pskcxml' => 'application/pskc+xml',
+ 'ptid' => 'application/vnd.pvi.ptid1',
+ 'pub' => 'application/x-mspublisher',
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
+ 'pwn' => 'application/vnd.3m.post-it-notes',
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
+ 'qam' => 'application/vnd.epson.quickanime',
+ 'qbo' => 'application/vnd.intu.qbo',
+ 'qfx' => 'application/vnd.intu.qfx',
+ 'qps' => 'application/vnd.publishare-delta-tree',
+ 'qt' => 'video/quicktime',
+ 'qwd' => 'application/vnd.quark.quarkxpress',
+ 'qwt' => 'application/vnd.quark.quarkxpress',
+ 'qxb' => 'application/vnd.quark.quarkxpress',
+ 'qxd' => 'application/vnd.quark.quarkxpress',
+ 'qxl' => 'application/vnd.quark.quarkxpress',
+ 'qxt' => 'application/vnd.quark.quarkxpress',
+ 'ra' => 'audio/x-pn-realaudio',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rb' => 'text/plain',
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
+ 'rdf' => 'application/rdf+xml',
+ 'rdz' => 'application/vnd.data-vision.rdz',
+ 'rep' => 'application/vnd.businessobjects',
+ 'res' => 'application/x-dtbresource+xml',
+ 'resx' => 'text/xml',
+ 'rgb' => 'image/x-rgb',
+ 'rif' => 'application/reginfo+xml',
+ 'rip' => 'audio/vnd.rip',
+ 'rl' => 'application/resource-lists+xml',
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
+ 'rld' => 'application/resource-lists-diff+xml',
+ 'rm' => 'application/vnd.rn-realmedia',
+ 'rmi' => 'audio/midi',
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
+ 'rnc' => 'application/relax-ng-compact-syntax',
+ 'roff' => 'text/troff',
+ 'rp9' => 'application/vnd.cloanto.rp9',
+ 'rpss' => 'application/vnd.nokia.radio-presets',
+ 'rpst' => 'application/vnd.nokia.radio-preset',
+ 'rq' => 'application/sparql-query',
+ 'rs' => 'application/rls-services+xml',
+ 'rsd' => 'application/rsd+xml',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'rtx' => 'text/richtext',
+ 's' => 'text/x-asm',
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
+ 'sbml' => 'application/sbml+xml',
+ 'sc' => 'application/vnd.ibm.secure-container',
+ 'scd' => 'application/x-msschedule',
+ 'scm' => 'application/vnd.lotus-screencam',
+ 'scq' => 'application/scvp-cv-request',
+ 'scs' => 'application/scvp-cv-response',
+ 'scurl' => 'text/vnd.curl.scurl',
+ 'sda' => 'application/vnd.stardivision.draw',
+ 'sdc' => 'application/vnd.stardivision.calc',
+ 'sdd' => 'application/vnd.stardivision.impress',
+ 'sdkd' => 'application/vnd.solent.sdkm+xml',
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
+ 'sdp' => 'application/sdp',
+ 'sdw' => 'application/vnd.stardivision.writer',
+ 'see' => 'application/vnd.seemail',
+ 'seed' => 'application/vnd.fdsn.seed',
+ 'sema' => 'application/vnd.sema',
+ 'semd' => 'application/vnd.semd',
+ 'semf' => 'application/vnd.semf',
+ 'ser' => 'application/java-serialized-object',
+ 'setpay' => 'application/set-payment-initiation',
+ 'setreg' => 'application/set-registration-initiation',
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
+ 'sfs' => 'application/vnd.spotfire.sfs',
+ 'sgl' => 'application/vnd.stardivision.writer-global',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'shf' => 'application/shf+xml',
+ 'sig' => 'application/pgp-signature',
+ 'silo' => 'model/mesh',
+ 'sis' => 'application/vnd.symbian.install',
+ 'sisx' => 'application/vnd.symbian.install',
+ 'sit' => 'application/x-stuffit',
+ 'sitx' => 'application/x-stuffitx',
+ 'skd' => 'application/vnd.koan',
+ 'skm' => 'application/vnd.koan',
+ 'skp' => 'application/vnd.koan',
+ 'skt' => 'application/vnd.koan',
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'slt' => 'application/vnd.epson.salt',
+ 'sm' => 'application/vnd.stepmania.stepchart',
+ 'smf' => 'application/vnd.stardivision.math',
+ 'smi' => 'application/smil+xml',
+ 'smil' => 'application/smil+xml',
+ 'snd' => 'audio/basic',
+ 'snf' => 'application/x-font-snf',
+ 'so' => 'application/octet-stream',
+ 'spc' => 'application/x-pkcs7-certificates',
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
+ 'spl' => 'application/x-futuresplash',
+ 'spot' => 'text/vnd.in3d.spot',
+ 'spp' => 'application/scvp-vp-response',
+ 'spq' => 'application/scvp-vp-request',
+ 'spx' => 'audio/ogg',
+ 'src' => 'application/x-wais-source',
+ 'sru' => 'application/sru+xml',
+ 'srx' => 'application/sparql-results+xml',
+ 'sse' => 'application/vnd.kodak-descriptor',
+ 'ssf' => 'application/vnd.epson.ssf',
+ 'ssml' => 'application/ssml+xml',
+ 'st' => 'application/vnd.sailingtracker.track',
+ 'stc' => 'application/vnd.sun.xml.calc.template',
+ 'std' => 'application/vnd.sun.xml.draw.template',
+ 'stf' => 'application/vnd.wt.stf',
+ 'sti' => 'application/vnd.sun.xml.impress.template',
+ 'stk' => 'application/hyperstudio',
+ 'stl' => 'application/vnd.ms-pki.stl',
+ 'str' => 'application/vnd.pg.format',
+ 'stw' => 'application/vnd.sun.xml.writer.template',
+ 'sub' => 'image/vnd.dvb.subtitle',
+ 'sus' => 'application/vnd.sus-calendar',
+ 'susp' => 'application/vnd.sus-calendar',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 'svc' => 'application/vnd.dvb.service',
+ 'svd' => 'application/vnd.svd',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg+xml',
+ 'swa' => 'application/x-director',
+ 'swf' => 'application/x-shockwave-flash',
+ 'swi' => 'application/vnd.aristanetworks.swi',
+ 'sxc' => 'application/vnd.sun.xml.calc',
+ 'sxd' => 'application/vnd.sun.xml.draw',
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
+ 'sxi' => 'application/vnd.sun.xml.impress',
+ 'sxm' => 'application/vnd.sun.xml.math',
+ 'sxw' => 'application/vnd.sun.xml.writer',
+ 't' => 'text/troff',
+ 'tao' => 'application/vnd.tao.intent-module-archive',
+ 'tar' => 'application/x-tar',
+ 'tcap' => 'application/vnd.3gpp2.tcap',
+ 'tcl' => 'application/x-tcl',
+ 'teacher' => 'application/vnd.smart.teacher',
+ 'tei' => 'application/tei+xml',
+ 'teicorpus' => 'application/tei+xml',
+ 'tex' => 'application/x-tex',
+ 'texi' => 'application/x-texinfo',
+ 'texinfo' => 'application/x-texinfo',
+ 'text' => 'text/plain',
+ 'tfi' => 'application/thraud+xml',
+ 'tfm' => 'application/x-tex-tfm',
+ 'thmx' => 'application/vnd.ms-officetheme',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'tmo' => 'application/vnd.tmobile-livetv',
+ 'torrent' => 'application/x-bittorrent',
+ 'tpl' => 'application/vnd.groove-tool-template',
+ 'tpt' => 'application/vnd.trid.tpt',
+ 'tr' => 'text/troff',
+ 'tra' => 'application/vnd.trueapp',
+ 'trm' => 'application/x-msterminal',
+ 'tsd' => 'application/timestamped-data',
+ 'tsv' => 'text/tab-separated-values',
+ 'ttc' => 'application/x-font-ttf',
+ 'ttf' => 'application/x-font-ttf',
+ 'ttl' => 'text/turtle',
+ 'twd' => 'application/vnd.simtech-mindmapper',
+ 'twds' => 'application/vnd.simtech-mindmapper',
+ 'txd' => 'application/vnd.genomatix.tuxedo',
+ 'txf' => 'application/vnd.mobius.txf',
+ 'txt' => 'text/plain',
+ 'u32' => 'application/x-authorware-bin',
+ 'udeb' => 'application/x-debian-package',
+ 'ufd' => 'application/vnd.ufdl',
+ 'ufdl' => 'application/vnd.ufdl',
+ 'umj' => 'application/vnd.umajin',
+ 'unityweb' => 'application/vnd.unity',
+ 'uoml' => 'application/vnd.uoml+xml',
+ 'uri' => 'text/uri-list',
+ 'uris' => 'text/uri-list',
+ 'urls' => 'text/uri-list',
+ 'ustar' => 'application/x-ustar',
+ 'utz' => 'application/vnd.uiq.theme',
+ 'uu' => 'text/x-uuencode',
+ 'uva' => 'audio/vnd.dece.audio',
+ 'uvd' => 'application/vnd.dece.data',
+ 'uvf' => 'application/vnd.dece.data',
+ 'uvg' => 'image/vnd.dece.graphic',
+ 'uvh' => 'video/vnd.dece.hd',
+ 'uvi' => 'image/vnd.dece.graphic',
+ 'uvm' => 'video/vnd.dece.mobile',
+ 'uvp' => 'video/vnd.dece.pd',
+ 'uvs' => 'video/vnd.dece.sd',
+ 'uvt' => 'application/vnd.dece.ttml+xml',
+ 'uvu' => 'video/vnd.uvvu.mp4',
+ 'uvv' => 'video/vnd.dece.video',
+ 'uvva' => 'audio/vnd.dece.audio',
+ 'uvvd' => 'application/vnd.dece.data',
+ 'uvvf' => 'application/vnd.dece.data',
+ 'uvvg' => 'image/vnd.dece.graphic',
+ 'uvvh' => 'video/vnd.dece.hd',
+ 'uvvi' => 'image/vnd.dece.graphic',
+ 'uvvm' => 'video/vnd.dece.mobile',
+ 'uvvp' => 'video/vnd.dece.pd',
+ 'uvvs' => 'video/vnd.dece.sd',
+ 'uvvt' => 'application/vnd.dece.ttml+xml',
+ 'uvvu' => 'video/vnd.uvvu.mp4',
+ 'uvvv' => 'video/vnd.dece.video',
+ 'uvvx' => 'application/vnd.dece.unspecified',
+ 'uvx' => 'application/vnd.dece.unspecified',
+ 'vcd' => 'application/x-cdlink',
+ 'vcf' => 'text/x-vcard',
+ 'vcg' => 'application/vnd.groove-vcard',
+ 'vcs' => 'text/x-vcalendar',
+ 'vcx' => 'application/vnd.vcx',
+ 'vis' => 'application/vnd.visionary',
+ 'viv' => 'video/vnd.vivo',
+ 'vor' => 'application/vnd.stardivision.writer',
+ 'vox' => 'application/x-authorware-bin',
+ 'vrml' => 'model/vrml',
+ 'vsd' => 'application/vnd.visio',
+ 'vsf' => 'application/vnd.vsf',
+ 'vss' => 'application/vnd.visio',
+ 'vst' => 'application/vnd.visio',
+ 'vsw' => 'application/vnd.visio',
+ 'vtu' => 'model/vnd.vtu',
+ 'vxml' => 'application/voicexml+xml',
+ 'w3d' => 'application/x-director',
+ 'wad' => 'application/x-doom',
+ 'wav' => 'audio/x-wav',
+ 'wax' => 'audio/x-ms-wax',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
+ 'wbxml' => 'application/vnd.wap.wbxml',
+ 'wcm' => 'application/vnd.ms-works',
+ 'wdb' => 'application/vnd.ms-works',
+ 'weba' => 'audio/webm',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wg' => 'application/vnd.pmi.widget',
+ 'wgt' => 'application/widget',
+ 'wks' => 'application/vnd.ms-works',
+ 'wm' => 'video/x-ms-wm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmd' => 'application/x-ms-wmd',
+ 'wmf' => 'application/x-msmetafile',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmlc' => 'application/vnd.wap.wmlc',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wmx' => 'video/x-ms-wmx',
+ 'wmz' => 'application/x-ms-wmz',
+ 'woff' => 'application/x-font-woff',
+ 'wpd' => 'application/vnd.wordperfect',
+ 'wpl' => 'application/vnd.ms-wpl',
+ 'wps' => 'application/vnd.ms-works',
+ 'wqd' => 'application/vnd.wqd',
+ 'wri' => 'application/x-mswrite',
+ 'wrl' => 'model/vrml',
+ 'wsdl' => 'application/wsdl+xml',
+ 'wspolicy' => 'application/wspolicy+xml',
+ 'wtb' => 'application/vnd.webturbo',
+ 'wvx' => 'video/x-ms-wvx',
+ 'x32' => 'application/x-authorware-bin',
+ 'x3d' => 'application/vnd.hzn-3d-crossword',
+ 'xap' => 'application/x-silverlight-app',
+ 'xar' => 'application/vnd.xara',
+ 'xbap' => 'application/x-ms-xbap',
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
+ 'xbm' => 'image/x-xbitmap',
+ 'xdf' => 'application/xcap-diff+xml',
+ 'xdm' => 'application/vnd.syncml.dm+xml',
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
+ 'xdssc' => 'application/dssc+xml',
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
+ 'xenc' => 'application/xenc+xml',
+ 'xer' => 'application/patch-ops-error+xml',
+ 'xfdf' => 'application/vnd.adobe.xfdf',
+ 'xfdl' => 'application/vnd.xfdl',
+ 'xht' => 'application/xhtml+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xhvml' => 'application/xv+xml',
+ 'xif' => 'image/vnd.xiff',
+ 'xla' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12',
+ 'xlc' => 'application/vnd.ms-excel',
+ 'xlm' => 'application/vnd.ms-excel',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlw' => 'application/vnd.ms-excel',
+ 'xml' => 'application/xml',
+ 'xo' => 'application/vnd.olpc-sugar',
+ 'xop' => 'application/xop+xml',
+ 'xpi' => 'application/x-xpinstall',
+ 'xpm' => 'image/x-xpixmap',
+ 'xpr' => 'application/vnd.is-xpr',
+ 'xps' => 'application/vnd.ms-xpsdocument',
+ 'xpw' => 'application/vnd.intercon.formnet',
+ 'xpx' => 'application/vnd.intercon.formnet',
+ 'xsl' => 'application/xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xsm' => 'application/vnd.syncml+xml',
+ 'xspf' => 'application/xspf+xml',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xvm' => 'application/xv+xml',
+ 'xvml' => 'application/xv+xml',
+ 'xwd' => 'image/x-xwindowdump',
+ 'xyz' => 'chemical/x-xyz',
+ 'yaml' => 'text/yaml',
+ 'yang' => 'application/yang',
+ 'yin' => 'application/yin+xml',
+ 'yml' => 'text/yaml',
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
+ 'zip' => 'application/zip',
+ 'zir' => 'application/vnd.zul',
+ 'zirz' => 'application/vnd.zul',
+ 'zmm' => 'application/vnd.handheld-entertainment+xml'
+ );
+
+ /**
+ * Get a singleton instance of the class
+ *
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Get a mimetype value from a file extension
+ *
+ * @param string $extension File extension
+ *
+ * @return string|null
+ *
+ */
+ public function fromExtension($extension)
+ {
+ $extension = strtolower($extension);
+
+ return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null;
+ }
+
+ /**
+ * Get a mimetype from a filename
+ *
+ * @param string $filename Filename to generate a mimetype from
+ *
+ * @return string|null
+ */
+ public function fromFilename($filename)
+ {
+ return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php
new file mode 100644
index 0000000..4b4e49d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Guzzle\Http\QueryAggregator;
+
+use Guzzle\Http\QueryString;
+
+/**
+ * Aggregates nested query string variables using commas
+ */
+class CommaAggregator implements QueryAggregatorInterface
+{
+ public function aggregate($key, $value, QueryString $query)
+ {
+ if ($query->isUrlEncoding()) {
+ return array($query->encodeValue($key) => implode(',', array_map(array($query, 'encodeValue'), $value)));
+ } else {
+ return array($key => implode(',', $value));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php
new file mode 100644
index 0000000..1bf1730
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Guzzle\Http\QueryAggregator;
+
+use Guzzle\Http\QueryString;
+
+/**
+ * Does not aggregate nested query string values and allows duplicates in the resulting array
+ *
+ * Example: http://test.com?q=1&q=2
+ */
+class DuplicateAggregator implements QueryAggregatorInterface
+{
+ public function aggregate($key, $value, QueryString $query)
+ {
+ if ($query->isUrlEncoding()) {
+ return array($query->encodeValue($key) => array_map(array($query, 'encodeValue'), $value));
+ } else {
+ return array($key => $value);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php
new file mode 100644
index 0000000..133ea2b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Http\QueryAggregator;
+
+use Guzzle\Http\QueryString;
+
+/**
+ * Aggregates nested query string variables using PHP style []
+ */
+class PhpAggregator implements QueryAggregatorInterface
+{
+ public function aggregate($key, $value, QueryString $query)
+ {
+ $ret = array();
+
+ foreach ($value as $k => $v) {
+ $k = "{$key}[{$k}]";
+ if (is_array($v)) {
+ $ret = array_merge($ret, self::aggregate($k, $v, $query));
+ } else {
+ $ret[$query->encodeValue($k)] = $query->encodeValue($v);
+ }
+ }
+
+ return $ret;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php
new file mode 100644
index 0000000..72bee62
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Guzzle\Http\QueryAggregator;
+
+use Guzzle\Http\QueryString;
+
+/**
+ * Interface used for aggregating nested query string variables into a flattened array of key value pairs
+ */
+interface QueryAggregatorInterface
+{
+ /**
+ * Aggregate multi-valued parameters into a flattened associative array
+ *
+ * @param string $key The name of the query string parameter
+ * @param array $value The values of the parameter
+ * @param QueryString $query The query string that is being aggregated
+ *
+ * @return array Returns an array of the combined values
+ */
+ public function aggregate($key, $value, QueryString $query);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/QueryString.php b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryString.php
new file mode 100644
index 0000000..38a2640
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/QueryString.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Http\QueryAggregator\DuplicateAggregator;
+use Guzzle\Http\QueryAggregator\QueryAggregatorInterface;
+use Guzzle\Http\QueryAggregator\PhpAggregator;
+
+/**
+ * Query string object to handle managing query string parameters and aggregating those parameters together as a string.
+ */
+class QueryString extends Collection
+{
+ /** @var string Used to URL encode with rawurlencode */
+ const RFC_3986 = 'RFC 3986';
+
+ /** @var string Used to encode with urlencode */
+ const FORM_URLENCODED = 'application/x-www-form-urlencoded';
+
+ /** @var string Constant used to create blank query string values (e.g. ?foo) */
+ const BLANK = "_guzzle_blank_";
+
+ /** @var string The query string field separator (e.g. '&') */
+ protected $fieldSeparator = '&';
+
+ /** @var string The query string value separator (e.g. '=') */
+ protected $valueSeparator = '=';
+
+ /** @var bool URL encode fields and values */
+ protected $urlEncode = 'RFC 3986';
+
+ /** @var QueryAggregatorInterface */
+ protected $aggregator;
+
+ /** @var array Cached PHP aggregator */
+ private static $defaultAggregator = null;
+
+ /**
+ * Parse a query string into a QueryString object
+ *
+ * @param string $query Query string to parse
+ *
+ * @return self
+ */
+ public static function fromString($query)
+ {
+ $q = new static();
+ if ($query === '') {
+ return $q;
+ }
+
+ $foundDuplicates = $foundPhpStyle = false;
+
+ foreach (explode('&', $query) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = rawurldecode($parts[0]);
+ if ($paramIsPhpStyleArray = substr($key, -2) == '[]') {
+ $foundPhpStyle = true;
+ $key = substr($key, 0, -2);
+ }
+ if (isset($parts[1])) {
+ $value = rawurldecode(str_replace('+', '%20', $parts[1]));
+ if (isset($q[$key])) {
+ $q->add($key, $value);
+ $foundDuplicates = true;
+ } elseif ($paramIsPhpStyleArray) {
+ $q[$key] = array($value);
+ } else {
+ $q[$key] = $value;
+ }
+ } else {
+ // Uses false by default to represent keys with no trailing "=" sign.
+ $q->add($key, false);
+ }
+ }
+
+ // Use the duplicate aggregator if duplicates were found and not using PHP style arrays
+ if ($foundDuplicates && !$foundPhpStyle) {
+ $q->setAggregator(new DuplicateAggregator());
+ }
+
+ return $q;
+ }
+
+ /**
+ * Convert the query string parameters to a query string string
+ *
+ * @return string
+ * @throws RuntimeException
+ */
+ public function __toString()
+ {
+ if (!$this->data) {
+ return '';
+ }
+
+ $queryList = array();
+ foreach ($this->prepareData($this->data) as $name => $value) {
+ $queryList[] = $this->convertKvp($name, $value);
+ }
+
+ return implode($this->fieldSeparator, $queryList);
+ }
+
+ /**
+ * Get the query string field separator
+ *
+ * @return string
+ */
+ public function getFieldSeparator()
+ {
+ return $this->fieldSeparator;
+ }
+
+ /**
+ * Get the query string value separator
+ *
+ * @return string
+ */
+ public function getValueSeparator()
+ {
+ return $this->valueSeparator;
+ }
+
+ /**
+ * Returns the type of URL encoding used by the query string
+ *
+ * One of: false, "RFC 3986", or "application/x-www-form-urlencoded"
+ *
+ * @return bool|string
+ */
+ public function getUrlEncoding()
+ {
+ return $this->urlEncode;
+ }
+
+ /**
+ * Returns true or false if using URL encoding
+ *
+ * @return bool
+ */
+ public function isUrlEncoding()
+ {
+ return $this->urlEncode !== false;
+ }
+
+ /**
+ * Provide a function for combining multi-valued query string parameters into a single or multiple fields
+ *
+ * @param null|QueryAggregatorInterface $aggregator Pass in a QueryAggregatorInterface object to handle converting
+ * deeply nested query string variables into a flattened array.
+ * Pass null to use the default PHP style aggregator. For legacy
+ * reasons, this function accepts a callable that must accepts a
+ * $key, $value, and query object.
+ * @return self
+ * @see \Guzzle\Http\QueryString::aggregateUsingComma()
+ */
+ public function setAggregator(QueryAggregatorInterface $aggregator = null)
+ {
+ // Use the default aggregator if none was set
+ if (!$aggregator) {
+ if (!self::$defaultAggregator) {
+ self::$defaultAggregator = new PhpAggregator();
+ }
+ $aggregator = self::$defaultAggregator;
+ }
+
+ $this->aggregator = $aggregator;
+
+ return $this;
+ }
+
+ /**
+ * Set whether or not field names and values should be rawurlencoded
+ *
+ * @param bool|string $encode Set to TRUE to use RFC 3986 encoding (rawurlencode), false to disable encoding, or
+ * form_urlencoding to use application/x-www-form-urlencoded encoding (urlencode)
+ * @return self
+ */
+ public function useUrlEncoding($encode)
+ {
+ $this->urlEncode = ($encode === true) ? self::RFC_3986 : $encode;
+
+ return $this;
+ }
+
+ /**
+ * Set the query string separator
+ *
+ * @param string $separator The query string separator that will separate fields
+ *
+ * @return self
+ */
+ public function setFieldSeparator($separator)
+ {
+ $this->fieldSeparator = $separator;
+
+ return $this;
+ }
+
+ /**
+ * Set the query string value separator
+ *
+ * @param string $separator The query string separator that will separate values from fields
+ *
+ * @return self
+ */
+ public function setValueSeparator($separator)
+ {
+ $this->valueSeparator = $separator;
+
+ return $this;
+ }
+
+ /**
+ * Returns an array of url encoded field names and values
+ *
+ * @return array
+ */
+ public function urlEncode()
+ {
+ return $this->prepareData($this->data);
+ }
+
+ /**
+ * URL encodes a value based on the url encoding type of the query string object
+ *
+ * @param string $value Value to encode
+ *
+ * @return string
+ */
+ public function encodeValue($value)
+ {
+ if ($this->urlEncode == self::RFC_3986) {
+ return rawurlencode($value);
+ } elseif ($this->urlEncode == self::FORM_URLENCODED) {
+ return urlencode($value);
+ } else {
+ return (string) $value;
+ }
+ }
+
+ /**
+ * Url encode parameter data and convert nested query strings into a flattened hash.
+ *
+ * @param array $data The data to encode
+ *
+ * @return array Returns an array of encoded values and keys
+ */
+ protected function prepareData(array $data)
+ {
+ // If no aggregator is present then set the default
+ if (!$this->aggregator) {
+ $this->setAggregator(null);
+ }
+
+ $temp = array();
+ foreach ($data as $key => $value) {
+ if ($value === false || $value === null) {
+ // False and null will not include the "=". Use an empty string to include the "=".
+ $temp[$this->encodeValue($key)] = $value;
+ } elseif (is_array($value)) {
+ $temp = array_merge($temp, $this->aggregator->aggregate($key, $value, $this));
+ } else {
+ $temp[$this->encodeValue($key)] = $this->encodeValue($value);
+ }
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Converts a key value pair that can contain strings, nulls, false, or arrays
+ * into a single string.
+ *
+ * @param string $name Name of the field
+ * @param mixed $value Value of the field
+ * @return string
+ */
+ private function convertKvp($name, $value)
+ {
+ if ($value === self::BLANK || $value === null || $value === false) {
+ return $name;
+ } elseif (!is_array($value)) {
+ return $name . $this->valueSeparator . $value;
+ }
+
+ $result = '';
+ foreach ($value as $v) {
+ $result .= $this->convertKvp($name, $v) . $this->fieldSeparator;
+ }
+
+ return rtrim($result, $this->fieldSeparator);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php b/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php
new file mode 100644
index 0000000..ef28273
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Stream\StreamInterface;
+
+/**
+ * EntityBody decorator used to return only a subset of an entity body
+ */
+class ReadLimitEntityBody extends AbstractEntityBodyDecorator
+{
+ /** @var int Limit the number of bytes that can be read */
+ protected $limit;
+
+ /** @var int Offset to start reading from */
+ protected $offset;
+
+ /**
+ * @param EntityBodyInterface $body Body to wrap
+ * @param int $limit Total number of bytes to allow to be read from the stream
+ * @param int $offset Position to seek to before reading (only works on seekable streams)
+ */
+ public function __construct(EntityBodyInterface $body, $limit, $offset = 0)
+ {
+ parent::__construct($body);
+ $this->setLimit($limit)->setOffset($offset);
+ }
+
+ /**
+ * Returns only a subset of the decorated entity body when cast as a string
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ if (!$this->body->isReadable() ||
+ (!$this->body->isSeekable() && $this->body->isConsumed())
+ ) {
+ return '';
+ }
+
+ $originalPos = $this->body->ftell();
+ $this->body->seek($this->offset);
+ $data = '';
+ while (!$this->feof()) {
+ $data .= $this->read(1048576);
+ }
+ $this->body->seek($originalPos);
+
+ return (string) $data ?: '';
+ }
+
+ public function isConsumed()
+ {
+ return $this->body->isConsumed() ||
+ ($this->body->ftell() >= $this->offset + $this->limit);
+ }
+
+ /**
+ * Returns the Content-Length of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getContentLength()
+ {
+ $length = $this->body->getContentLength();
+
+ return $length === false
+ ? $this->limit
+ : min($this->limit, min($length, $this->offset + $this->limit) - $this->offset);
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited entity body
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return $whence === SEEK_SET
+ ? $this->body->seek(max($this->offset, min($this->offset + $this->limit, $offset)))
+ : false;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @return self
+ */
+ public function setOffset($offset)
+ {
+ $this->body->seek($offset);
+ $this->offset = $offset;
+
+ return $this;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the stream
+ *
+ * @param int $limit Total number of bytes to allow to be read from the stream
+ *
+ * @return self
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+
+ return $this;
+ }
+
+ public function read($length)
+ {
+ // Check if the current position is less than the total allowed bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->body->ftell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte limit is not exceeded
+ return $this->body->read(min($remaining, $length));
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php
new file mode 100644
index 0000000..1a824b8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php
@@ -0,0 +1,250 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\Url;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Exception\TooManyRedirectsException;
+use Guzzle\Http\Exception\CouldNotRewindStreamException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Plugin to implement HTTP redirects. Can redirect like a web browser or using strict RFC 2616 compliance
+ */
+class RedirectPlugin implements EventSubscriberInterface
+{
+ const REDIRECT_COUNT = 'redirect.count';
+ const MAX_REDIRECTS = 'redirect.max';
+ const STRICT_REDIRECTS = 'redirect.strict';
+ const PARENT_REQUEST = 'redirect.parent_request';
+ const DISABLE = 'redirect.disable';
+
+ /**
+ * @var int Default number of redirects allowed when no setting is supplied by a request
+ */
+ protected $defaultMaxRedirects = 5;
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.sent' => array('onRequestSent', 100),
+ 'request.clone' => 'cleanupRequest',
+ 'request.before_send' => 'cleanupRequest'
+ );
+ }
+
+ /**
+ * Clean up the parameters of a request when it is cloned
+ *
+ * @param Event $event Event emitted
+ */
+ public function cleanupRequest(Event $event)
+ {
+ $params = $event['request']->getParams();
+ unset($params[self::REDIRECT_COUNT]);
+ unset($params[self::PARENT_REQUEST]);
+ }
+
+ /**
+ * Called when a request receives a redirect response
+ *
+ * @param Event $event Event emitted
+ */
+ public function onRequestSent(Event $event)
+ {
+ $response = $event['response'];
+ $request = $event['request'];
+
+ // Only act on redirect requests with Location headers
+ if (!$response || $request->getParams()->get(self::DISABLE)) {
+ return;
+ }
+
+ // Trace the original request based on parameter history
+ $original = $this->getOriginalRequest($request);
+
+ // Terminating condition to set the effective response on the original request
+ if (!$response->isRedirect() || !$response->hasHeader('Location')) {
+ if ($request !== $original) {
+ // This is a terminating redirect response, so set it on the original request
+ $response->getParams()->set(self::REDIRECT_COUNT, $original->getParams()->get(self::REDIRECT_COUNT));
+ $original->setResponse($response);
+ $response->setEffectiveUrl($request->getUrl());
+ }
+ return;
+ }
+
+ $this->sendRedirectRequest($original, $request, $response);
+ }
+
+ /**
+ * Get the original request that initiated a series of redirects
+ *
+ * @param RequestInterface $request Request to get the original request from
+ *
+ * @return RequestInterface
+ */
+ protected function getOriginalRequest(RequestInterface $request)
+ {
+ $original = $request;
+ // The number of redirects is held on the original request, so determine which request that is
+ while ($parent = $original->getParams()->get(self::PARENT_REQUEST)) {
+ $original = $parent;
+ }
+
+ return $original;
+ }
+
+ /**
+ * Create a redirect request for a specific request object
+ *
+ * Takes into account strict RFC compliant redirection (e.g. redirect POST with POST) vs doing what most clients do
+ * (e.g. redirect POST with GET).
+ *
+ * @param RequestInterface $request Request being redirected
+ * @param RequestInterface $original Original request
+ * @param int $statusCode Status code of the redirect
+ * @param string $location Location header of the redirect
+ *
+ * @return RequestInterface Returns a new redirect request
+ * @throws CouldNotRewindStreamException If the body needs to be rewound but cannot
+ */
+ protected function createRedirectRequest(
+ RequestInterface $request,
+ $statusCode,
+ $location,
+ RequestInterface $original
+ ) {
+ $redirectRequest = null;
+ $strict = $original->getParams()->get(self::STRICT_REDIRECTS);
+
+ // Switch method to GET for 303 redirects. 301 and 302 redirects also switch to GET unless we are forcing RFC
+ // compliance to emulate what most browsers do. NOTE: IE only switches methods on 301/302 when coming from a POST.
+ if ($request instanceof EntityEnclosingRequestInterface && ($statusCode == 303 || (!$strict && $statusCode <= 302))) {
+ $redirectRequest = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET');
+ } else {
+ $redirectRequest = clone $request;
+ }
+
+ $redirectRequest->setIsRedirect(true);
+ // Always use the same response body when redirecting
+ $redirectRequest->setResponseBody($request->getResponseBody());
+
+ $location = Url::factory($location);
+ // If the location is not absolute, then combine it with the original URL
+ if (!$location->isAbsolute()) {
+ $originalUrl = $redirectRequest->getUrl(true);
+ // Remove query string parameters and just take what is present on the redirect Location header
+ $originalUrl->getQuery()->clear();
+ $location = $originalUrl->combine((string) $location, true);
+ }
+
+ $redirectRequest->setUrl($location);
+
+ // Add the parent request to the request before it sends (make sure it's before the onRequestClone event too)
+ $redirectRequest->getEventDispatcher()->addListener(
+ 'request.before_send',
+ $func = function ($e) use (&$func, $request, $redirectRequest) {
+ $redirectRequest->getEventDispatcher()->removeListener('request.before_send', $func);
+ $e['request']->getParams()->set(RedirectPlugin::PARENT_REQUEST, $request);
+ }
+ );
+
+ // Rewind the entity body of the request if needed
+ if ($redirectRequest instanceof EntityEnclosingRequestInterface && $redirectRequest->getBody()) {
+ $body = $redirectRequest->getBody();
+ // Only rewind the body if some of it has been read already, and throw an exception if the rewind fails
+ if ($body->ftell() && !$body->rewind()) {
+ throw new CouldNotRewindStreamException(
+ 'Unable to rewind the non-seekable entity body of the request after redirecting. cURL probably '
+ . 'sent part of body before the redirect occurred. Try adding acustom rewind function using on the '
+ . 'entity body of the request using setRewindFunction().'
+ );
+ }
+ }
+
+ return $redirectRequest;
+ }
+
+ /**
+ * Prepare the request for redirection and enforce the maximum number of allowed redirects per client
+ *
+ * @param RequestInterface $original Original request
+ * @param RequestInterface $request Request to prepare and validate
+ * @param Response $response The current response
+ *
+ * @return RequestInterface
+ */
+ protected function prepareRedirection(RequestInterface $original, RequestInterface $request, Response $response)
+ {
+ $params = $original->getParams();
+ // This is a new redirect, so increment the redirect counter
+ $current = $params[self::REDIRECT_COUNT] + 1;
+ $params[self::REDIRECT_COUNT] = $current;
+ // Use a provided maximum value or default to a max redirect count of 5
+ $max = isset($params[self::MAX_REDIRECTS]) ? $params[self::MAX_REDIRECTS] : $this->defaultMaxRedirects;
+
+ // Throw an exception if the redirect count is exceeded
+ if ($current > $max) {
+ $this->throwTooManyRedirectsException($original, $max);
+ return false;
+ } else {
+ // Create a redirect request based on the redirect rules set on the request
+ return $this->createRedirectRequest(
+ $request,
+ $response->getStatusCode(),
+ trim($response->getLocation()),
+ $original
+ );
+ }
+ }
+
+ /**
+ * Send a redirect request and handle any errors
+ *
+ * @param RequestInterface $original The originating request
+ * @param RequestInterface $request The current request being redirected
+ * @param Response $response The response of the current request
+ *
+ * @throws BadResponseException|\Exception
+ */
+ protected function sendRedirectRequest(RequestInterface $original, RequestInterface $request, Response $response)
+ {
+ // Validate and create a redirect request based on the original request and current response
+ if ($redirectRequest = $this->prepareRedirection($original, $request, $response)) {
+ try {
+ $redirectRequest->send();
+ } catch (BadResponseException $e) {
+ $e->getResponse();
+ if (!$e->getResponse()) {
+ throw $e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Throw a too many redirects exception for a request
+ *
+ * @param RequestInterface $original Request
+ * @param int $max Max allowed redirects
+ *
+ * @throws TooManyRedirectsException when too many redirects have been issued
+ */
+ protected function throwTooManyRedirectsException(RequestInterface $original, $max)
+ {
+ $original->getEventDispatcher()->addListener(
+ 'request.complete',
+ $func = function ($e) use (&$func, $original, $max) {
+ $original->getEventDispatcher()->removeListener('request.complete', $func);
+ $str = "{$max} redirects were issued for this request:\n" . $e['request']->getRawHeaders();
+ throw new TooManyRedirectsException($str);
+ }
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem b/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem
new file mode 100644
index 0000000..18ce703
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem
@@ -0,0 +1,3870 @@
+##
+## Bundle of CA Root Certificates
+##
+## Certificate data from Mozilla downloaded on: Wed Aug 13 21:49:32 2014
+##
+## This is a bundle of X.509 certificates of public Certificate Authorities
+## (CA). These were automatically extracted from Mozilla's root certificates
+## file (certdata.txt). This file can be found in the mozilla source tree:
+## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
+##
+## It contains the certificates in PEM format and therefore
+## can be directly used with curl / libcurl / php_curl, or with
+## an Apache+mod_ssl webserver for SSL client authentication.
+## Just configure this file as the SSLCACertificateFile.
+##
+## Conversion done with mk-ca-bundle.pl verison 1.22.
+## SHA1: bf2c15b3019e696660321d2227d942936dc50aa7
+##
+
+
+GTE CyberTrust Global Root
+==========================
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz
+MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0
+IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u
+sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql
+HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID
+AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW
+M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF
+NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+
+Thawte Server CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE
+AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j
+b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV
+BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u
+c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG
+A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
+ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl
+/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7
+1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J
+GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ
+GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+
+Thawte Premium Server CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
+AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
+ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
+AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
+VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
+aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
+cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
+aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
+Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
+qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
+SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
+8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
+UCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+
+Equifax Secure CA
+=================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
+ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
+B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
+fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
+8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
+CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
+spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
+zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
+BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
+70+sB3c4
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA
+TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah
+WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf
+Tqj/ZA1k
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G2
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO
+FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71
+lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB
+MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT
+1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD
+Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9
+-----END CERTIFICATE-----
+
+GlobalSign Root CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
+GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
+b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
+VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
+DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
+THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
+Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
+c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
+gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
+Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
+j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
+hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
+X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
+ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
+s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
+S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
+TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
+ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
+YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
+BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
+9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
+01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
+9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+
+ValiCert Class 1 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
+MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
+GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
+DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
+lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
+icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
+Orf1LXLI
+-----END CERTIFICATE-----
+
+ValiCert Class 2 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
+CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
+ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
+SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
+UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
+W9ViH0Pd
+-----END CERTIFICATE-----
+
+RSA Root Certificate 1
+======================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td
+3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H
+BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs
+3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF
+V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r
+on+jjBXu
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1
+EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc
+cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw
+EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj
+055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f
+j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0
+xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa
+t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+
+Verisign Class 4 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS
+tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM
+8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW
+Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX
+Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt
+mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd
+RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG
+UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+
+Entrust.net Secure Server CA
+============================
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
+ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
+A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
+eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
+dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
+aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
+gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
+ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
+CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
+dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
+NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
+HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
+BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
+Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
+n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+
+Entrust.net Premium 2048 Secure Server CA
+=========================================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
+NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
+KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
+T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
+J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
+nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+
+Baltimore CyberTrust Root
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
+ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
+ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
+SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
+dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
+uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
+UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
+G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
+XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
+l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
+VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
+cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
+hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
+Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
+RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+Equifax Secure Global eBusiness CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp
+bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx
+HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds
+b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV
+PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN
+qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn
+hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
+BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs
+MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN
+I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY
+NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 1
+=============================
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB
+LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE
+ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz
+IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ
+1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a
+IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk
+MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW
+Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF
+AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5
+lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+
+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+
+AddTrust Low-Value Services Root
+================================
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU
+cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw
+CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO
+ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6
+54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr
+oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1
+Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui
+GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w
+HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT
+RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw
+HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt
+ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph
+iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr
+mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj
+ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+
+AddTrust External Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD
+VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw
+NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU
+cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg
+Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821
++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw
+Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo
+aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy
+2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7
+7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk
+VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl
+j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355
+e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u
+G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+
+AddTrust Public Services Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU
+cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ
+BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l
+dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu
+nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i
+d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG
+Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw
+HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G
+A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G
+A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4
+JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL
++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9
+Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H
+EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+
+AddTrust Qualified Certificates Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU
+cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx
+CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ
+IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx
+64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3
+KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o
+L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR
+wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU
+MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE
+BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y
+azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG
+GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze
+RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB
+iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=
+-----END CERTIFICATE-----
+
+Entrust Root Certification Authority
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
+BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
+b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
+A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
+MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
+MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
+Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
+dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
+A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
+Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
+j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
+rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
+MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
+hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
+Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
+v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
+W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
+tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+RSA Security 2048 v3
+====================
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK
+ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy
+MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb
+BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7
+Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb
+WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH
+KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP
++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/
+MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E
+FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY
+v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj
+0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj
+VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395
+nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA
+pKnXwiJPZ9d37CAFYd4=
+-----END CERTIFICATE-----
+
+GeoTrust Global CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw
+MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo
+BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet
+8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc
+T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU
+vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk
+DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q
+zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4
+d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2
+mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p
+XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm
+Mw==
+-----END CERTIFICATE-----
+
+GeoTrust Global CA 2
+====================
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw
+MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/
+NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k
+LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA
+Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b
+HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH
+K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7
+srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh
+ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL
+OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC
+x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF
+H4z1Ir+rzoPz4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA
+=====================
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1
+MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu
+Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t
+JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e
+RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs
+7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d
+8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V
+qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga
+Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB
+Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu
+KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08
+ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0
+XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB
+hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2
+qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL
+oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK
+xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF
+KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2
+DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK
+xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU
+p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI
+P/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA 2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0
+MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg
+SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0
+DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17
+j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q
+JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a
+QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2
+WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP
+20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn
+ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC
+SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG
+8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2
++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E
+BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ
+4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+
+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq
+A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg
+Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP
+pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d
+FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp
+gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm
+X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 1
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG
+v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z
+DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh
+sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP
+8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z
+o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf
+GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF
+VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft
+3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g
+Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en
+fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8
+f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO
+qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN
+RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0
+gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn
+6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid
+FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6
+Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj
+B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op
+aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY
+T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p
++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg
+JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy
+zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO
+ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh
+1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf
+GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff
+Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP
+cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk=
+-----END CERTIFICATE-----
+
+Visa eCommerce Root
+===================
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG
+EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug
+QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2
+WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm
+VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL
+F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b
+RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0
+TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI
+/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs
+GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
+MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc
+CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW
+YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz
+zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu
+YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+
+Certum Root CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
+ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
+Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
+by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
+wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
+kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
+89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
+Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
+NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
+GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
+GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
+0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
+qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+
+Comodo AAA Services root
+========================
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
+MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
+c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
+BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
+C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
+i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
+Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
+Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
+Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
+cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
+LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
+7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
+8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
+12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+
+Comodo Secure Services root
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw
+MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu
+Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi
+BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP
+9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc
+rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC
+oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V
+p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E
+FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj
+YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm
+aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm
+4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL
+DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw
+pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H
+RR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+
+Comodo Trusted Services root
+============================
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw
+MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h
+bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw
+IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7
+3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y
+/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6
+juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS
+ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud
+DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp
+ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl
+cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw
+uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA
+BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l
+R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O
+9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+
+QuoVadis Root CA
+================
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
+ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
+MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
+cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
+EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
+J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
+F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
+YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
+AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
+PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
+ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
+MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
+YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
+ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
+Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu
+BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
+FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
+tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
+fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
+LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
+gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
+5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
+5nrQNiOKSnQ2+Q==
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 2
+==================
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
+ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
+XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
+lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
+lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
+lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
+66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
+wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
+D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
+BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
+J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
+DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
+a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
+Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
+UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
+VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
+IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
+WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
+f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
+4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
+VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 3
+==================
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
+OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
+DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
+KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
+DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
+BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
+p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
+nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
+MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
+Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
+uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
+BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
+YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
+BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
+VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
+ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
+AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
+qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
+hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
+POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
+Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
+8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
+bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
+g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
+vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
+qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+
+Security Communication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
+8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
+DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
+5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
+DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
+JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
+0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
+mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
+s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
+6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
+FL39vmwLAw==
+-----END CERTIFICATE-----
+
+Sonera Class 2 Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
+NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
+IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3
+/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
+dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
+f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
+tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
+nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
+XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
+0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
+cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
+Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
+EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH
+llpwrN9M
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA
+=============================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE
+ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w
+HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh
+bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt
+vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P
+jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca
+C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth
+vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6
+22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV
+HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v
+dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN
+BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR
+EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw
+MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y
+nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+
+TDC Internet Root CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE
+ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx
+NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu
+ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j
+xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL
+znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc
+5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6
+otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI
+AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM
+VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM
+MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC
+AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe
+UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G
+CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m
+gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
+2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb
+O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU
+Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l
+-----END CERTIFICATE-----
+
+UTN DATACorp SGC Root CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ
+BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa
+MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w
+HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
+dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys
+raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo
+wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA
+9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv
+33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud
+DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9
+BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD
+LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3
+DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0
+I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx
+EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP
+DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+
+UTN USERFirst Hardware Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd
+BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx
+OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0
+eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
+ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI
+wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd
+tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8
+i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf
+Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw
+gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF
+lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF
+UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF
+BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW
+XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2
+lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn
+iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67
+nfhmqA==
+-----END CERTIFICATE-----
+
+Camerfirma Chambers of Commerce Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
+NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
+cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
+MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
+AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
+xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
+NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
+DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
+d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
+EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
+cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
+AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
+bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
+VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
+fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
+L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
+UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
+ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
+erfutGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+
+Camerfirma Global Chambersign Root
+==================================
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
+NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
+YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
+MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
+ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
+1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
+by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
+6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
+8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
+BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
+aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
+Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
+aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
+ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
+PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
+gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
+PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
+IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
+t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+
+NetLock Notary (Class A) Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI
+EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j
+ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX
+DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH
+EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD
+VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz
+cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM
+D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ
+z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC
+/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7
+tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6
+4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG
+A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC
+Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv
+bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn
+LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0
+ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz
+IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh
+IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu
+b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh
+bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg
+Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp
+bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5
+ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP
+ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB
+CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr
+KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM
+8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+
+NetLock Business (Class B) Root
+===============================
+-----BEGIN CERTIFICATE-----
+MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg
+VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD
+VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv
+bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg
+VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S
+o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr
+1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
+HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ
+RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh
+dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0
+ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv
+c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg
+YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
+c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz
+Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA
+bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl
+IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2
+YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj
+cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM
+43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR
+stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI
+-----END CERTIFICATE-----
+
+NetLock Express (Class C) Root
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j
+ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z
+W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63
+euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw
+DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN
+RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn
+YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB
+IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i
+aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0
+ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
+ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo
+dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y
+emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k
+IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ
+UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg
+YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2
+xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW
+gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A==
+-----END CERTIFICATE-----
+
+XRamp Global CA Root
+====================
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
+BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
+dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
+HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
+U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
+IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
+foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
+zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
+AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
+xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
+oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
+AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
+/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
+nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
+8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+
+Go Daddy Class 2 CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
+VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
+A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
+ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
+qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
+YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
+vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
+BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
+atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
+MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
+PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
+I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
+Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
+vZ8=
+-----END CERTIFICATE-----
+
+Starfield Class 2 CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
+U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
+MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
+A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
+SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
+bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
+JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
+epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
+F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
+MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
+hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
+bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
+afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
+PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
+KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
+QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj
+YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH
+AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw
+Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg
+U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5
+LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh
+cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT
+dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC
+AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh
+3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm
+vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk
+fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3
+fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ
+EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl
+1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/
+lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro
+g14=
+-----END CERTIFICATE-----
+
+Taiwan GRCA
+===========
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG
+EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X
+DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv
+dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN
+w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5
+BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O
+1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO
+htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov
+J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7
+Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t
+B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB
+O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8
+lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV
+HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2
+09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj
+Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2
+Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU
+D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz
+DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk
+Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk
+7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ
+CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy
++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS
+-----END CERTIFICATE-----
+
+Swisscom Root CA 1
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4
+MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM
+MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF
+NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe
+AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC
+b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn
+7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN
+cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp
+WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5
+haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY
+MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9
+MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn
+jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ
+MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H
+VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl
+vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl
+OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3
+1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq
+nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy
+x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW
+NY6E0F/6MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+
+DigiCert Assured ID Root CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
+MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
+9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
+UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
+/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
+oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
+GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
+66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
+hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
+EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
+SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
+8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+
+DigiCert Global Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
+MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
+TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
+BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
+4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
+7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
+o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
+8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
+BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
+EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
+tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
+UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+
+DigiCert High Assurance EV Root CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
+KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
+MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
+Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
+Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
+OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
+MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
+NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
+h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
+Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
+JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
+V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
+myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
+mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Certplus Class 2 Primary CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE
+BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN
+OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy
+dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR
+5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ
+Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO
+YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e
+e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME
+CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ
+YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t
+L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD
+P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R
+TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+
+7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW
+//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+
+DST Root CA X3
+==============
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
+DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
+cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
+rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
+UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
+xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
+utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
+MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug
+dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
+GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
+RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
+fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+
+DST ACES CA X6
+==============
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT
+MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha
+MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE
+CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI
+DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa
+pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow
+GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy
+MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu
+Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy
+dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU
+CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2
+5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t
+Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs
+vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
+oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 1
+==============================================
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
+MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
+acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
+MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
+TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
+aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
+yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
+Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
+8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
+W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
+BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
+sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
+q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
+nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2
+==============================================
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
+MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
+dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
+A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
+acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
+LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
+x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
+QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
+5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
+AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
+Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
+9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
+UrbnBEI=
+-----END CERTIFICATE-----
+
+SwissSign Gold CA - G2
+======================
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
+EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
+MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
+c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
+t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
+jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
+vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
+ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
+AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
+jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
+peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
+7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
+GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
+OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
+5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
+44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
+Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
+Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
+mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
+vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
+KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
+NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
+viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+
+SwissSign Silver CA - G2
+========================
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
+BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
+DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
+aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
+N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
+6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
+MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
+qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
+FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
+ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
+celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
+CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
+tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
+4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
+kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
+3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
+/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
+DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
+e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
+WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
+DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
+DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ
+cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN
+b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9
+nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge
+RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt
+tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI
+hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K
+Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN
+NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa
+Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG
+1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+
+thawte Primary Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3
+MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg
+SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv
+KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT
+FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs
+oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ
+1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc
+q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K
+aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p
+afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF
+AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE
+uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89
+jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH
+z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G5
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
+biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
+dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
+j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
+Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
+fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
+SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
+KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
+Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
+ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+
+SecureTrust CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
+dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
+BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
+OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
+DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
+GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
+01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
+ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
+aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
+SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
+mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
+nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+
+Secure Global CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
+bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
+MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
+YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
+bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
+8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
+HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
+0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
+oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
+MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
+CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
+3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+
+COMODO Certification Authority
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
+MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
+T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
+xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
+4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
+1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
+rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
+b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
+AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
+OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
+IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
+-----END CERTIFICATE-----
+
+Network Solutions Certificate Authority
+=======================================
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
+EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
+IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
+MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
+jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
+aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
+crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
+/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
+AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
+bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
+A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
+4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
+GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
+ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+
+WellsSecure Public Root Certificate Authority
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM
+F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw
+NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl
+bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD
+VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1
+iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13
+i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8
+bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB
+K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB
+AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu
+cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm
+lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB
+i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww
+GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI
+K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0
+bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj
+qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es
+E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ
+tylv2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+
+COMODO ECC Certification Authority
+==================================
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
+GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
+4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
+wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
+FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
+U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+IGC/A
+=====
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE
+Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy
+MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI
+EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT
+STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2
+TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW
+So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy
+HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd
+frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ
+tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB
+egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC
+iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK
+q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q
+MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI
+lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF
+0mBWWg==
+-----END CERTIFICATE-----
+
+Security Communication EV RootCA1
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
+BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
+Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
+/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
+WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
+ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
+bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
+9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
+iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
+Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
+mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
+T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+
+OISTE WISeKey Global Root GA CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
+BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
+A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
+bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
+VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
+IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
+IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
+Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
+Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
+d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
+/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
+LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
+MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
+okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE
+BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL
+EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0
+MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz
+dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT
+GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG
+d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N
+oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc
+QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ
+PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb
+MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG
+IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD
+VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3
+LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A
+dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA
+4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg
+AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA
+egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6
+Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO
+PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv
+c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h
+cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw
+IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT
+WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV
+MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER
+MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp
+Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal
+HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT
+nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE
+aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK
+yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB
+S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+
+Certigna
+========
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
+EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
+MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
+Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
+XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
+GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
+ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
+DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
+Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
+tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
+BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
+SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
+hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
+PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
+1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+AC Ra\xC3\xADz Certic\xC3\xA1mara S.A.
+======================================
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT
+AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg
+LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w
+HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+
+U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh
+IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN
+yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU
+2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3
+4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP
+2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm
+8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf
+HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa
+Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK
+5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b
+czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g
+ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF
+BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug
+cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf
+AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX
+EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v
+/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3
+MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4
+3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk
+eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f
+/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h
+RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU
+Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 2 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw
+MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw
+IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2
+xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ
+Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u
+SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G
+dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ
+KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj
+TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP
+JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk
+vQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 3 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw
+MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W
+yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo
+6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ
+uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk
+2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE
+O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8
+yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9
+IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal
+092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc
+5A==
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA I
+=============================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN
+MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg
+VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw
+JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC
+qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv
+xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw
+ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O
+gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j
+BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG
+1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy
+vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3
+ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a
+7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+
+Deutsche Telekom Root CA 2
+==========================
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT
+RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG
+A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5
+MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G
+A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS
+b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5
+bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI
+KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY
+AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK
+Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV
+jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV
+HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr
+E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy
+zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8
+rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G
+dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+
+ComSign Secured CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE
+AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w
+NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD
+QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs
+49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH
+7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB
+kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1
+9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw
+AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t
+U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA
+j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC
+AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a
+BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp
+FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP
+51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+
+Cybertrust Global Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
+ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
+MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
+ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
+0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
+AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
+89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
+8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
+MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
+A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
+lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
+5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
+hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
+X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+
+ePKI Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
+MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
+MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
+IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
+lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
+qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
+12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
+WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
+lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
+vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
+Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
+MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
+1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
+KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
+xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
+NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
+GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
+xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
+gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
+sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
+BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3
+=============================================================================================================================
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH
+DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q
+aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry
+b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV
+BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg
+S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4
+MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl
+IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF
+n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl
+IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft
+dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl
+cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO
+Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1
+xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR
+6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd
+BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4
+N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT
+y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh
+LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M
+dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+
+Buypass Class 2 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2
+MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M
+cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83
+0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4
+0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R
+uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV
+1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt
+7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2
+fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w
+wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+
+Buypass Class 3 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1
+MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx
+ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0
+n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia
+AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c
+1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7
+pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA
+EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5
+htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj
+el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+
+EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1
+==========================================================================
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg
+QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe
+Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt
+IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by
+X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b
+gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr
+eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ
+TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy
+Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn
+uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI
+qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm
+ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0
+Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW
+Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t
+FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm
+zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k
+XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT
+bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU
+RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK
+1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt
+2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ
+Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9
+AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+
+certSIGN ROOT CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
+VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
+Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
+CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
+JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
+rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
+ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
+0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
+AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
+AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
+SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
+x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
+vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
+TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+CNNIC ROOT
+==========
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE
+ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw
+OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD
+o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz
+VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT
+VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or
+czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK
+y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC
+wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S
+lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5
+Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM
+O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8
+BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2
+G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m
+mxE=
+-----END CERTIFICATE-----
+
+ApplicationCA - Japanese Government
+===================================
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT
+SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw
+MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl
+cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4
+fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN
+wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE
+jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu
+nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU
+WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD
+vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs
+o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g
+/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD
+io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW
+dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G3
+=============================================
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz
+NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo
+YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT
+LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j
+K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE
+c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C
+IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu
+dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr
+2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9
+cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE
+Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s
+t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G2
+===========================
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC
+VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu
+IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg
+Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV
+MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG
+b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt
+IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS
+LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5
+8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN
+G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K
+rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G3
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w
+ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD
+VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG
+A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At
+P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC
++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY
+7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW
+vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ
+KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK
+A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC
+8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm
+er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
+OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
+b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
+KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
+EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
+ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
+npaqBA+K
+-----END CERTIFICATE-----
+
+VeriSign Universal Root Certification Authority
+===============================================
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj
+1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP
+MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
+9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
+AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR
+tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
+CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O
+a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
+Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
+Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
+P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
+wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4
+mJO37M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G4
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3
+b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz
+ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo
+b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8
+Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz
+rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw
+HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u
+Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD
+A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx
+AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+NetLock Arany (Class Gold) Főtanúsítvány
+============================================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
+A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
+dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
+cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
+MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
+ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
+c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
+0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
+/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
+H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
+fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
+neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
+qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
+YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
+NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
+dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA - G2
+==================================
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC
+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ
+5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn
+vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj
+CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil
+e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR
+OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI
+CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65
+48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi
+trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737
+qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB
+AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC
+ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA
+A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz
++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj
+f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN
+kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk
+CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF
+URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb
+CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h
+oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV
+IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm
+66+KAQ==
+-----END CERTIFICATE-----
+
+CA Disig
+========
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK
+QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw
+MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz
+bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm
+GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD
+Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo
+hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt
+ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w
+gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P
+AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz
+aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff
+ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa
+BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t
+WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3
+mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K
+ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA
+4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+
+Juur-SK
+=======
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA
+c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw
+DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG
+SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy
+aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf
+TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC
++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw
+UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa
+Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF
+MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD
+HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh
+AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA
+cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr
+AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw
+cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G
+A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo
+ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL
+abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678
+IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh
+Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2
+yyqcjg==
+-----END CERTIFICATE-----
+
+Hongkong Post Root CA 1
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
+DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
+NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
+IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
+ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
+auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
+qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
+V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
+HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
+h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
+l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
+IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
+T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
+c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
+-----END CERTIFICATE-----
+
+SecureSign RootCA11
+===================
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
+SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
+b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
+KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
+cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
+TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
+wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
+g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
+O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
+bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
+t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
+OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
+bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
+Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
+y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
+lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+
+ACEDICOM Root
+=============
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD
+T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4
+MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG
+A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk
+WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD
+YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew
+MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb
+m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk
+HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT
+xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2
+3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9
+2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq
+TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz
+4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU
+9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg
+aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP
+eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk
+zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1
+ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI
+KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq
+nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE
+I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp
+MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o
+tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky
+CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX
+bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/
+D/xwzoiQ
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
+MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
+c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
+BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
+U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
+fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
+0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
+pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
+1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
+AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
+QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
+FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
+lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
+I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
+yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
+LXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi
+===================================================
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz
+ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3
+MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0
+cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u
+aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY
+8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y
+jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI
+JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk
+9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG
+SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d
+F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq
+D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4
+Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R3
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
+iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
+0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
+rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
+OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
+xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
+lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
+EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
+bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
+YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
+kpeDMdmztcpHWD9f
+-----END CERTIFICATE-----
+
+Autoridad de Certificacion Firmaprofesional CIF A62634068
+=========================================================
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
+EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
+DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
+bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
+ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
+51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
+R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
+T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
+Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
+osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
+crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
+saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
+KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
+6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+Izenpe.com
+==========
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
+EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
+MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
+QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
+03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
+ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
+PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
+OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
+F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
+0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
+leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
+AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
+SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
+NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
+BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
+Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
+kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
+hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
+g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
+aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
+nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
+ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
+Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
+WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+Chambers of Commerce Root - 2008
+================================
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
+Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
+ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
+EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
+cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
+XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
+h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
+ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
+NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
+D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
+lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
+0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
+EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
+G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
+BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
+bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
+bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
+CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
+AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
+wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
+3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
+RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
+M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
+YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
+9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
+zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
+nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
+-----END CERTIFICATE-----
+
+Global Chambersign Root - 2008
+==============================
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
+NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
+Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
+QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
+VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
+XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
+ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
+/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
+TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
+H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
+Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
+HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
+AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
+BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
+BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
+aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
+aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
+1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
+dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
+/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
+ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
+dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
+9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
+foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
+qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
+P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
+c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+
+Go Daddy Root Certificate Authority - G2
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
+MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
+A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
+9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
+fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
+NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
+BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
+vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
+5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
+N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+Starfield Root Certificate Authority - G2
+=========================================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
+eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
+DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
+VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
+W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
+bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
+N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
+ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
+JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
+TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
+4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
+F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
+c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+Starfield Services Root Certificate Authority - G2
+==================================================
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
+IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
+dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
+h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
+hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
+LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
+rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
+SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
+E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
+xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
+YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
+-----END CERTIFICATE-----
+
+AffirmTrust Commercial
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
+MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
+DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
+C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
+BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
+MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
+HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
+hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
+qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
+0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
+sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+AffirmTrust Networking
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
+MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
+Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
+dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
+/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
+h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
+HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
+UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
+12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
+WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
+/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+AffirmTrust Premium
+===================
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
+OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
+dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
+BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
+5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
+GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
+p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
+S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
+6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
+/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
+MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
+6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
+L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
+BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
+IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
+g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
+zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+AffirmTrust Premium ECC
+=======================
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
+BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
+MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
+cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
+N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
+BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
+BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
+57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
+eQ==
+-----END CERTIFICATE-----
+
+Certum Trusted Network CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
+ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
+MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
+ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
+l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
+J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
+fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
+cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
+Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
+DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
+jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
+mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
+Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+Certinomis - Autorité Racine
+=============================
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg
+LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG
+A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw
+JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa
+wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly
+Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw
+2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N
+jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q
+c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC
+lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb
+xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g
+530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna
+4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x
+WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva
+R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40
+nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B
+CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv
+JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE
+qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b
+WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE
+wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/
+vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+
+Root CA Generalitat Valenciana
+==============================
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE
+ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290
+IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3
+WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE
+CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2
+F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B
+ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ
+D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte
+JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB
+AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n
+dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB
+ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl
+AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA
+YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy
+AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt
+AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA
+YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu
+AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA
+OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0
+dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV
+BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S
+b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh
+TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz
+Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63
+NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH
+iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt
++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+
+A-Trust-nQual-03
+================
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE
+Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy
+a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R
+dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw
+RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0
+ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1
+c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA
+zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n
+yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE
+SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4
+iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V
+cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV
+eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40
+ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr
+sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd
+JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6
+ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+
+TWCA Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
+VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
+EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
+IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
+QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
+oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
+4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
+y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
+9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
+mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
+QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
+T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
+Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+Security Communication RootCA2
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
+SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
+aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
+3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
+spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
+EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
+QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
+u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
+3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
+tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
+mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+EC-ACC
+======
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
+BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
+ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
+VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
+CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
+BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
+MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
+SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
+Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
+cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
+w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
+ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
+HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
+E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
+0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
+VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
+Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
+dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
+lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
+Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
+l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
+E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
+5EI=
+-----END CERTIFICATE-----
+
+Hellenic Academic and Research Institutions RootCA 2011
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
+O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
+aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
+AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
+IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
+1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
+71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
+8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
+3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
+MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
+MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
+b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
+XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
+/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
+7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+
+Actalis Authentication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
+BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
+AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
+MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
+IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
+wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
+by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
+zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
+YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
+oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
+EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
+hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
+EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
+jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
+iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
+WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
+JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
+K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
+Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
+4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
+2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
+lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
+OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
+vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+Trustis FPS Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
+EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
+IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
+BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ
+RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
+H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
+cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
+o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
+AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
+BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c
+GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
+yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P
+8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
+l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
+iB6XzCGcKQENZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ
+Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0
+dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu
+c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv
+bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0
+aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t
+L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG
+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5
+fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm
+N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN
+Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T
+tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX
+e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA
+2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs
+HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib
+D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority G2
+===================================
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE
+ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O
+o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG
+4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi
+Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul
+Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs
+O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H
+vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L
+nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS
+FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa
+z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ
+KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk
+J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+
+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG
+/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc
+nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld
+blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc
+l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm
+7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm
+obp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+
+Buypass Class 2 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
+DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
+g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
+9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
+/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
+CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
+awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
+zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
+Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
+Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
+M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
+osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
+aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
+DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
+LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
+oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
+wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
+CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
+rJgWVqA=
+-----END CERTIFICATE-----
+
+Buypass Class 3 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
+DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
+sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
+5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
+7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
+ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
+2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
+/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
+RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
+Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
+j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
+uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
+Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
+ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
+KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
+6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
+UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
+eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
+Cp/HuZc=
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 3
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
+MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
+9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
+NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
+iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
+0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
+AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
+fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
+ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
+P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
+-----END CERTIFICATE-----
+
+EE Certification Centre Root CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy
+dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw
+MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB
+UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy
+ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM
+TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2
+rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw
+93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN
+P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ
+MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
+BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj
+xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM
+lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU
+3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM
+dcGWxZ0=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2007
+=================================================
+-----BEGIN CERTIFICATE-----
+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X
+DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl
+a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN
+BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp
+bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N
+YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv
+KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya
+KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT
+rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC
+AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s
+Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO
+Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb
+BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK
+poRq0Tl9
+-----END CERTIFICATE-----
+
+D-TRUST Root Class 3 CA 2 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
+Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
+LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
+ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
+BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
+KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
+p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
+AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
+4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
+eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
+MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
+PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
+OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
+2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
+dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
+X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+
+D-TRUST Root Class 3 CA 2 EV 2009
+=================================
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
+OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
+OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
+egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
+zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
+7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
+sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
+11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
+cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
+ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
+MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
+b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
+c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
+PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
+ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
+NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
+w9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+
+PSCProcert
+==========
+-----BEGIN CERTIFICATE-----
+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk
+ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ
+MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz
+dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl
+cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw
+IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw
+MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w
+DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD
+ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp
+Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC
+wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA
+3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh
+RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO
+EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2
+0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU
+td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw
+Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp
+r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/
+AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz
+Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId
+xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp
+ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH
+EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h
+Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k
+ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG
+9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG
+MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG
+LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52
+ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy
+YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o
+dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq
+T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN
+g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q
+uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1
+n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn
+FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo
+5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq
+3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5
+poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y
+eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
+-----END CERTIFICATE-----
+
+China Internet Network Information Center EV Certificates Root
+==============================================================
+-----BEGIN CERTIFICATE-----
+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV
+BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D
+aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg
+Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG
+A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM
+PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl
+cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y
+jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV
+98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H
+klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23
+KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC
+7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD
+glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5
+0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM
+7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0
+5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8=
+-----END CERTIFICATE-----
+
+Swisscom Root CA 2
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2
+MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM
+LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo
+ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ
+wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH
+Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a
+SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS
+NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab
+mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY
+Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3
+qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu
+MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO
+v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ
+82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz
+o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs
+a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx
+OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW
+mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o
++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC
+rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX
+5OfNeOI5wSsSnqaeG8XmDtkx2Q==
+-----END CERTIFICATE-----
+
+Swisscom Root EV CA 2
+=====================
+-----BEGIN CERTIFICATE-----
+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE
+BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl
+cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN
+MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT
+HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg
+Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz
+o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy
+Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti
+GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li
+qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH
+Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG
+alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa
+m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox
+bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi
+xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB
+bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL
+j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU
+wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7
+XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH
+59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/
+23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq
+J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA
+HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi
+uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW
+l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc=
+-----END CERTIFICATE-----
+
+CA Disig Root R1
+================
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw
+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
+ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx
+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
+c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy
+3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8
+u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2
+m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk
+CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa
+YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6
+vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL
+LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX
+ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is
+XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ
+04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B
+LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM
+CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb
+VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85
+YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS
+ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix
+lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N
+UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ
+a7+h89n07eLw4+1knj0vllJPgFOL
+-----END CERTIFICATE-----
+
+CA Disig Root R2
+================
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
+ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
+c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
+w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
+xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
+A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
+GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
+g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
+5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
+koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
+Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
+Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
+Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
+sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
+dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
+1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
+mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
+utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
+sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
+UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
+7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+
+ACCVRAIZ1
+=========
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
+SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
+MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
+UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
+jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
+RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
+aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
+0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
+WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
+8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
+5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
+9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
+Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
+Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
+Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
+Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
+QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
+AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
+YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
+AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
+IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
+aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
+dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
+MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
+hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
+R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
+YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
+nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
+TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
+sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
+Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
+3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
+EfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+
+TWCA Global Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
+CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
+QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
+EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
+Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
+nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
+r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
+Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
+tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
+KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
+sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
+yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
+kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
+zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
+AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
+cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
+8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
+/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
+lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
+A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
+i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
+EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
+zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
+-----END CERTIFICATE-----
+
+TeliaSonera Root CA v1
+======================
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
+CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
+MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
+VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
+6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
+3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
+B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
+Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
+oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
+F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
+oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
+gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
+TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
+AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
+DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
+zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
+pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
+G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
+c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
+JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
+qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
+Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
+WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+
+E-Tugra Certification Authority
+===============================
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w
+DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls
+ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw
+NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx
+QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl
+cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD
+DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd
+hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K
+CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g
+ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ
+BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0
+E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz
+rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq
+jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5
+dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB
+/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG
+MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK
+kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO
+XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807
+VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo
+a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc
+dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV
+KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT
+Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0
+8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G
+C7TbO6Orb1wdtn7os4I07QZcJA==
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 2
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
+MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
+SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
+vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
+2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
+WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
+YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
+r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
+vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
+3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
+-----END CERTIFICATE-----
+
+Atos TrustedRoot 2011
+=====================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
+cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
+MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
+A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
+hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
+54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
+HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
+z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
+l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
+bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
+k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
+TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
+61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
+3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php b/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php
new file mode 100644
index 0000000..dbd4c18
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\ClientInterface;
+use Guzzle\Stream\StreamRequestFactoryInterface;
+use Guzzle\Stream\PhpStreamRequestFactory;
+
+/**
+ * Simplified interface to Guzzle that does not require a class to be instantiated
+ */
+final class StaticClient
+{
+ /** @var Client Guzzle client */
+ private static $client;
+
+ /**
+ * Mount the client to a simpler class name for a specific client
+ *
+ * @param string $className Class name to use to mount
+ * @param ClientInterface $client Client used to send requests
+ */
+ public static function mount($className = 'Guzzle', ClientInterface $client = null)
+ {
+ class_alias(__CLASS__, $className);
+ if ($client) {
+ self::$client = $client;
+ }
+ }
+
+ /**
+ * @param string $method HTTP request method (GET, POST, HEAD, DELETE, PUT, etc)
+ * @param string $url URL of the request
+ * @param array $options Options to use with the request. See: Guzzle\Http\Message\RequestFactory::applyOptions()
+ * @return \Guzzle\Http\Message\Response|\Guzzle\Stream\Stream
+ */
+ public static function request($method, $url, $options = array())
+ {
+ // @codeCoverageIgnoreStart
+ if (!self::$client) {
+ self::$client = new Client();
+ }
+ // @codeCoverageIgnoreEnd
+
+ $request = self::$client->createRequest($method, $url, null, null, $options);
+
+ if (isset($options['stream'])) {
+ if ($options['stream'] instanceof StreamRequestFactoryInterface) {
+ return $options['stream']->fromRequest($request);
+ } elseif ($options['stream'] == true) {
+ $streamFactory = new PhpStreamRequestFactory();
+ return $streamFactory->fromRequest($request);
+ }
+ }
+
+ return $request->send();
+ }
+
+ /**
+ * Send a GET request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function get($url, $options = array())
+ {
+ return self::request('GET', $url, $options);
+ }
+
+ /**
+ * Send a HEAD request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function head($url, $options = array())
+ {
+ return self::request('HEAD', $url, $options);
+ }
+
+ /**
+ * Send a DELETE request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function delete($url, $options = array())
+ {
+ return self::request('DELETE', $url, $options);
+ }
+
+ /**
+ * Send a POST request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function post($url, $options = array())
+ {
+ return self::request('POST', $url, $options);
+ }
+
+ /**
+ * Send a PUT request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function put($url, $options = array())
+ {
+ return self::request('PUT', $url, $options);
+ }
+
+ /**
+ * Send a PATCH request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function patch($url, $options = array())
+ {
+ return self::request('PATCH', $url, $options);
+ }
+
+ /**
+ * Send an OPTIONS request
+ *
+ * @param string $url URL of the request
+ * @param array $options Array of request options
+ *
+ * @return \Guzzle\Http\Message\Response
+ * @see Guzzle::request for a list of available options
+ */
+ public static function options($url, $options = array())
+ {
+ return self::request('OPTIONS', $url, $options);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php
new file mode 100644
index 0000000..6a4e772
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php
@@ -0,0 +1,554 @@
+<?php
+
+namespace Guzzle\Http;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Parses and generates URLs based on URL parts. In favor of performance, URL parts are not validated.
+ */
+class Url
+{
+ protected $scheme;
+ protected $host;
+ protected $port;
+ protected $username;
+ protected $password;
+ protected $path = '';
+ protected $fragment;
+
+ /** @var QueryString Query part of the URL */
+ protected $query;
+
+ /**
+ * Factory method to create a new URL from a URL string
+ *
+ * @param string $url Full URL used to create a Url object
+ *
+ * @return Url
+ * @throws InvalidArgumentException
+ */
+ public static function factory($url)
+ {
+ static $defaults = array('scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null,
+ 'user' => null, 'pass' => null, 'fragment' => null);
+
+ if (false === ($parts = parse_url($url))) {
+ throw new InvalidArgumentException('Was unable to parse malformed url: ' . $url);
+ }
+
+ $parts += $defaults;
+
+ // Convert the query string into a QueryString object
+ if ($parts['query'] || 0 !== strlen($parts['query'])) {
+ $parts['query'] = QueryString::fromString($parts['query']);
+ }
+
+ return new static($parts['scheme'], $parts['host'], $parts['user'],
+ $parts['pass'], $parts['port'], $parts['path'], $parts['query'],
+ $parts['fragment']);
+ }
+
+ /**
+ * Build a URL from parse_url parts. The generated URL will be a relative URL if a scheme or host are not provided.
+ *
+ * @param array $parts Array of parse_url parts
+ *
+ * @return string
+ */
+ public static function buildUrl(array $parts)
+ {
+ $url = $scheme = '';
+
+ if (isset($parts['scheme'])) {
+ $scheme = $parts['scheme'];
+ $url .= $scheme . ':';
+ }
+
+ if (isset($parts['host'])) {
+ $url .= '//';
+ if (isset($parts['user'])) {
+ $url .= $parts['user'];
+ if (isset($parts['pass'])) {
+ $url .= ':' . $parts['pass'];
+ }
+ $url .= '@';
+ }
+
+ $url .= $parts['host'];
+
+ // Only include the port if it is not the default port of the scheme
+ if (isset($parts['port'])
+ && !(($scheme == 'http' && $parts['port'] == 80) || ($scheme == 'https' && $parts['port'] == 443))
+ ) {
+ $url .= ':' . $parts['port'];
+ }
+ }
+
+ // Add the path component if present
+ if (isset($parts['path']) && 0 !== strlen($parts['path'])) {
+ // Always ensure that the path begins with '/' if set and something is before the path
+ if ($url && $parts['path'][0] != '/' && substr($url, -1) != '/') {
+ $url .= '/';
+ }
+ $url .= $parts['path'];
+ }
+
+ // Add the query string if present
+ if (isset($parts['query'])) {
+ $url .= '?' . $parts['query'];
+ }
+
+ // Ensure that # is only added to the url if fragment contains anything.
+ if (isset($parts['fragment'])) {
+ $url .= '#' . $parts['fragment'];
+ }
+
+ return $url;
+ }
+
+ /**
+ * Create a new URL from URL parts
+ *
+ * @param string $scheme Scheme of the URL
+ * @param string $host Host of the URL
+ * @param string $username Username of the URL
+ * @param string $password Password of the URL
+ * @param int $port Port of the URL
+ * @param string $path Path of the URL
+ * @param QueryString|array|string $query Query string of the URL
+ * @param string $fragment Fragment of the URL
+ */
+ public function __construct($scheme, $host, $username = null, $password = null, $port = null, $path = null, QueryString $query = null, $fragment = null)
+ {
+ $this->scheme = $scheme;
+ $this->host = $host;
+ $this->port = $port;
+ $this->username = $username;
+ $this->password = $password;
+ $this->fragment = $fragment;
+ if (!$query) {
+ $this->query = new QueryString();
+ } else {
+ $this->setQuery($query);
+ }
+ $this->setPath($path);
+ }
+
+ /**
+ * Clone the URL
+ */
+ public function __clone()
+ {
+ $this->query = clone $this->query;
+ }
+
+ /**
+ * Returns the URL as a URL string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return self::buildUrl($this->getParts());
+ }
+
+ /**
+ * Get the parts of the URL as an array
+ *
+ * @return array
+ */
+ public function getParts()
+ {
+ $query = (string) $this->query;
+
+ return array(
+ 'scheme' => $this->scheme,
+ 'user' => $this->username,
+ 'pass' => $this->password,
+ 'host' => $this->host,
+ 'port' => $this->port,
+ 'path' => $this->getPath(),
+ 'query' => $query !== '' ? $query : null,
+ 'fragment' => $this->fragment,
+ );
+ }
+
+ /**
+ * Set the host of the request.
+ *
+ * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com)
+ *
+ * @return Url
+ */
+ public function setHost($host)
+ {
+ if (strpos($host, ':') === false) {
+ $this->host = $host;
+ } else {
+ list($host, $port) = explode(':', $host);
+ $this->host = $host;
+ $this->setPort($port);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the host part of the URL
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Set the scheme part of the URL (http, https, ftp, etc)
+ *
+ * @param string $scheme Scheme to set
+ *
+ * @return Url
+ */
+ public function setScheme($scheme)
+ {
+ if ($this->scheme == 'http' && $this->port == 80) {
+ $this->port = null;
+ } elseif ($this->scheme == 'https' && $this->port == 443) {
+ $this->port = null;
+ }
+
+ $this->scheme = $scheme;
+
+ return $this;
+ }
+
+ /**
+ * Get the scheme part of the URL
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * Set the port part of the URL
+ *
+ * @param int $port Port to set
+ *
+ * @return Url
+ */
+ public function setPort($port)
+ {
+ $this->port = $port;
+
+ return $this;
+ }
+
+ /**
+ * Get the port part of the URl. Will return the default port for a given scheme if no port has been set.
+ *
+ * @return int|null
+ */
+ public function getPort()
+ {
+ if ($this->port) {
+ return $this->port;
+ } elseif ($this->scheme == 'http') {
+ return 80;
+ } elseif ($this->scheme == 'https') {
+ return 443;
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the path part of the URL
+ *
+ * @param array|string $path Path string or array of path segments
+ *
+ * @return Url
+ */
+ public function setPath($path)
+ {
+ static $pathReplace = array(' ' => '%20', '?' => '%3F');
+ if (is_array($path)) {
+ $path = '/' . implode('/', $path);
+ }
+
+ $this->path = strtr($path, $pathReplace);
+
+ return $this;
+ }
+
+ /**
+ * Normalize the URL so that double slashes and relative paths are removed
+ *
+ * @return Url
+ */
+ public function normalizePath()
+ {
+ if (!$this->path || $this->path == '/' || $this->path == '*') {
+ return $this;
+ }
+
+ $results = array();
+ $segments = $this->getPathSegments();
+ foreach ($segments as $segment) {
+ if ($segment == '..') {
+ array_pop($results);
+ } elseif ($segment != '.' && $segment != '') {
+ $results[] = $segment;
+ }
+ }
+
+ // Combine the normalized parts and add the leading slash if needed
+ $this->path = ($this->path[0] == '/' ? '/' : '') . implode('/', $results);
+
+ // Add the trailing slash if necessary
+ if ($this->path != '/' && end($segments) == '') {
+ $this->path .= '/';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a relative path to the currently set path.
+ *
+ * @param string $relativePath Relative path to add
+ *
+ * @return Url
+ */
+ public function addPath($relativePath)
+ {
+ if ($relativePath != '/' && is_string($relativePath) && strlen($relativePath) > 0) {
+ // Add a leading slash if needed
+ if ($relativePath[0] != '/') {
+ $relativePath = '/' . $relativePath;
+ }
+ $this->setPath(str_replace('//', '/', $this->path . $relativePath));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the path part of the URL
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Get the path segments of the URL as an array
+ *
+ * @return array
+ */
+ public function getPathSegments()
+ {
+ return array_slice(explode('/', $this->getPath()), 1);
+ }
+
+ /**
+ * Set the password part of the URL
+ *
+ * @param string $password Password to set
+ *
+ * @return Url
+ */
+ public function setPassword($password)
+ {
+ $this->password = $password;
+
+ return $this;
+ }
+
+ /**
+ * Get the password part of the URL
+ *
+ * @return null|string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Set the username part of the URL
+ *
+ * @param string $username Username to set
+ *
+ * @return Url
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ /**
+ * Get the username part of the URl
+ *
+ * @return null|string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Get the query part of the URL as a QueryString object
+ *
+ * @return QueryString
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Set the query part of the URL
+ *
+ * @param QueryString|string|array $query Query to set
+ *
+ * @return Url
+ */
+ public function setQuery($query)
+ {
+ if (is_string($query)) {
+ $output = null;
+ parse_str($query, $output);
+ $this->query = new QueryString($output);
+ } elseif (is_array($query)) {
+ $this->query = new QueryString($query);
+ } elseif ($query instanceof QueryString) {
+ $this->query = $query;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the fragment part of the URL
+ *
+ * @return null|string
+ */
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ /**
+ * Set the fragment part of the URL
+ *
+ * @param string $fragment Fragment to set
+ *
+ * @return Url
+ */
+ public function setFragment($fragment)
+ {
+ $this->fragment = $fragment;
+
+ return $this;
+ }
+
+ /**
+ * Check if this is an absolute URL
+ *
+ * @return bool
+ */
+ public function isAbsolute()
+ {
+ return $this->scheme && $this->host;
+ }
+
+ /**
+ * Combine the URL with another URL. Follows the rules specific in RFC 3986 section 5.4.
+ *
+ * @param string $url Relative URL to combine with
+ * @param bool $strictRfc3986 Set to true to use strict RFC 3986 compliance when merging paths. When first
+ * released, Guzzle used an incorrect algorithm for combining relative URL paths. In
+ * order to not break users, we introduced this flag to allow the merging of URLs based
+ * on strict RFC 3986 section 5.4.1. This means that "http://a.com/foo/baz" merged with
+ * "bar" would become "http://a.com/foo/bar". When this value is set to false, it would
+ * become "http://a.com/foo/baz/bar".
+ * @return Url
+ * @throws InvalidArgumentException
+ * @link http://tools.ietf.org/html/rfc3986#section-5.4
+ */
+ public function combine($url, $strictRfc3986 = false)
+ {
+ $url = self::factory($url);
+
+ // Use the more absolute URL as the base URL
+ if (!$this->isAbsolute() && $url->isAbsolute()) {
+ $url = $url->combine($this);
+ }
+
+ // Passing a URL with a scheme overrides everything
+ if ($buffer = $url->getScheme()) {
+ $this->scheme = $buffer;
+ $this->host = $url->getHost();
+ $this->port = $url->getPort();
+ $this->username = $url->getUsername();
+ $this->password = $url->getPassword();
+ $this->path = $url->getPath();
+ $this->query = $url->getQuery();
+ $this->fragment = $url->getFragment();
+ return $this;
+ }
+
+ // Setting a host overrides the entire rest of the URL
+ if ($buffer = $url->getHost()) {
+ $this->host = $buffer;
+ $this->port = $url->getPort();
+ $this->username = $url->getUsername();
+ $this->password = $url->getPassword();
+ $this->path = $url->getPath();
+ $this->query = $url->getQuery();
+ $this->fragment = $url->getFragment();
+ return $this;
+ }
+
+ $path = $url->getPath();
+ $query = $url->getQuery();
+
+ if (!$path) {
+ if (count($query)) {
+ $this->addQuery($query, $strictRfc3986);
+ }
+ } else {
+ if ($path[0] == '/') {
+ $this->path = $path;
+ } elseif ($strictRfc3986) {
+ $this->path .= '/../' . $path;
+ } else {
+ $this->path .= '/' . $path;
+ }
+ $this->normalizePath();
+ $this->addQuery($query, $strictRfc3986);
+ }
+
+ $this->fragment = $url->getFragment();
+
+ return $this;
+ }
+
+ private function addQuery(QueryString $new, $strictRfc386)
+ {
+ if (!$strictRfc386) {
+ $new->merge($this->query);
+ }
+
+ $this->query = $new;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json
new file mode 100644
index 0000000..9384a5b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "guzzle/http",
+ "description": "HTTP libraries used by Guzzle",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["http client", "http", "client", "Guzzle", "curl"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/common": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/stream": "self.version"
+ },
+ "suggest": {
+ "ext-curl": "*"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Http": "" }
+ },
+ "target-dir": "Guzzle/Http",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php b/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php
new file mode 100644
index 0000000..c699773
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Guzzle\Inflection;
+
+/**
+ * Default inflection implementation
+ */
+class Inflector implements InflectorInterface
+{
+ /** @var InflectorInterface */
+ protected static $default;
+
+ /**
+ * Get the default inflector object that has support for caching
+ *
+ * @return MemoizingInflector
+ */
+ public static function getDefault()
+ {
+ // @codeCoverageIgnoreStart
+ if (!self::$default) {
+ self::$default = new MemoizingInflector(new self());
+ }
+ // @codeCoverageIgnoreEnd
+
+ return self::$default;
+ }
+
+ public function snake($word)
+ {
+ return ctype_lower($word) ? $word : strtolower(preg_replace('/(.)([A-Z])/', "$1_$2", $word));
+ }
+
+ public function camel($word)
+ {
+ return str_replace(' ', '', ucwords(strtr($word, '_-', ' ')));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Inflection/InflectorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Inflection/InflectorInterface.php
new file mode 100644
index 0000000..321d718
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Inflection/InflectorInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Inflection;
+
+/**
+ * Inflector interface used to convert the casing of words
+ */
+interface InflectorInterface
+{
+ /**
+ * Converts strings from camel case to snake case (e.g. CamelCase camel_case).
+ *
+ * @param string $word Word to convert to snake case
+ *
+ * @return string
+ */
+ public function snake($word);
+
+ /**
+ * Converts strings from snake_case to upper CamelCase
+ *
+ * @param string $word Value to convert into upper CamelCase
+ *
+ * @return string
+ */
+ public function camel($word);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Inflection/MemoizingInflector.php b/vendor/guzzle/guzzle/src/Guzzle/Inflection/MemoizingInflector.php
new file mode 100644
index 0000000..32968d6
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Inflection/MemoizingInflector.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Guzzle\Inflection;
+
+/**
+ * Decorator used to add memoization to previously inflected words
+ */
+class MemoizingInflector implements InflectorInterface
+{
+ /** @var array Array of cached inflections */
+ protected $cache = array(
+ 'snake' => array(),
+ 'camel' => array()
+ );
+
+ /** @var int Max entries per cache */
+ protected $maxCacheSize;
+
+ /** @var InflectorInterface Decorated inflector */
+ protected $decoratedInflector;
+
+ /**
+ * @param InflectorInterface $inflector Inflector being decorated
+ * @param int $maxCacheSize Maximum number of cached items to hold per cache
+ */
+ public function __construct(InflectorInterface $inflector, $maxCacheSize = 500)
+ {
+ $this->decoratedInflector = $inflector;
+ $this->maxCacheSize = $maxCacheSize;
+ }
+
+ public function snake($word)
+ {
+ if (!isset($this->cache['snake'][$word])) {
+ $this->pruneCache('snake');
+ $this->cache['snake'][$word] = $this->decoratedInflector->snake($word);
+ }
+
+ return $this->cache['snake'][$word];
+ }
+
+ /**
+ * Converts strings from snake_case to upper CamelCase
+ *
+ * @param string $word Value to convert into upper CamelCase
+ *
+ * @return string
+ */
+ public function camel($word)
+ {
+ if (!isset($this->cache['camel'][$word])) {
+ $this->pruneCache('camel');
+ $this->cache['camel'][$word] = $this->decoratedInflector->camel($word);
+ }
+
+ return $this->cache['camel'][$word];
+ }
+
+ /**
+ * Prune one of the named caches by removing 20% of the cache if it is full
+ *
+ * @param string $cache Type of cache to prune
+ */
+ protected function pruneCache($cache)
+ {
+ if (count($this->cache[$cache]) == $this->maxCacheSize) {
+ $this->cache[$cache] = array_slice($this->cache[$cache], $this->maxCacheSize * 0.2);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php b/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php
new file mode 100644
index 0000000..db37e4f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Guzzle\Inflection;
+
+/**
+ * Decorator used to add pre-computed inflection mappings to an inflector
+ */
+class PreComputedInflector implements InflectorInterface
+{
+ /** @var array Array of pre-computed inflections */
+ protected $mapping = array(
+ 'snake' => array(),
+ 'camel' => array()
+ );
+
+ /** @var InflectorInterface Decorated inflector */
+ protected $decoratedInflector;
+
+ /**
+ * @param InflectorInterface $inflector Inflector being decorated
+ * @param array $snake Hash of pre-computed camel to snake
+ * @param array $camel Hash of pre-computed snake to camel
+ * @param bool $mirror Mirror snake and camel reflections
+ */
+ public function __construct(InflectorInterface $inflector, array $snake = array(), array $camel = array(), $mirror = false)
+ {
+ if ($mirror) {
+ $camel = array_merge(array_flip($snake), $camel);
+ $snake = array_merge(array_flip($camel), $snake);
+ }
+
+ $this->decoratedInflector = $inflector;
+ $this->mapping = array(
+ 'snake' => $snake,
+ 'camel' => $camel
+ );
+ }
+
+ public function snake($word)
+ {
+ return isset($this->mapping['snake'][$word])
+ ? $this->mapping['snake'][$word]
+ : $this->decoratedInflector->snake($word);
+ }
+
+ /**
+ * Converts strings from snake_case to upper CamelCase
+ *
+ * @param string $word Value to convert into upper CamelCase
+ *
+ * @return string
+ */
+ public function camel($word)
+ {
+ return isset($this->mapping['camel'][$word])
+ ? $this->mapping['camel'][$word]
+ : $this->decoratedInflector->camel($word);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json
new file mode 100644
index 0000000..93f9e7b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "guzzle/inflection",
+ "description": "Guzzle inflection component",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["inflection", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Inflection": "" }
+ },
+ "target-dir": "Guzzle/Inflection",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php
new file mode 100644
index 0000000..1b6bd7e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Iterator;
+
+/**
+ * AppendIterator that is not affected by https://bugs.php.net/bug.php?id=49104
+ */
+class AppendIterator extends \AppendIterator
+{
+ /**
+ * Works around the bug in which PHP calls rewind() and next() when appending
+ *
+ * @param \Iterator $iterator Iterator to append
+ */
+ public function append(\Iterator $iterator)
+ {
+ $this->getArrayIterator()->append($iterator);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php
new file mode 100644
index 0000000..d76cdd4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Guzzle\Iterator;
+
+/**
+ * Pulls out chunks from an inner iterator and yields the chunks as arrays
+ */
+class ChunkedIterator extends \IteratorIterator
+{
+ /** @var int Size of each chunk */
+ protected $chunkSize;
+
+ /** @var array Current chunk */
+ protected $chunk;
+
+ /**
+ * @param \Traversable $iterator Traversable iterator
+ * @param int $chunkSize Size to make each chunk
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(\Traversable $iterator, $chunkSize)
+ {
+ $chunkSize = (int) $chunkSize;
+ if ($chunkSize < 0 ) {
+ throw new \InvalidArgumentException("The chunk size must be equal or greater than zero; $chunkSize given");
+ }
+
+ parent::__construct($iterator);
+ $this->chunkSize = $chunkSize;
+ }
+
+ public function rewind()
+ {
+ parent::rewind();
+ $this->next();
+ }
+
+ public function next()
+ {
+ $this->chunk = array();
+ for ($i = 0; $i < $this->chunkSize && parent::valid(); $i++) {
+ $this->chunk[] = parent::current();
+ parent::next();
+ }
+ }
+
+ public function current()
+ {
+ return $this->chunk;
+ }
+
+ public function valid()
+ {
+ return (bool) $this->chunk;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php
new file mode 100644
index 0000000..b103367
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Iterator;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Filters values using a callback
+ *
+ * Used when PHP 5.4's {@see \CallbackFilterIterator} is not available
+ */
+class FilterIterator extends \FilterIterator
+{
+ /** @var mixed Callback used for filtering */
+ protected $callback;
+
+ /**
+ * @param \Iterator $iterator Traversable iterator
+ * @param array|\Closure $callback Callback used for filtering. Return true to keep or false to filter.
+ *
+ * @throws InvalidArgumentException if the callback if not callable
+ */
+ public function __construct(\Iterator $iterator, $callback)
+ {
+ parent::__construct($iterator);
+ if (!is_callable($callback)) {
+ throw new InvalidArgumentException('The callback must be callable');
+ }
+ $this->callback = $callback;
+ }
+
+ public function accept()
+ {
+ return call_user_func($this->callback, $this->current());
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php
new file mode 100644
index 0000000..7e586bd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Iterator;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Maps values before yielding
+ */
+class MapIterator extends \IteratorIterator
+{
+ /** @var mixed Callback */
+ protected $callback;
+
+ /**
+ * @param \Traversable $iterator Traversable iterator
+ * @param array|\Closure $callback Callback used for iterating
+ *
+ * @throws InvalidArgumentException if the callback if not callable
+ */
+ public function __construct(\Traversable $iterator, $callback)
+ {
+ parent::__construct($iterator);
+ if (!is_callable($callback)) {
+ throw new InvalidArgumentException('The callback must be callable');
+ }
+ $this->callback = $callback;
+ }
+
+ public function current()
+ {
+ return call_user_func($this->callback, parent::current());
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php
new file mode 100644
index 0000000..de4ab03
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Iterator;
+
+/**
+ * Proxies missing method calls to the innermost iterator
+ */
+class MethodProxyIterator extends \IteratorIterator
+{
+ /**
+ * Proxy method calls to the wrapped iterator
+ *
+ * @param string $name Name of the method
+ * @param array $args Arguments to proxy
+ *
+ * @return mixed
+ */
+ public function __call($name, array $args)
+ {
+ $i = $this->getInnerIterator();
+ while ($i instanceof \OuterIterator) {
+ $i = $i->getInnerIterator();
+ }
+
+ return call_user_func_array(array($i, $name), $args);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md b/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md
new file mode 100644
index 0000000..8bb7e08
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md
@@ -0,0 +1,25 @@
+Guzzle Iterator
+===============
+
+Provides useful Iterators and Iterator decorators
+
+- ChunkedIterator: Pulls out chunks from an inner iterator and yields the chunks as arrays
+- FilterIterator: Used when PHP 5.4's CallbackFilterIterator is not available
+- MapIterator: Maps values before yielding
+- MethodProxyIterator: Proxies missing method calls to the innermost iterator
+
+### Installing via Composer
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+
+# Add Guzzle as a dependency
+php composer.phar require guzzle/iterator:~3.0
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json
new file mode 100644
index 0000000..ee17379
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/iterator",
+ "description": "Provides helpful iterators and iterator decorators",
+ "keywords": ["iterator", "guzzle"],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/common": ">=2.8.0"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Iterator": "/" }
+ },
+ "target-dir": "Guzzle/Iterator",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php
new file mode 100644
index 0000000..7f6271b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Guzzle\Log;
+
+/**
+ * Adapter class that allows Guzzle to log data using various logging implementations
+ */
+abstract class AbstractLogAdapter implements LogAdapterInterface
+{
+ protected $log;
+
+ public function getLogObject()
+ {
+ return $this->log;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php
new file mode 100644
index 0000000..a70fc8d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Log;
+
+/**
+ * Stores all log messages in an array
+ */
+class ArrayLogAdapter implements LogAdapterInterface
+{
+ protected $logs = array();
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ $this->logs[] = array('message' => $message, 'priority' => $priority, 'extras' => $extras);
+ }
+
+ /**
+ * Get logged entries
+ *
+ * @return array
+ */
+ public function getLogs()
+ {
+ return $this->logs;
+ }
+
+ /**
+ * Clears logged entries
+ */
+ public function clearLogs()
+ {
+ $this->logs = array();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php
new file mode 100644
index 0000000..d4bb73f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Log;
+
+/**
+ * Logs messages using Closures. Closures combined with filtering can trigger application events based on log messages.
+ */
+class ClosureLogAdapter extends AbstractLogAdapter
+{
+ public function __construct($logObject)
+ {
+ if (!is_callable($logObject)) {
+ throw new \InvalidArgumentException('Object must be callable');
+ }
+
+ $this->log = $logObject;
+ }
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ call_user_func($this->log, $message, $priority, $extras);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php
new file mode 100644
index 0000000..d7ac4ea
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Log;
+
+/**
+ * Adapter class that allows Guzzle to log data to various logging implementations.
+ */
+interface LogAdapterInterface
+{
+ /**
+ * Log a message at a priority
+ *
+ * @param string $message Message to log
+ * @param integer $priority Priority of message (use the \LOG_* constants of 0 - 7)
+ * @param array $extras Extra information to log in event
+ */
+ public function log($message, $priority = LOG_INFO, $extras = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/MessageFormatter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/MessageFormatter.php
new file mode 100644
index 0000000..b5cfe9d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/MessageFormatter.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Guzzle\Log;
+
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Message formatter used in various places in the framework
+ *
+ * Format messages using a template that can contain the the following variables:
+ *
+ * - {request}: Full HTTP request message
+ * - {response}: Full HTTP response message
+ * - {ts}: Timestamp
+ * - {host}: Host of the request
+ * - {method}: Method of the request
+ * - {url}: URL of the request
+ * - {host}: Host of the request
+ * - {protocol}: Request protocol
+ * - {version}: Protocol version
+ * - {resource}: Resource of the request (path + query + fragment)
+ * - {port}: Port of the request
+ * - {hostname}: Hostname of the machine that sent the request
+ * - {code}: Status code of the response (if available)
+ * - {phrase}: Reason phrase of the response (if available)
+ * - {curl_error}: Curl error message (if available)
+ * - {curl_code}: Curl error code (if available)
+ * - {curl_stderr}: Curl standard error (if available)
+ * - {connect_time}: Time in seconds it took to establish the connection (if available)
+ * - {total_time}: Total transaction time in seconds for last transfer (if available)
+ * - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
+ * - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
+ * - {req_body}: Request body
+ * - {res_body}: Response body
+ */
+class MessageFormatter
+{
+ const DEFAULT_FORMAT = "{hostname} {req_header_User-Agent} - [{ts}] \"{method} {resource} {protocol}/{version}\" {code} {res_header_Content-Length}";
+ const DEBUG_FORMAT = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{curl_stderr}";
+ const SHORT_FORMAT = '[{ts}] "{method} {resource} {protocol}/{version}" {code}';
+
+ /**
+ * @var string Template used to format log messages
+ */
+ protected $template;
+
+ /**
+ * @param string $template Log message template
+ */
+ public function __construct($template = self::DEFAULT_FORMAT)
+ {
+ $this->template = $template ?: self::DEFAULT_FORMAT;
+ }
+
+ /**
+ * Set the template to use for logging
+ *
+ * @param string $template Log message template
+ *
+ * @return self
+ */
+ public function setTemplate($template)
+ {
+ $this->template = $template;
+
+ return $this;
+ }
+
+ /**
+ * Returns a formatted message
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param Response $response Response that was received
+ * @param CurlHandle $handle Curl handle associated with the message
+ * @param array $customData Associative array of custom template data
+ *
+ * @return string
+ */
+ public function format(
+ RequestInterface $request,
+ Response $response = null,
+ CurlHandle $handle = null,
+ array $customData = array()
+ ) {
+ $cache = $customData;
+
+ return preg_replace_callback(
+ '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
+ function (array $matches) use ($request, $response, $handle, &$cache) {
+
+ if (array_key_exists($matches[1], $cache)) {
+ return $cache[$matches[1]];
+ }
+
+ $result = '';
+ switch ($matches[1]) {
+ case 'request':
+ $result = (string) $request;
+ break;
+ case 'response':
+ $result = (string) $response;
+ break;
+ case 'req_body':
+ $result = $request instanceof EntityEnclosingRequestInterface
+ ? (string) $request->getBody() : '';
+ break;
+ case 'res_body':
+ $result = $response ? $response->getBody(true) : '';
+ break;
+ case 'ts':
+ $result = gmdate('c');
+ break;
+ case 'method':
+ $result = $request->getMethod();
+ break;
+ case 'url':
+ $result = (string) $request->getUrl();
+ break;
+ case 'resource':
+ $result = $request->getResource();
+ break;
+ case 'protocol':
+ $result = 'HTTP';
+ break;
+ case 'version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'host':
+ $result = $request->getHost();
+ break;
+ case 'hostname':
+ $result = gethostname();
+ break;
+ case 'port':
+ $result = $request->getPort();
+ break;
+ case 'code':
+ $result = $response ? $response->getStatusCode() : '';
+ break;
+ case 'phrase':
+ $result = $response ? $response->getReasonPhrase() : '';
+ break;
+ case 'connect_time':
+ $result = $handle && $handle->getInfo(CURLINFO_CONNECT_TIME)
+ ? $handle->getInfo(CURLINFO_CONNECT_TIME)
+ : ($response ? $response->getInfo('connect_time') : '');
+ break;
+ case 'total_time':
+ $result = $handle && $handle->getInfo(CURLINFO_TOTAL_TIME)
+ ? $handle->getInfo(CURLINFO_TOTAL_TIME)
+ : ($response ? $response->getInfo('total_time') : '');
+ break;
+ case 'curl_error':
+ $result = $handle ? $handle->getError() : '';
+ break;
+ case 'curl_code':
+ $result = $handle ? $handle->getErrorNo() : '';
+ break;
+ case 'curl_stderr':
+ $result = $handle ? $handle->getStderr() : '';
+ break;
+ default:
+ if (strpos($matches[1], 'req_header_') === 0) {
+ $result = $request->getHeader(substr($matches[1], 11));
+ } elseif ($response && strpos($matches[1], 'res_header_') === 0) {
+ $result = $response->getHeader(substr($matches[1], 11));
+ }
+ }
+
+ $cache[$matches[1]] = $result;
+ return $result;
+ },
+ $this->template
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php
new file mode 100644
index 0000000..6afe7b6
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Log;
+
+use Monolog\Logger;
+
+/**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+class MonologLogAdapter extends AbstractLogAdapter
+{
+ /**
+ * syslog to Monolog mappings
+ */
+ private static $mapping = array(
+ LOG_DEBUG => Logger::DEBUG,
+ LOG_INFO => Logger::INFO,
+ LOG_WARNING => Logger::WARNING,
+ LOG_ERR => Logger::ERROR,
+ LOG_CRIT => Logger::CRITICAL,
+ LOG_ALERT => Logger::ALERT
+ );
+
+ public function __construct(Logger $logObject)
+ {
+ $this->log = $logObject;
+ }
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ $this->log->addRecord(self::$mapping[$priority], $message, $extras);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php
new file mode 100644
index 0000000..38a2b60
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Log;
+
+use Psr\Log\LogLevel;
+use Psr\Log\LoggerInterface;
+
+/**
+ * PSR-3 log adapter
+ *
+ * @link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ */
+class PsrLogAdapter extends AbstractLogAdapter
+{
+ /**
+ * syslog to PSR-3 mappings
+ */
+ private static $mapping = array(
+ LOG_DEBUG => LogLevel::DEBUG,
+ LOG_INFO => LogLevel::INFO,
+ LOG_WARNING => LogLevel::WARNING,
+ LOG_ERR => LogLevel::ERROR,
+ LOG_CRIT => LogLevel::CRITICAL,
+ LOG_ALERT => LogLevel::ALERT
+ );
+
+ public function __construct(LoggerInterface $logObject)
+ {
+ $this->log = $logObject;
+ }
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ $this->log->log(self::$mapping[$priority], $message, $extras);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php
new file mode 100644
index 0000000..0ea8e3b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Guzzle\Log;
+
+use Guzzle\Common\Version;
+
+/**
+ * Adapts a Zend Framework 1 logger object
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+class Zf1LogAdapter extends AbstractLogAdapter
+{
+ public function __construct(\Zend_Log $logObject)
+ {
+ $this->log = $logObject;
+ Version::warn(__CLASS__ . ' is deprecated');
+ }
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ $this->log->log($message, $priority, $extras);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php b/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php
new file mode 100644
index 0000000..863f6a1
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Log;
+
+use Zend\Log\Logger;
+
+/**
+ * Adapts a Zend Framework 2 logger object
+ */
+class Zf2LogAdapter extends AbstractLogAdapter
+{
+ public function __construct(Logger $logObject)
+ {
+ $this->log = $logObject;
+ }
+
+ public function log($message, $priority = LOG_INFO, $extras = array())
+ {
+ $this->log->log($priority, $message, $extras);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json
new file mode 100644
index 0000000..a8213e8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "guzzle/log",
+ "description": "Guzzle log adapter component",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["log", "adapter", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Log": "" }
+ },
+ "suggest": {
+ "guzzle/http": "self.version"
+ },
+ "target-dir": "Guzzle/Log",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php
new file mode 100644
index 0000000..4349eeb
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Guzzle\Parser\Cookie;
+
+/**
+ * Default Guzzle implementation of a Cookie parser
+ */
+class CookieParser implements CookieParserInterface
+{
+ /** @var array Cookie part names to snake_case array values */
+ protected static $cookieParts = array(
+ 'domain' => 'Domain',
+ 'path' => 'Path',
+ 'max_age' => 'Max-Age',
+ 'expires' => 'Expires',
+ 'version' => 'Version',
+ 'secure' => 'Secure',
+ 'port' => 'Port',
+ 'discard' => 'Discard',
+ 'comment' => 'Comment',
+ 'comment_url' => 'Comment-Url',
+ 'http_only' => 'HttpOnly'
+ );
+
+ public function parseCookie($cookie, $host = null, $path = null, $decode = false)
+ {
+ // Explode the cookie string using a series of semicolons
+ $pieces = array_filter(array_map('trim', explode(';', $cookie)));
+
+ // The name of the cookie (first kvp) must include an equal sign.
+ if (empty($pieces) || !strpos($pieces[0], '=')) {
+ return false;
+ }
+
+ // Create the default return array
+ $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array(
+ 'cookies' => array(),
+ 'data' => array(),
+ 'path' => null,
+ 'http_only' => false,
+ 'discard' => false,
+ 'domain' => $host
+ ));
+ $foundNonCookies = 0;
+
+ // Add the cookie pieces into the parsed data array
+ foreach ($pieces as $part) {
+
+ $cookieParts = explode('=', $part, 2);
+ $key = trim($cookieParts[0]);
+
+ if (count($cookieParts) == 1) {
+ // Can be a single value (e.g. secure, httpOnly)
+ $value = true;
+ } else {
+ // Be sure to strip wrapping quotes
+ $value = trim($cookieParts[1], " \n\r\t\0\x0B\"");
+ if ($decode) {
+ $value = urldecode($value);
+ }
+ }
+
+ // Only check for non-cookies when cookies have been found
+ if (!empty($data['cookies'])) {
+ foreach (self::$cookieParts as $mapValue => $search) {
+ if (!strcasecmp($search, $key)) {
+ $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value;
+ $foundNonCookies++;
+ continue 2;
+ }
+ }
+ }
+
+ // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a
+ // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data.
+ $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value;
+ }
+
+ // Calculate the expires date
+ if (!$data['expires'] && $data['max_age']) {
+ $data['expires'] = time() + (int) $data['max_age'];
+ }
+
+ // Check path attribute according RFC6265 http://tools.ietf.org/search/rfc6265#section-5.2.4
+ // "If the attribute-value is empty or if the first character of the
+ // attribute-value is not %x2F ("/"):
+ // Let cookie-path be the default-path.
+ // Otherwise:
+ // Let cookie-path be the attribute-value."
+ if (!$data['path'] || substr($data['path'], 0, 1) !== '/') {
+ $data['path'] = $this->getDefaultPath($path);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get default cookie path according to RFC 6265
+ * http://tools.ietf.org/search/rfc6265#section-5.1.4 Paths and Path-Match
+ *
+ * @param string $path Request uri-path
+ *
+ * @return string
+ */
+ protected function getDefaultPath($path) {
+ // "The user agent MUST use an algorithm equivalent to the following algorithm
+ // to compute the default-path of a cookie:"
+
+ // "2. If the uri-path is empty or if the first character of the uri-path is not
+ // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
+ if (empty($path) || substr($path, 0, 1) !== '/') {
+ return '/';
+ }
+
+ // "3. If the uri-path contains no more than one %x2F ("/") character, output
+ // %x2F ("/") and skip the remaining step."
+ if ($path === "/") {
+ return $path;
+ }
+
+ $rightSlashPos = strrpos($path, '/');
+ if ($rightSlashPos === 0) {
+ return "/";
+ }
+
+ // "4. Output the characters of the uri-path from the first character up to,
+ // but not including, the right-most %x2F ("/")."
+ return substr($path, 0, $rightSlashPos);
+
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php
new file mode 100644
index 0000000..d21ffe2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Guzzle\Parser\Cookie;
+
+/**
+ * Cookie parser interface
+ */
+interface CookieParserInterface
+{
+ /**
+ * Parse a cookie string as set in a Set-Cookie HTTP header and return an associative array of data.
+ *
+ * @param string $cookie Cookie header value to parse
+ * @param string $host Host of an associated request
+ * @param string $path Path of an associated request
+ * @param bool $decode Set to TRUE to urldecode cookie values
+ *
+ * @return array|bool Returns FALSE on failure or returns an array of arrays, with each of the sub arrays including:
+ * - domain (string) - Domain of the cookie
+ * - path (string) - Path of the cookie
+ * - cookies (array) - Associative array of cookie names and values
+ * - max_age (int) - Lifetime of the cookie in seconds
+ * - version (int) - Version of the cookie specification. RFC 2965 is 1
+ * - secure (bool) - Whether or not this is a secure cookie
+ * - discard (bool) - Whether or not this is a discardable cookie
+ * - custom (string) - Custom cookie data array
+ * - comment (string) - How the cookie is intended to be used
+ * - comment_url (str)- URL that contains info on how it will be used
+ * - port (array|str) - Array of ports or null
+ * - http_only (bool) - HTTP only cookie
+ */
+ public function parseCookie($cookie, $host = null, $path = null, $decode = false);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/AbstractMessageParser.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/AbstractMessageParser.php
new file mode 100644
index 0000000..d25f9cc
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/AbstractMessageParser.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Guzzle\Parser\Message;
+
+/**
+ * Implements shared message parsing functionality
+ */
+abstract class AbstractMessageParser implements MessageParserInterface
+{
+ /**
+ * Create URL parts from HTTP message parts
+ *
+ * @param string $requestUrl Associated URL
+ * @param array $parts HTTP message parts
+ *
+ * @return array
+ */
+ protected function getUrlPartsFromMessage($requestUrl, array $parts)
+ {
+ // Parse the URL information from the message
+ $urlParts = array(
+ 'path' => $requestUrl,
+ 'scheme' => 'http'
+ );
+
+ // Check for the Host header
+ if (isset($parts['headers']['Host'])) {
+ $urlParts['host'] = $parts['headers']['Host'];
+ } elseif (isset($parts['headers']['host'])) {
+ $urlParts['host'] = $parts['headers']['host'];
+ } else {
+ $urlParts['host'] = null;
+ }
+
+ if (false === strpos($urlParts['host'], ':')) {
+ $urlParts['port'] = '';
+ } else {
+ $hostParts = explode(':', $urlParts['host']);
+ $urlParts['host'] = trim($hostParts[0]);
+ $urlParts['port'] = (int) trim($hostParts[1]);
+ if ($urlParts['port'] == 443) {
+ $urlParts['scheme'] = 'https';
+ }
+ }
+
+ // Check if a query is present
+ $path = $urlParts['path'];
+ $qpos = strpos($path, '?');
+ if ($qpos) {
+ $urlParts['query'] = substr($path, $qpos + 1);
+ $urlParts['path'] = substr($path, 0, $qpos);
+ } else {
+ $urlParts['query'] = '';
+ }
+
+ return $urlParts;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php
new file mode 100644
index 0000000..efc1aa3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Guzzle\Parser\Message;
+
+/**
+ * Default request and response parser used by Guzzle. Optimized for speed.
+ */
+class MessageParser extends AbstractMessageParser
+{
+ public function parseRequest($message)
+ {
+ if (!$message) {
+ return false;
+ }
+
+ $parts = $this->parseMessage($message);
+
+ // Parse the protocol and protocol version
+ if (isset($parts['start_line'][2])) {
+ $startParts = explode('/', $parts['start_line'][2]);
+ $protocol = strtoupper($startParts[0]);
+ $version = isset($startParts[1]) ? $startParts[1] : '1.1';
+ } else {
+ $protocol = 'HTTP';
+ $version = '1.1';
+ }
+
+ $parsed = array(
+ 'method' => strtoupper($parts['start_line'][0]),
+ 'protocol' => $protocol,
+ 'version' => $version,
+ 'headers' => $parts['headers'],
+ 'body' => $parts['body']
+ );
+
+ $parsed['request_url'] = $this->getUrlPartsFromMessage(isset($parts['start_line'][1]) ? $parts['start_line'][1] : '' , $parsed);
+
+ return $parsed;
+ }
+
+ public function parseResponse($message)
+ {
+ if (!$message) {
+ return false;
+ }
+
+ $parts = $this->parseMessage($message);
+ list($protocol, $version) = explode('/', trim($parts['start_line'][0]));
+
+ return array(
+ 'protocol' => $protocol,
+ 'version' => $version,
+ 'code' => $parts['start_line'][1],
+ 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '',
+ 'headers' => $parts['headers'],
+ 'body' => $parts['body']
+ );
+ }
+
+ /**
+ * Parse a message into parts
+ *
+ * @param string $message Message to parse
+ *
+ * @return array
+ */
+ protected function parseMessage($message)
+ {
+ $startLine = null;
+ $headers = array();
+ $body = '';
+
+ // Iterate over each line in the message, accounting for line endings
+ $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
+ for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
+
+ $line = $lines[$i];
+
+ // If two line breaks were encountered, then this is the end of body
+ if (empty($line)) {
+ if ($i < $totalLines - 1) {
+ $body = implode('', array_slice($lines, $i + 2));
+ }
+ break;
+ }
+
+ // Parse message headers
+ if (!$startLine) {
+ $startLine = explode(' ', $line, 3);
+ } elseif (strpos($line, ':')) {
+ $parts = explode(':', $line, 2);
+ $key = trim($parts[0]);
+ $value = isset($parts[1]) ? trim($parts[1]) : '';
+ if (!isset($headers[$key])) {
+ $headers[$key] = $value;
+ } elseif (!is_array($headers[$key])) {
+ $headers[$key] = array($headers[$key], $value);
+ } else {
+ $headers[$key][] = $value;
+ }
+ }
+ }
+
+ return array(
+ 'start_line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php
new file mode 100644
index 0000000..cc44808
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Parser\Message;
+
+/**
+ * HTTP message parser interface used to parse HTTP messages into an array
+ */
+interface MessageParserInterface
+{
+ /**
+ * Parse an HTTP request message into an associative array of parts.
+ *
+ * @param string $message HTTP request to parse
+ *
+ * @return array|bool Returns false if the message is invalid
+ */
+ public function parseRequest($message);
+
+ /**
+ * Parse an HTTP response message into an associative array of parts.
+ *
+ * @param string $message HTTP response to parse
+ *
+ * @return array|bool Returns false if the message is invalid
+ */
+ public function parseResponse($message);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/PeclHttpMessageParser.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/PeclHttpMessageParser.php
new file mode 100644
index 0000000..944aaa2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/PeclHttpMessageParser.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Guzzle\Parser\Message;
+
+/**
+ * Pecl HTTP message parser
+ */
+class PeclHttpMessageParser extends AbstractMessageParser
+{
+ public function parseRequest($message)
+ {
+ if (!$message) {
+ return false;
+ }
+
+ $parts = http_parse_message($message);
+
+ $parsed = array(
+ 'method' => $parts->requestMethod,
+ 'protocol' => 'HTTP',
+ 'version' => number_format($parts->httpVersion, 1),
+ 'headers' => $parts->headers,
+ 'body' => $parts->body
+ );
+
+ $parsed['request_url'] = $this->getUrlPartsFromMessage($parts->requestUrl, $parsed);
+
+ return $parsed;
+ }
+
+ public function parseResponse($message)
+ {
+ if (!$message) {
+ return false;
+ }
+
+ $parts = http_parse_message($message);
+
+ return array(
+ 'protocol' => 'HTTP',
+ 'version' => number_format($parts->httpVersion, 1),
+ 'code' => $parts->responseCode,
+ 'reason_phrase' => $parts->responseStatus,
+ 'headers' => $parts->headers,
+ 'body' => $parts->body
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php
new file mode 100644
index 0000000..f838683
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Guzzle\Parser;
+
+/**
+ * Registry of parsers used by the application
+ */
+class ParserRegistry
+{
+ /** @var ParserRegistry Singleton instance */
+ protected static $instance;
+
+ /** @var array Array of parser instances */
+ protected $instances = array();
+
+ /** @var array Mapping of parser name to default class */
+ protected $mapping = array(
+ 'message' => 'Guzzle\\Parser\\Message\\MessageParser',
+ 'cookie' => 'Guzzle\\Parser\\Cookie\\CookieParser',
+ 'url' => 'Guzzle\\Parser\\Url\\UrlParser',
+ 'uri_template' => 'Guzzle\\Parser\\UriTemplate\\UriTemplate',
+ );
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new static;
+ }
+
+ return self::$instance;
+ }
+
+ public function __construct()
+ {
+ // Use the PECL URI template parser if available
+ if (extension_loaded('uri_template')) {
+ $this->mapping['uri_template'] = 'Guzzle\\Parser\\UriTemplate\\PeclUriTemplate';
+ }
+ }
+
+ /**
+ * Get a parser by name from an instance
+ *
+ * @param string $name Name of the parser to retrieve
+ *
+ * @return mixed|null
+ */
+ public function getParser($name)
+ {
+ if (!isset($this->instances[$name])) {
+ if (!isset($this->mapping[$name])) {
+ return null;
+ }
+ $class = $this->mapping[$name];
+ $this->instances[$name] = new $class();
+ }
+
+ return $this->instances[$name];
+ }
+
+ /**
+ * Register a custom parser by name with the register
+ *
+ * @param string $name Name or handle of the parser to register
+ * @param mixed $parser Instantiated parser to register
+ */
+ public function registerParser($name, $parser)
+ {
+ $this->instances[$name] = $parser;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php
new file mode 100644
index 0000000..b0764e8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Guzzle\Parser\UriTemplate;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Expands URI templates using the uri_template pecl extension (pecl install uri_template-beta)
+ *
+ * @link http://pecl.php.net/package/uri_template
+ * @link https://github.com/ioseb/uri-template
+ */
+class PeclUriTemplate implements UriTemplateInterface
+{
+ public function __construct()
+ {
+ if (!extension_loaded('uri_template')) {
+ throw new RuntimeException('uri_template PECL extension must be installed to use PeclUriTemplate');
+ }
+ }
+
+ public function expand($template, array $variables)
+ {
+ return uri_template($template, $variables);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplate.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplate.php
new file mode 100644
index 0000000..0df032f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplate.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Guzzle\Parser\UriTemplate;
+
+/**
+ * Expands URI templates using an array of variables
+ *
+ * @link http://tools.ietf.org/html/draft-gregorio-uritemplate-08
+ */
+class UriTemplate implements UriTemplateInterface
+{
+ const DEFAULT_PATTERN = '/\{([^\}]+)\}/';
+
+ /** @var string URI template */
+ private $template;
+
+ /** @var array Variables to use in the template expansion */
+ private $variables;
+
+ /** @var string Regex used to parse expressions */
+ private $regex = self::DEFAULT_PATTERN;
+
+ /** @var array Hash for quick operator lookups */
+ private static $operatorHash = array(
+ '+' => true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true
+ );
+
+ /** @var array Delimiters */
+ private static $delims = array(
+ ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='
+ );
+
+ /** @var array Percent encoded delimiters */
+ private static $delimsPct = array(
+ '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
+ '%3B', '%3D'
+ );
+
+ public function expand($template, array $variables)
+ {
+ if ($this->regex == self::DEFAULT_PATTERN && false === strpos($template, '{')) {
+ return $template;
+ }
+
+ $this->template = $template;
+ $this->variables = $variables;
+
+ return preg_replace_callback($this->regex, array($this, 'expandMatch'), $this->template);
+ }
+
+ /**
+ * Set the regex patten used to expand URI templates
+ *
+ * @param string $regexPattern
+ */
+ public function setRegex($regexPattern)
+ {
+ $this->regex = $regexPattern;
+ }
+
+ /**
+ * Parse an expression into parts
+ *
+ * @param string $expression Expression to parse
+ *
+ * @return array Returns an associative array of parts
+ */
+ private function parseExpression($expression)
+ {
+ // Check for URI operators
+ $operator = '';
+
+ if (isset(self::$operatorHash[$expression[0]])) {
+ $operator = $expression[0];
+ $expression = substr($expression, 1);
+ }
+
+ $values = explode(',', $expression);
+ foreach ($values as &$value) {
+ $value = trim($value);
+ $varspec = array();
+ $substrPos = strpos($value, ':');
+ if ($substrPos) {
+ $varspec['value'] = substr($value, 0, $substrPos);
+ $varspec['modifier'] = ':';
+ $varspec['position'] = (int) substr($value, $substrPos + 1);
+ } elseif (substr($value, -1) == '*') {
+ $varspec['modifier'] = '*';
+ $varspec['value'] = substr($value, 0, -1);
+ } else {
+ $varspec['value'] = (string) $value;
+ $varspec['modifier'] = '';
+ }
+ $value = $varspec;
+ }
+
+ return array(
+ 'operator' => $operator,
+ 'values' => $values
+ );
+ }
+
+ /**
+ * Process an expansion
+ *
+ * @param array $matches Matches met in the preg_replace_callback
+ *
+ * @return string Returns the replacement string
+ */
+ private function expandMatch(array $matches)
+ {
+ static $rfc1738to3986 = array(
+ '+' => '%20',
+ '%7e' => '~'
+ );
+
+ $parsed = self::parseExpression($matches[1]);
+ $replacements = array();
+
+ $prefix = $parsed['operator'];
+ $joiner = $parsed['operator'];
+ $useQueryString = false;
+ if ($parsed['operator'] == '?') {
+ $joiner = '&';
+ $useQueryString = true;
+ } elseif ($parsed['operator'] == '&') {
+ $useQueryString = true;
+ } elseif ($parsed['operator'] == '#') {
+ $joiner = ',';
+ } elseif ($parsed['operator'] == ';') {
+ $useQueryString = true;
+ } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') {
+ $joiner = ',';
+ $prefix = '';
+ }
+
+ foreach ($parsed['values'] as $value) {
+
+ if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) {
+ continue;
+ }
+
+ $variable = $this->variables[$value['value']];
+ $actuallyUseQueryString = $useQueryString;
+ $expanded = '';
+
+ if (is_array($variable)) {
+
+ $isAssoc = $this->isAssoc($variable);
+ $kvp = array();
+ foreach ($variable as $key => $var) {
+
+ if ($isAssoc) {
+ $key = rawurlencode($key);
+ $isNestedArray = is_array($var);
+ } else {
+ $isNestedArray = false;
+ }
+
+ if (!$isNestedArray) {
+ $var = rawurlencode($var);
+ if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
+ $var = $this->decodeReserved($var);
+ }
+ }
+
+ if ($value['modifier'] == '*') {
+ if ($isAssoc) {
+ if ($isNestedArray) {
+ // Nested arrays must allow for deeply nested structures
+ $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986);
+ } else {
+ $var = $key . '=' . $var;
+ }
+ } elseif ($key > 0 && $actuallyUseQueryString) {
+ $var = $value['value'] . '=' . $var;
+ }
+ }
+
+ $kvp[$key] = $var;
+ }
+
+ if (empty($variable)) {
+ $actuallyUseQueryString = false;
+ } elseif ($value['modifier'] == '*') {
+ $expanded = implode($joiner, $kvp);
+ if ($isAssoc) {
+ // Don't prepend the value name when using the explode modifier with an associative array
+ $actuallyUseQueryString = false;
+ }
+ } else {
+ if ($isAssoc) {
+ // When an associative array is encountered and the explode modifier is not set, then the
+ // result must be a comma separated list of keys followed by their respective values.
+ foreach ($kvp as $k => &$v) {
+ $v = $k . ',' . $v;
+ }
+ }
+ $expanded = implode(',', $kvp);
+ }
+
+ } else {
+ if ($value['modifier'] == ':') {
+ $variable = substr($variable, 0, $value['position']);
+ }
+ $expanded = rawurlencode($variable);
+ if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
+ $expanded = $this->decodeReserved($expanded);
+ }
+ }
+
+ if ($actuallyUseQueryString) {
+ if (!$expanded && $joiner != '&') {
+ $expanded = $value['value'];
+ } else {
+ $expanded = $value['value'] . '=' . $expanded;
+ }
+ }
+
+ $replacements[] = $expanded;
+ }
+
+ $ret = implode($joiner, $replacements);
+ if ($ret && $prefix) {
+ return $prefix . $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determines if an array is associative
+ *
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ private function isAssoc(array $array)
+ {
+ return (bool) count(array_filter(array_keys($array), 'is_string'));
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and # modifiers)
+ *
+ * @param string $string String to fix
+ *
+ * @return string
+ */
+ private function decodeReserved($string)
+ {
+ return str_replace(self::$delimsPct, self::$delims, $string);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php
new file mode 100644
index 0000000..c81d515
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Parser\UriTemplate;
+
+/**
+ * Expands URI templates using an array of variables
+ *
+ * @link http://tools.ietf.org/html/rfc6570
+ */
+interface UriTemplateInterface
+{
+ /**
+ * Expand the URI template using the supplied variables
+ *
+ * @param string $template URI Template to expand
+ * @param array $variables Variables to use with the expansion
+ *
+ * @return string Returns the expanded template
+ */
+ public function expand($template, array $variables);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParser.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParser.php
new file mode 100644
index 0000000..c4cc896
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParser.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Guzzle\Parser\Url;
+
+use Guzzle\Common\Version;
+
+/**
+ * Parses URLs into parts using PHP's built-in parse_url() function
+ * @deprecated Just use parse_url. UTF-8 characters should be percent encoded anyways.
+ * @codeCoverageIgnore
+ */
+class UrlParser implements UrlParserInterface
+{
+ /** @var bool Whether or not to work with UTF-8 strings */
+ protected $utf8 = false;
+
+ /**
+ * Set whether or not to attempt to handle UTF-8 strings (still WIP)
+ *
+ * @param bool $utf8 Set to TRUE to handle UTF string
+ */
+ public function setUtf8Support($utf8)
+ {
+ $this->utf8 = $utf8;
+ }
+
+ public function parseUrl($url)
+ {
+ Version::warn(__CLASS__ . ' is deprecated. Just use parse_url()');
+
+ static $defaults = array('scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null,
+ 'user' => null, 'pass' => null, 'fragment' => null);
+
+ $parts = parse_url($url);
+
+ // Need to handle query parsing specially for UTF-8 requirements
+ if ($this->utf8 && isset($parts['query'])) {
+ $queryPos = strpos($url, '?');
+ if (isset($parts['fragment'])) {
+ $parts['query'] = substr($url, $queryPos + 1, strpos($url, '#') - $queryPos - 1);
+ } else {
+ $parts['query'] = substr($url, $queryPos + 1);
+ }
+ }
+
+ return $parts + $defaults;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php
new file mode 100644
index 0000000..89ac4b3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Parser\Url;
+
+/**
+ * URL parser interface
+ */
+interface UrlParserInterface
+{
+ /**
+ * Parse a URL using special handling for a subset of UTF-8 characters in the query string if needed.
+ *
+ * @param string $url URL to parse
+ *
+ * @return array Returns an array identical to what is returned from parse_url(). When an array key is missing from
+ * this array, you must fill it in with NULL to avoid warnings in calling code.
+ */
+ public function parseUrl($url);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Parser/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Parser/composer.json
new file mode 100644
index 0000000..378b281
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Parser/composer.json
@@ -0,0 +1,19 @@
+{
+ "name": "guzzle/parser",
+ "homepage": "http://guzzlephp.org/",
+ "description": "Interchangeable parsers used by Guzzle",
+ "keywords": ["HTTP", "message", "cookie", "URL", "URI Template"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Parser": "" }
+ },
+ "target-dir": "Guzzle/Parser",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php
new file mode 100644
index 0000000..ae59418
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Guzzle\Plugin\Async;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\CurlException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Sends requests but does not wait for the response
+ */
+class AsyncPlugin implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => 'onBeforeSend',
+ 'request.exception' => 'onRequestTimeout',
+ 'request.sent' => 'onRequestSent',
+ 'curl.callback.progress' => 'onCurlProgress'
+ );
+ }
+
+ /**
+ * Event used to ensure that progress callback are emitted from the curl handle's request mediator.
+ *
+ * @param Event $event
+ */
+ public function onBeforeSend(Event $event)
+ {
+ // Ensure that progress callbacks are dispatched
+ $event['request']->getCurlOptions()->set('progress', true);
+ }
+
+ /**
+ * Event emitted when a curl progress function is called. When the amount of data uploaded == the amount of data to
+ * upload OR any bytes have been downloaded, then time the request out after 1ms because we're done with
+ * transmitting the request, and tell curl not download a body.
+ *
+ * @param Event $event
+ */
+ public function onCurlProgress(Event $event)
+ {
+ if ($event['handle'] &&
+ ($event['downloaded'] || (isset($event['uploaded']) && $event['upload_size'] === $event['uploaded']))
+ ) {
+ // Timeout after 1ms
+ curl_setopt($event['handle'], CURLOPT_TIMEOUT_MS, 1);
+ // Even if the response is quick, tell curl not to download the body.
+ // - Note that we can only perform this shortcut if the request transmitted a body so as to ensure that the
+ // request method is not converted to a HEAD request before the request was sent via curl.
+ if ($event['uploaded']) {
+ curl_setopt($event['handle'], CURLOPT_NOBODY, true);
+ }
+ }
+ }
+
+ /**
+ * Event emitted when a curl exception occurs. Ignore the exception and set a mock response.
+ *
+ * @param Event $event
+ */
+ public function onRequestTimeout(Event $event)
+ {
+ if ($event['exception'] instanceof CurlException) {
+ $event['request']->setResponse(new Response(200, array(
+ 'X-Guzzle-Async' => 'Did not wait for the response'
+ )));
+ }
+ }
+
+ /**
+ * Event emitted when a request completes because it took less than 1ms. Add an X-Guzzle-Async header to notify the
+ * caller that there is no body in the message.
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ // Let the caller know this was meant to be async
+ $event['request']->getResponse()->setHeader('X-Guzzle-Async', 'Did not wait for the response');
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json
new file mode 100644
index 0000000..dc3fc5b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-async",
+ "description": "Guzzle async request plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Async": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Async",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php
new file mode 100644
index 0000000..0a85983
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Abstract backoff strategy that allows for a chain of responsibility
+ */
+abstract class AbstractBackoffStrategy implements BackoffStrategyInterface
+{
+ /** @var AbstractBackoffStrategy Next strategy in the chain */
+ protected $next;
+
+ /** @param AbstractBackoffStrategy $next Next strategy in the chain */
+ public function setNext(AbstractBackoffStrategy $next)
+ {
+ $this->next = $next;
+ }
+
+ /**
+ * Get the next backoff strategy in the chain
+ *
+ * @return AbstractBackoffStrategy|null
+ */
+ public function getNext()
+ {
+ return $this->next;
+ }
+
+ public function getBackoffPeriod(
+ $retries,
+ RequestInterface $request,
+ Response $response = null,
+ HttpException $e = null
+ ) {
+ $delay = $this->getDelay($retries, $request, $response, $e);
+ if ($delay === false) {
+ // The strategy knows that this must not be retried
+ return false;
+ } elseif ($delay === null) {
+ // If the strategy is deferring a decision and the next strategy will not make a decision then return false
+ return !$this->next || !$this->next->makesDecision()
+ ? false
+ : $this->next->getBackoffPeriod($retries, $request, $response, $e);
+ } elseif ($delay === true) {
+ // if the strategy knows that it must retry but is deferring to the next to determine the delay
+ if (!$this->next) {
+ return 0;
+ } else {
+ $next = $this->next;
+ while ($next->makesDecision() && $next->getNext()) {
+ $next = $next->getNext();
+ }
+ return !$next->makesDecision() ? $next->getBackoffPeriod($retries, $request, $response, $e) : 0;
+ }
+ } else {
+ return $delay;
+ }
+ }
+
+ /**
+ * Check if the strategy does filtering and makes decisions on whether or not to retry.
+ *
+ * Strategies that return false will never retry if all of the previous strategies in a chain defer on a backoff
+ * decision.
+ *
+ * @return bool
+ */
+ abstract public function makesDecision();
+
+ /**
+ * Implement the concrete strategy
+ *
+ * @param int $retries Number of retries of the request
+ * @param RequestInterface $request Request that was sent
+ * @param Response $response Response that was received. Note that there may not be a response
+ * @param HttpException $e Exception that was encountered if any
+ *
+ * @return bool|int|null Returns false to not retry or the number of seconds to delay between retries. Return true
+ * or null to defer to the next strategy if available, and if not, return 0.
+ */
+ abstract protected function getDelay(
+ $retries,
+ RequestInterface $request,
+ Response $response = null,
+ HttpException $e = null
+ );
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php
new file mode 100644
index 0000000..6ebee6c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+/**
+ * Strategy used to retry when certain error codes are encountered
+ */
+abstract class AbstractErrorCodeBackoffStrategy extends AbstractBackoffStrategy
+{
+ /** @var array Default cURL errors to retry */
+ protected static $defaultErrorCodes = array();
+
+ /** @var array Error codes that can be retried */
+ protected $errorCodes;
+
+ /**
+ * @param array $codes Array of codes that should be retried
+ * @param BackoffStrategyInterface $next The optional next strategy
+ */
+ public function __construct(array $codes = null, BackoffStrategyInterface $next = null)
+ {
+ $this->errorCodes = array_fill_keys($codes ?: static::$defaultErrorCodes, 1);
+ $this->next = $next;
+ }
+
+ /**
+ * Get the default failure codes to retry
+ *
+ * @return array
+ */
+ public static function getDefaultFailureCodes()
+ {
+ return static::$defaultErrorCodes;
+ }
+
+ public function makesDecision()
+ {
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php
new file mode 100644
index 0000000..ec54c28
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Common\Event;
+use Guzzle\Log\LogAdapterInterface;
+use Guzzle\Log\MessageFormatter;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Logs backoff retries triggered from the BackoffPlugin
+ *
+ * Format your log messages using a template that can contain template substitutions found in {@see MessageFormatter}.
+ * In addition to the default template substitutions, there is also:
+ *
+ * - retries: The number of times the request has been retried
+ * - delay: The amount of time the request is being delayed
+ */
+class BackoffLogger implements EventSubscriberInterface
+{
+ /** @var string Default log message template */
+ const DEFAULT_FORMAT = '[{ts}] {method} {url} - {code} {phrase} - Retries: {retries}, Delay: {delay}, Time: {connect_time}, {total_time}, cURL: {curl_code} {curl_error}';
+
+ /** @var LogAdapterInterface Logger used to log retries */
+ protected $logger;
+
+ /** @var MessageFormatter Formatter used to format log messages */
+ protected $formatter;
+
+ /**
+ * @param LogAdapterInterface $logger Logger used to log the retries
+ * @param MessageFormatter $formatter Formatter used to format log messages
+ */
+ public function __construct(LogAdapterInterface $logger, MessageFormatter $formatter = null)
+ {
+ $this->logger = $logger;
+ $this->formatter = $formatter ?: new MessageFormatter(self::DEFAULT_FORMAT);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry');
+ }
+
+ /**
+ * Set the template to use for logging
+ *
+ * @param string $template Log message template
+ *
+ * @return self
+ */
+ public function setTemplate($template)
+ {
+ $this->formatter->setTemplate($template);
+
+ return $this;
+ }
+
+ /**
+ * Called when a request is being retried
+ *
+ * @param Event $event Event emitted
+ */
+ public function onRequestRetry(Event $event)
+ {
+ $this->logger->log($this->formatter->format(
+ $event['request'],
+ $event['response'],
+ $event['handle'],
+ array(
+ 'retries' => $event['retries'],
+ 'delay' => $event['delay']
+ )
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php
new file mode 100644
index 0000000..99ace05
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Curl\CurlMultiInterface;
+use Guzzle\Http\Exception\CurlException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Plugin to automatically retry failed HTTP requests using a backoff strategy
+ */
+class BackoffPlugin extends AbstractHasDispatcher implements EventSubscriberInterface
+{
+ const DELAY_PARAM = CurlMultiInterface::BLOCKING;
+ const RETRY_PARAM = 'plugins.backoff.retry_count';
+ const RETRY_EVENT = 'plugins.backoff.retry';
+
+ /** @var BackoffStrategyInterface Backoff strategy */
+ protected $strategy;
+
+ /**
+ * @param BackoffStrategyInterface $strategy The backoff strategy used to determine whether or not to retry and
+ * the amount of delay between retries.
+ */
+ public function __construct(BackoffStrategyInterface $strategy = null)
+ {
+ $this->strategy = $strategy;
+ }
+
+ /**
+ * Retrieve a basic truncated exponential backoff plugin that will retry HTTP errors and cURL errors
+ *
+ * @param int $maxRetries Maximum number of retries
+ * @param array $httpCodes HTTP response codes to retry
+ * @param array $curlCodes cURL error codes to retry
+ *
+ * @return self
+ */
+ public static function getExponentialBackoff(
+ $maxRetries = 3,
+ array $httpCodes = null,
+ array $curlCodes = null
+ ) {
+ return new self(new TruncatedBackoffStrategy($maxRetries,
+ new HttpBackoffStrategy($httpCodes,
+ new CurlBackoffStrategy($curlCodes,
+ new ExponentialBackoffStrategy()
+ )
+ )
+ ));
+ }
+
+ public static function getAllEvents()
+ {
+ return array(self::RETRY_EVENT);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.sent' => 'onRequestSent',
+ 'request.exception' => 'onRequestSent',
+ CurlMultiInterface::POLLING_REQUEST => 'onRequestPoll'
+ );
+ }
+
+ /**
+ * Called when a request has been sent and isn't finished processing
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ $request = $event['request'];
+ $response = $event['response'];
+ $exception = $event['exception'];
+
+ $params = $request->getParams();
+ $retries = (int) $params->get(self::RETRY_PARAM);
+ $delay = $this->strategy->getBackoffPeriod($retries, $request, $response, $exception);
+
+ if ($delay !== false) {
+ // Calculate how long to wait until the request should be retried
+ $params->set(self::RETRY_PARAM, ++$retries)
+ ->set(self::DELAY_PARAM, microtime(true) + $delay);
+ // Send the request again
+ $request->setState(RequestInterface::STATE_TRANSFER);
+ $this->dispatch(self::RETRY_EVENT, array(
+ 'request' => $request,
+ 'response' => $response,
+ 'handle' => ($exception && $exception instanceof CurlException) ? $exception->getCurlHandle() : null,
+ 'retries' => $retries,
+ 'delay' => $delay
+ ));
+ }
+ }
+
+ /**
+ * Called when a request is polling in the curl multi object
+ *
+ * @param Event $event
+ */
+ public function onRequestPoll(Event $event)
+ {
+ $request = $event['request'];
+ $delay = $request->getParams()->get(self::DELAY_PARAM);
+
+ // If the duration of the delay has passed, retry the request using the pool
+ if (null !== $delay && microtime(true) >= $delay) {
+ // Remove the request from the pool and then add it back again. This is required for cURL to know that we
+ // want to retry sending the easy handle.
+ $request->getParams()->remove(self::DELAY_PARAM);
+ // Rewind the request body if possible
+ if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()) {
+ $request->getBody()->seek(0);
+ }
+ $multi = $event['curl_multi'];
+ $multi->remove($request);
+ $multi->add($request);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php
new file mode 100644
index 0000000..4e590db
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Strategy to determine if a request should be retried and how long to delay between retries
+ */
+interface BackoffStrategyInterface
+{
+ /**
+ * Get the amount of time to delay in seconds before retrying a request
+ *
+ * @param int $retries Number of retries of the request
+ * @param RequestInterface $request Request that was sent
+ * @param Response $response Response that was received. Note that there may not be a response
+ * @param HttpException $e Exception that was encountered if any
+ *
+ * @return bool|int Returns false to not retry or the number of seconds to delay between retries
+ */
+ public function getBackoffPeriod(
+ $retries,
+ RequestInterface $request,
+ Response $response = null,
+ HttpException $e = null
+ );
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CallbackBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CallbackBackoffStrategy.php
new file mode 100644
index 0000000..b4f77c3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CallbackBackoffStrategy.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Strategy that will invoke a closure to determine whether or not to retry with a delay
+ */
+class CallbackBackoffStrategy extends AbstractBackoffStrategy
+{
+ /** @var \Closure|array|mixed Callable method to invoke */
+ protected $callback;
+
+ /** @var bool Whether or not this strategy makes a retry decision */
+ protected $decision;
+
+ /**
+ * @param \Closure|array|mixed $callback Callable method to invoke
+ * @param bool $decision Set to true if this strategy makes a backoff decision
+ * @param BackoffStrategyInterface $next The optional next strategy
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($callback, $decision, BackoffStrategyInterface $next = null)
+ {
+ if (!is_callable($callback)) {
+ throw new InvalidArgumentException('The callback must be callable');
+ }
+ $this->callback = $callback;
+ $this->decision = (bool) $decision;
+ $this->next = $next;
+ }
+
+ public function makesDecision()
+ {
+ return $this->decision;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ return call_user_func($this->callback, $retries, $request, $response, $e);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php
new file mode 100644
index 0000000..061d2a4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Will retry the request using the same amount of delay for each retry.
+ *
+ * Warning: If no decision making strategies precede this strategy in the the chain, then all requests will be retried
+ */
+class ConstantBackoffStrategy extends AbstractBackoffStrategy
+{
+ /** @var int Amount of time for each delay */
+ protected $delay;
+
+ /** @param int $delay Amount of time to delay between each additional backoff */
+ public function __construct($delay)
+ {
+ $this->delay = $delay;
+ }
+
+ public function makesDecision()
+ {
+ return false;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ return $this->delay;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php
new file mode 100644
index 0000000..a584ed4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+use Guzzle\Http\Exception\CurlException;
+
+/**
+ * Strategy used to retry when certain cURL error codes are encountered.
+ */
+class CurlBackoffStrategy extends AbstractErrorCodeBackoffStrategy
+{
+ /** @var array Default cURL errors to retry */
+ protected static $defaultErrorCodes = array(
+ CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_PARTIAL_FILE, CURLE_WRITE_ERROR, CURLE_READ_ERROR,
+ CURLE_OPERATION_TIMEOUTED, CURLE_SSL_CONNECT_ERROR, CURLE_HTTP_PORT_FAILED, CURLE_GOT_NOTHING,
+ CURLE_SEND_ERROR, CURLE_RECV_ERROR
+ );
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ if ($e && $e instanceof CurlException) {
+ return isset($this->errorCodes[$e->getErrorNo()]) ? true : null;
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php
new file mode 100644
index 0000000..fb2912d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Implements an exponential backoff retry strategy.
+ *
+ * Warning: If no decision making strategies precede this strategy in the the chain, then all requests will be retried
+ */
+class ExponentialBackoffStrategy extends AbstractBackoffStrategy
+{
+ public function makesDecision()
+ {
+ return false;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ return (int) pow(2, $retries);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/HttpBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/HttpBackoffStrategy.php
new file mode 100644
index 0000000..9c63a14
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/HttpBackoffStrategy.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Strategy used to retry HTTP requests based on the response code.
+ *
+ * Retries 500 and 503 error by default.
+ */
+class HttpBackoffStrategy extends AbstractErrorCodeBackoffStrategy
+{
+ /** @var array Default cURL errors to retry */
+ protected static $defaultErrorCodes = array(500, 503);
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ if ($response) {
+ //Short circuit the rest of the checks if it was successful
+ if ($response->isSuccessful()) {
+ return false;
+ } else {
+ return isset($this->errorCodes[$response->getStatusCode()]) ? true : null;
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php
new file mode 100644
index 0000000..b35e8a4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Implements a linear backoff retry strategy.
+ *
+ * Warning: If no decision making strategies precede this strategy in the the chain, then all requests will be retried
+ */
+class LinearBackoffStrategy extends AbstractBackoffStrategy
+{
+ /** @var int Amount of time to progress each delay */
+ protected $step;
+
+ /**
+ * @param int $step Amount of time to increase the delay each additional backoff
+ */
+ public function __construct($step = 1)
+ {
+ $this->step = $step;
+ }
+
+ public function makesDecision()
+ {
+ return false;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ return $retries * $this->step;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php
new file mode 100644
index 0000000..4fd73fe
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Strategy used to retry HTTP requests when the response's reason phrase matches one of the registered phrases.
+ */
+class ReasonPhraseBackoffStrategy extends AbstractErrorCodeBackoffStrategy
+{
+ public function makesDecision()
+ {
+ return true;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ if ($response) {
+ return isset($this->errorCodes[$response->getReasonPhrase()]) ? true : null;
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php
new file mode 100644
index 0000000..3608f35
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Plugin\Backoff;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\HttpException;
+
+/**
+ * Strategy that will not retry more than a certain number of times.
+ */
+class TruncatedBackoffStrategy extends AbstractBackoffStrategy
+{
+ /** @var int Maximum number of retries per request */
+ protected $max;
+
+ /**
+ * @param int $maxRetries Maximum number of retries per request
+ * @param BackoffStrategyInterface $next The optional next strategy
+ */
+ public function __construct($maxRetries, BackoffStrategyInterface $next = null)
+ {
+ $this->max = $maxRetries;
+ $this->next = $next;
+ }
+
+ public function makesDecision()
+ {
+ return true;
+ }
+
+ protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
+ {
+ return $retries < $this->max ? null : false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json
new file mode 100644
index 0000000..91c122c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "guzzle/plugin-backoff",
+ "description": "Guzzle backoff retry plugins",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version",
+ "guzzle/log": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Backoff": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Backoff",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php
new file mode 100644
index 0000000..7790f88
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\CacheKeyProviderInterface is no longer used');
+
+/**
+ * @deprecated This is no longer used
+ * @codeCoverageIgnore
+ */
+interface CacheKeyProviderInterface {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php
new file mode 100644
index 0000000..ce4b317
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php
@@ -0,0 +1,353 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Cache\CacheAdapterFactory;
+use Guzzle\Cache\CacheAdapterInterface;
+use Guzzle\Common\Event;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Version;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Http\Exception\CurlException;
+use Doctrine\Common\Cache\ArrayCache;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Plugin to enable the caching of GET and HEAD requests. Caching can be done on all requests passing through this
+ * plugin or only after retrieving resources with cacheable response headers.
+ *
+ * This is a simple implementation of RFC 2616 and should be considered a private transparent proxy cache, meaning
+ * authorization and private data can be cached.
+ *
+ * It also implements RFC 5861's `stale-if-error` Cache-Control extension, allowing stale cache responses to be used
+ * when an error is encountered (such as a `500 Internal Server Error` or DNS failure).
+ */
+class CachePlugin implements EventSubscriberInterface
+{
+ /** @var RevalidationInterface Cache revalidation strategy */
+ protected $revalidation;
+
+ /** @var CanCacheStrategyInterface Object used to determine if a request can be cached */
+ protected $canCache;
+
+ /** @var CacheStorageInterface $cache Object used to cache responses */
+ protected $storage;
+
+ /** @var bool */
+ protected $autoPurge;
+
+ /**
+ * @param array|CacheAdapterInterface|CacheStorageInterface $options Array of options for the cache plugin,
+ * cache adapter, or cache storage object.
+ * - CacheStorageInterface storage: Adapter used to cache responses
+ * - RevalidationInterface revalidation: Cache revalidation strategy
+ * - CanCacheInterface can_cache: Object used to determine if a request can be cached
+ * - bool auto_purge Set to true to automatically PURGE resources when non-idempotent
+ * requests are sent to a resource. Defaults to false.
+ * @throws InvalidArgumentException if no cache is provided and Doctrine cache is not installed
+ */
+ public function __construct($options = null)
+ {
+ if (!is_array($options)) {
+ if ($options instanceof CacheAdapterInterface) {
+ $options = array('storage' => new DefaultCacheStorage($options));
+ } elseif ($options instanceof CacheStorageInterface) {
+ $options = array('storage' => $options);
+ } elseif ($options) {
+ $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options)));
+ } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) {
+ // @codeCoverageIgnoreStart
+ throw new InvalidArgumentException('No cache was provided and Doctrine is not installed');
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false;
+
+ // Add a cache storage if a cache adapter was provided
+ $this->storage = isset($options['storage'])
+ ? $options['storage']
+ : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));
+
+ if (!isset($options['can_cache'])) {
+ $this->canCache = new DefaultCanCacheStrategy();
+ } else {
+ $this->canCache = is_callable($options['can_cache'])
+ ? new CallbackCanCacheStrategy($options['can_cache'])
+ : $options['can_cache'];
+ }
+
+ // Use the provided revalidation strategy or the default
+ $this->revalidation = isset($options['revalidation'])
+ ? $options['revalidation']
+ : new DefaultRevalidation($this->storage, $this->canCache);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', -255),
+ 'request.sent' => array('onRequestSent', 255),
+ 'request.error' => array('onRequestError', 0),
+ 'request.exception' => array('onRequestException', 0),
+ );
+ }
+
+ /**
+ * Check if a response in cache will satisfy the request before sending
+ *
+ * @param Event $event
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ $request = $event['request'];
+ $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
+
+ if (!$this->canCache->canCacheRequest($request)) {
+ switch ($request->getMethod()) {
+ case 'PURGE':
+ $this->purge($request);
+ $request->setResponse(new Response(200, array(), 'purged'));
+ break;
+ case 'PUT':
+ case 'POST':
+ case 'DELETE':
+ case 'PATCH':
+ if ($this->autoPurge) {
+ $this->purge($request);
+ }
+ }
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $params = $request->getParams();
+ $params['cache.lookup'] = true;
+ $response->setHeader(
+ 'Age',
+ time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now')
+ );
+ // Validate that the response satisfies the request
+ if ($this->canResponseSatisfyRequest($request, $response)) {
+ if (!isset($params['cache.hit'])) {
+ $params['cache.hit'] = true;
+ }
+ $request->setResponse($response);
+ }
+ }
+ }
+
+ /**
+ * If possible, store a response in cache after sending
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ $request = $event['request'];
+ $response = $event['response'];
+
+ if ($request->getParams()->get('cache.hit') === null &&
+ $this->canCache->canCacheRequest($request) &&
+ $this->canCache->canCacheResponse($response)
+ ) {
+ $this->storage->cache($request, $response);
+ }
+
+ $this->addResponseHeaders($request, $response);
+ }
+
+ /**
+ * If possible, return a cache response on an error
+ *
+ * @param Event $event
+ */
+ public function onRequestError(Event $event)
+ {
+ $request = $event['request'];
+
+ if (!$this->canCache->canCacheRequest($request)) {
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $response->setHeader(
+ 'Age',
+ time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now')
+ );
+
+ if ($this->canResponseSatisfyFailedRequest($request, $response)) {
+ $request->getParams()->set('cache.hit', 'error');
+ $this->addResponseHeaders($request, $response);
+ $event['response'] = $response;
+ $event->stopPropagation();
+ }
+ }
+ }
+
+ /**
+ * If possible, set a cache response on a cURL exception
+ *
+ * @param Event $event
+ *
+ * @return null
+ */
+ public function onRequestException(Event $event)
+ {
+ if (!$event['exception'] instanceof CurlException) {
+ return;
+ }
+
+ $request = $event['request'];
+ if (!$this->canCache->canCacheRequest($request)) {
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now'));
+ if (!$this->canResponseSatisfyFailedRequest($request, $response)) {
+ return;
+ }
+ $request->getParams()->set('cache.hit', 'error');
+ $request->setResponse($response);
+ $this->addResponseHeaders($request, $response);
+ $event->stopPropagation();
+ }
+ }
+
+ /**
+ * Check if a cache response satisfies a request's caching constraints
+ *
+ * @param RequestInterface $request Request to validate
+ * @param Response $response Response to validate
+ *
+ * @return bool
+ */
+ public function canResponseSatisfyRequest(RequestInterface $request, Response $response)
+ {
+ $responseAge = $response->calculateAge();
+ $reqc = $request->getHeader('Cache-Control');
+ $resc = $response->getHeader('Cache-Control');
+
+ // Check the request's max-age header against the age of the response
+ if ($reqc && $reqc->hasDirective('max-age') &&
+ $responseAge > $reqc->getDirective('max-age')) {
+ return false;
+ }
+
+ // Check the response's max-age header
+ if ($response->isFresh() === false) {
+ $maxStale = $reqc ? $reqc->getDirective('max-stale') : null;
+ if (null !== $maxStale) {
+ if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) {
+ return false;
+ }
+ } elseif ($resc && $resc->hasDirective('max-age')
+ && $responseAge > $resc->getDirective('max-age')
+ ) {
+ return false;
+ }
+ }
+
+ if ($this->revalidation->shouldRevalidate($request, $response)) {
+ try {
+ return $this->revalidation->revalidate($request, $response);
+ } catch (CurlException $e) {
+ $request->getParams()->set('cache.hit', 'error');
+ return $this->canResponseSatisfyFailedRequest($request, $response);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a cache response satisfies a failed request's caching constraints
+ *
+ * @param RequestInterface $request Request to validate
+ * @param Response $response Response to validate
+ *
+ * @return bool
+ */
+ public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)
+ {
+ $reqc = $request->getHeader('Cache-Control');
+ $resc = $response->getHeader('Cache-Control');
+ $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null;
+ $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null;
+
+ if (!$requestStaleIfError && !$responseStaleIfError) {
+ return false;
+ }
+
+ if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) {
+ return false;
+ }
+
+ if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Purge all cache entries for a given URL
+ *
+ * @param string $url URL to purge
+ */
+ public function purge($url)
+ {
+ // BC compatibility with previous version that accepted a Request object
+ $url = $url instanceof RequestInterface ? $url->getUrl() : $url;
+ $this->storage->purge($url);
+ }
+
+ /**
+ * Add the plugin's headers to a response
+ *
+ * @param RequestInterface $request Request
+ * @param Response $response Response to add headers to
+ */
+ protected function addResponseHeaders(RequestInterface $request, Response $response)
+ {
+ $params = $request->getParams();
+ $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
+
+ $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache';
+ if ($header = $response->getHeader('X-Cache-Lookup')) {
+ // Don't add duplicates
+ $values = $header->toArray();
+ $values[] = $lookup;
+ $response->setHeader('X-Cache-Lookup', array_unique($values));
+ } else {
+ $response->setHeader('X-Cache-Lookup', $lookup);
+ }
+
+ if ($params['cache.hit'] === true) {
+ $xcache = 'HIT from GuzzleCache';
+ } elseif ($params['cache.hit'] == 'error') {
+ $xcache = 'HIT_ERROR from GuzzleCache';
+ } else {
+ $xcache = 'MISS from GuzzleCache';
+ }
+
+ if ($header = $response->getHeader('X-Cache')) {
+ // Don't add duplicates
+ $values = $header->toArray();
+ $values[] = $xcache;
+ $response->setHeader('X-Cache', array_unique($values));
+ } else {
+ $response->setHeader('X-Cache', $xcache);
+ }
+
+ if ($response->isFresh() === false) {
+ $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION));
+ if ($params['cache.hit'] === 'error') {
+ $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION));
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php
new file mode 100644
index 0000000..f3d9154
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Interface used to cache HTTP requests
+ */
+interface CacheStorageInterface
+{
+ /**
+ * Get a Response from the cache for a request
+ *
+ * @param RequestInterface $request
+ *
+ * @return null|Response
+ */
+ public function fetch(RequestInterface $request);
+
+ /**
+ * Cache an HTTP request
+ *
+ * @param RequestInterface $request Request being cached
+ * @param Response $response Response to cache
+ */
+ public function cache(RequestInterface $request, Response $response);
+
+ /**
+ * Deletes cache entries that match a request
+ *
+ * @param RequestInterface $request Request to delete from cache
+ */
+ public function delete(RequestInterface $request);
+
+ /**
+ * Purge all cache entries for a given URL
+ *
+ * @param string $url
+ */
+ public function purge($url);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php
new file mode 100644
index 0000000..2d271e3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Determines if a request can be cached using a callback
+ */
+class CallbackCanCacheStrategy extends DefaultCanCacheStrategy
+{
+ /** @var callable Callback for request */
+ protected $requestCallback;
+
+ /** @var callable Callback for response */
+ protected $responseCallback;
+
+ /**
+ * @param \Closure|array|mixed $requestCallback Callable method to invoke for requests
+ * @param \Closure|array|mixed $responseCallback Callable method to invoke for responses
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($requestCallback = null, $responseCallback = null)
+ {
+ if ($requestCallback && !is_callable($requestCallback)) {
+ throw new InvalidArgumentException('Method must be callable');
+ }
+
+ if ($responseCallback && !is_callable($responseCallback)) {
+ throw new InvalidArgumentException('Method must be callable');
+ }
+
+ $this->requestCallback = $requestCallback;
+ $this->responseCallback = $responseCallback;
+ }
+
+ public function canCacheRequest(RequestInterface $request)
+ {
+ return $this->requestCallback
+ ? call_user_func($this->requestCallback, $request)
+ : parent::canCacheRequest($request);
+ }
+
+ public function canCacheResponse(Response $response)
+ {
+ return $this->responseCallback
+ ? call_user_func($this->responseCallback, $response)
+ : parent::canCacheResponse($response);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php
new file mode 100644
index 0000000..6e01a8e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Strategy used to determine if a request can be cached
+ */
+interface CanCacheStrategyInterface
+{
+ /**
+ * Determine if a request can be cached
+ *
+ * @param RequestInterface $request Request to determine
+ *
+ * @return bool
+ */
+ public function canCacheRequest(RequestInterface $request);
+
+ /**
+ * Determine if a response can be cached
+ *
+ * @param Response $response Response to determine
+ *
+ * @return bool
+ */
+ public function canCacheResponse(Response $response);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php
new file mode 100644
index 0000000..ec0dc4e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+
+\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\DefaultCacheKeyProvider is no longer used');
+
+/**
+ * @deprecated This class is no longer used
+ * @codeCoverageIgnore
+ */
+class DefaultCacheKeyProvider implements CacheKeyProviderInterface
+{
+ public function getCacheKey(RequestInterface $request)
+ {
+ // See if the key has already been calculated
+ $key = $request->getParams()->get(self::CACHE_KEY);
+
+ if (!$key) {
+
+ $cloned = clone $request;
+ $cloned->removeHeader('Cache-Control');
+
+ // Check to see how and if the key should be filtered
+ foreach (explode(';', $request->getParams()->get(self::CACHE_KEY_FILTER)) as $part) {
+ $pieces = array_map('trim', explode('=', $part));
+ if (isset($pieces[1])) {
+ foreach (array_map('trim', explode(',', $pieces[1])) as $remove) {
+ if ($pieces[0] == 'header') {
+ $cloned->removeHeader($remove);
+ } elseif ($pieces[0] == 'query') {
+ $cloned->getQuery()->remove($remove);
+ }
+ }
+ }
+ }
+
+ $raw = (string) $cloned;
+ $key = 'GZ' . md5($raw);
+ $request->getParams()->set(self::CACHE_KEY, $key)->set(self::CACHE_KEY_RAW, $raw);
+ }
+
+ return $key;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php
new file mode 100644
index 0000000..26d7a8b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php
@@ -0,0 +1,266 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Cache\CacheAdapterFactory;
+use Guzzle\Cache\CacheAdapterInterface;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\Message\MessageInterface;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Default cache storage implementation
+ */
+class DefaultCacheStorage implements CacheStorageInterface
+{
+ /** @var string */
+ protected $keyPrefix;
+
+ /** @var CacheAdapterInterface Cache used to store cache data */
+ protected $cache;
+
+ /** @var int Default cache TTL */
+ protected $defaultTtl;
+
+ /**
+ * @param mixed $cache Cache used to store cache data
+ * @param string $keyPrefix Provide an optional key prefix to prefix on all cache keys
+ * @param int $defaultTtl Default cache TTL
+ */
+ public function __construct($cache, $keyPrefix = '', $defaultTtl = 3600)
+ {
+ $this->cache = CacheAdapterFactory::fromCache($cache);
+ $this->defaultTtl = $defaultTtl;
+ $this->keyPrefix = $keyPrefix;
+ }
+
+ public function cache(RequestInterface $request, Response $response)
+ {
+ $currentTime = time();
+
+ $overrideTtl = $request->getParams()->get('cache.override_ttl');
+ if ($overrideTtl) {
+ $ttl = $overrideTtl;
+ } else {
+ $maxAge = $response->getMaxAge();
+ if ($maxAge !== null) {
+ $ttl = $maxAge;
+ } else {
+ $ttl = $this->defaultTtl;
+ }
+ }
+
+ if ($cacheControl = $response->getHeader('Cache-Control')) {
+ $stale = $cacheControl->getDirective('stale-if-error');
+ if ($stale === true) {
+ $ttl += $ttl;
+ } else if (is_numeric($stale)) {
+ $ttl += $stale;
+ }
+ }
+
+ // Determine which manifest key should be used
+ $key = $this->getCacheKey($request);
+ $persistedRequest = $this->persistHeaders($request);
+ $entries = array();
+
+ if ($manifest = $this->cache->fetch($key)) {
+ // Determine which cache entries should still be in the cache
+ $vary = $response->getVary();
+ foreach (unserialize($manifest) as $entry) {
+ // Check if the entry is expired
+ if ($entry[4] < $currentTime) {
+ continue;
+ }
+ $entry[1]['vary'] = isset($entry[1]['vary']) ? $entry[1]['vary'] : '';
+ if ($vary != $entry[1]['vary'] || !$this->requestsMatch($vary, $entry[0], $persistedRequest)) {
+ $entries[] = $entry;
+ }
+ }
+ }
+
+ // Persist the response body if needed
+ $bodyDigest = null;
+ if ($response->getBody() && $response->getBody()->getContentLength() > 0) {
+ $bodyDigest = $this->getBodyKey($request->getUrl(), $response->getBody());
+ $this->cache->save($bodyDigest, (string) $response->getBody(), $ttl);
+ }
+
+ array_unshift($entries, array(
+ $persistedRequest,
+ $this->persistHeaders($response),
+ $response->getStatusCode(),
+ $bodyDigest,
+ $currentTime + $ttl
+ ));
+
+ $this->cache->save($key, serialize($entries));
+ }
+
+ public function delete(RequestInterface $request)
+ {
+ $key = $this->getCacheKey($request);
+ if ($entries = $this->cache->fetch($key)) {
+ // Delete each cached body
+ foreach (unserialize($entries) as $entry) {
+ if ($entry[3]) {
+ $this->cache->delete($entry[3]);
+ }
+ }
+ $this->cache->delete($key);
+ }
+ }
+
+ public function purge($url)
+ {
+ foreach (array('GET', 'HEAD', 'POST', 'PUT', 'DELETE') as $method) {
+ $this->delete(new Request($method, $url));
+ }
+ }
+
+ public function fetch(RequestInterface $request)
+ {
+ $key = $this->getCacheKey($request);
+ if (!($entries = $this->cache->fetch($key))) {
+ return null;
+ }
+
+ $match = null;
+ $headers = $this->persistHeaders($request);
+ $entries = unserialize($entries);
+ foreach ($entries as $index => $entry) {
+ if ($this->requestsMatch(isset($entry[1]['vary']) ? $entry[1]['vary'] : '', $headers, $entry[0])) {
+ $match = $entry;
+ break;
+ }
+ }
+
+ if (!$match) {
+ return null;
+ }
+
+ // Ensure that the response is not expired
+ $response = null;
+ if ($match[4] < time()) {
+ $response = -1;
+ } else {
+ $response = new Response($match[2], $match[1]);
+ if ($match[3]) {
+ if ($body = $this->cache->fetch($match[3])) {
+ $response->setBody($body);
+ } else {
+ // The response is not valid because the body was somehow deleted
+ $response = -1;
+ }
+ }
+ }
+
+ if ($response === -1) {
+ // Remove the entry from the metadata and update the cache
+ unset($entries[$index]);
+ if ($entries) {
+ $this->cache->save($key, serialize($entries));
+ } else {
+ $this->cache->delete($key);
+ }
+ return null;
+ }
+
+ return $response;
+ }
+
+ /**
+ * Hash a request URL into a string that returns cache metadata
+ *
+ * @param RequestInterface $request
+ *
+ * @return string
+ */
+ protected function getCacheKey(RequestInterface $request)
+ {
+ // Allow cache.key_filter to trim down the URL cache key by removing generate query string values (e.g. auth)
+ if ($filter = $request->getParams()->get('cache.key_filter')) {
+ $url = $request->getUrl(true);
+ foreach (explode(',', $filter) as $remove) {
+ $url->getQuery()->remove(trim($remove));
+ }
+ } else {
+ $url = $request->getUrl();
+ }
+
+ return $this->keyPrefix . md5($request->getMethod() . ' ' . $url);
+ }
+
+ /**
+ * Create a cache key for a response's body
+ *
+ * @param string $url URL of the entry
+ * @param EntityBodyInterface $body Response body
+ *
+ * @return string
+ */
+ protected function getBodyKey($url, EntityBodyInterface $body)
+ {
+ return $this->keyPrefix . md5($url) . $body->getContentMd5();
+ }
+
+ /**
+ * Determines whether two Request HTTP header sets are non-varying
+ *
+ * @param string $vary Response vary header
+ * @param array $r1 HTTP header array
+ * @param array $r2 HTTP header array
+ *
+ * @return bool
+ */
+ private function requestsMatch($vary, $r1, $r2)
+ {
+ if ($vary) {
+ foreach (explode(',', $vary) as $header) {
+ $key = trim(strtolower($header));
+ $v1 = isset($r1[$key]) ? $r1[$key] : null;
+ $v2 = isset($r2[$key]) ? $r2[$key] : null;
+ if ($v1 !== $v2) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates an array of cacheable and normalized message headers
+ *
+ * @param MessageInterface $message
+ *
+ * @return array
+ */
+ private function persistHeaders(MessageInterface $message)
+ {
+ // Headers are excluded from the caching (see RFC 2616:13.5.1)
+ static $noCache = array(
+ 'age' => true,
+ 'connection' => true,
+ 'keep-alive' => true,
+ 'proxy-authenticate' => true,
+ 'proxy-authorization' => true,
+ 'te' => true,
+ 'trailers' => true,
+ 'transfer-encoding' => true,
+ 'upgrade' => true,
+ 'set-cookie' => true,
+ 'set-cookie2' => true
+ );
+
+ // Clone the response to not destroy any necessary headers when caching
+ $headers = $message->getHeaders()->getAll();
+ $headers = array_diff_key($headers, $noCache);
+ // Cast the headers to a string
+ $headers = array_map(function ($h) { return (string) $h; }, $headers);
+
+ return $headers;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php
new file mode 100644
index 0000000..3ca1fbf
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Default strategy used to determine of an HTTP request can be cached
+ */
+class DefaultCanCacheStrategy implements CanCacheStrategyInterface
+{
+ public function canCacheRequest(RequestInterface $request)
+ {
+ // Only GET and HEAD requests can be cached
+ if ($request->getMethod() != RequestInterface::GET && $request->getMethod() != RequestInterface::HEAD) {
+ return false;
+ }
+
+ // Never cache requests when using no-store
+ if ($request->hasHeader('Cache-Control') && $request->getHeader('Cache-Control')->hasDirective('no-store')) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function canCacheResponse(Response $response)
+ {
+ return $response->isSuccessful() && $response->canCache();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php
new file mode 100644
index 0000000..af33234
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\BadResponseException;
+
+/**
+ * Default revalidation strategy
+ */
+class DefaultRevalidation implements RevalidationInterface
+{
+ /** @var CacheStorageInterface Cache object storing cache data */
+ protected $storage;
+
+ /** @var CanCacheStrategyInterface */
+ protected $canCache;
+
+ /**
+ * @param CacheStorageInterface $cache Cache storage
+ * @param CanCacheStrategyInterface $canCache Determines if a message can be cached
+ */
+ public function __construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)
+ {
+ $this->storage = $cache;
+ $this->canCache = $canCache ?: new DefaultCanCacheStrategy();
+ }
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ try {
+ $revalidate = $this->createRevalidationRequest($request, $response);
+ $validateResponse = $revalidate->send();
+ if ($validateResponse->getStatusCode() == 200) {
+ return $this->handle200Response($request, $validateResponse);
+ } elseif ($validateResponse->getStatusCode() == 304) {
+ return $this->handle304Response($request, $validateResponse, $response);
+ }
+ } catch (BadResponseException $e) {
+ $this->handleBadResponse($e);
+ }
+
+ // Other exceptions encountered in the revalidation request are ignored
+ // in hopes that sending a request to the origin server will fix it
+ return false;
+ }
+
+ public function shouldRevalidate(RequestInterface $request, Response $response)
+ {
+ if ($request->getMethod() != RequestInterface::GET) {
+ return false;
+ }
+
+ $reqCache = $request->getHeader('Cache-Control');
+ $resCache = $response->getHeader('Cache-Control');
+
+ $revalidate = $request->getHeader('Pragma') == 'no-cache' ||
+ ($reqCache && ($reqCache->hasDirective('no-cache') || $reqCache->hasDirective('must-revalidate'))) ||
+ ($resCache && ($resCache->hasDirective('no-cache') || $resCache->hasDirective('must-revalidate')));
+
+ // Use the strong ETag validator if available and the response contains no Cache-Control directive
+ if (!$revalidate && !$resCache && $response->hasHeader('ETag')) {
+ $revalidate = true;
+ }
+
+ return $revalidate;
+ }
+
+ /**
+ * Handles a bad response when attempting to revalidate
+ *
+ * @param BadResponseException $e Exception encountered
+ *
+ * @throws BadResponseException
+ */
+ protected function handleBadResponse(BadResponseException $e)
+ {
+ // 404 errors mean the resource no longer exists, so remove from
+ // cache, and prevent an additional request by throwing the exception
+ if ($e->getResponse()->getStatusCode() == 404) {
+ $this->storage->delete($e->getRequest());
+ throw $e;
+ }
+ }
+
+ /**
+ * Creates a request to use for revalidation
+ *
+ * @param RequestInterface $request Request
+ * @param Response $response Response to revalidate
+ *
+ * @return RequestInterface returns a revalidation request
+ */
+ protected function createRevalidationRequest(RequestInterface $request, Response $response)
+ {
+ $revalidate = clone $request;
+ $revalidate->removeHeader('Pragma')->removeHeader('Cache-Control');
+
+ if ($response->getLastModified()) {
+ $revalidate->setHeader('If-Modified-Since', $response->getLastModified());
+ }
+
+ if ($response->getEtag()) {
+ $revalidate->setHeader('If-None-Match', $response->getEtag());
+ }
+
+ // Remove any cache plugins that might be on the request to prevent infinite recursive revalidations
+ $dispatcher = $revalidate->getEventDispatcher();
+ foreach ($dispatcher->getListeners() as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ if (is_array($listener) && $listener[0] instanceof CachePlugin) {
+ $dispatcher->removeListener($eventName, $listener);
+ }
+ }
+ }
+
+ return $revalidate;
+ }
+
+ /**
+ * Handles a 200 response response from revalidating. The server does not support validation, so use this response.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param Response $validateResponse Response received
+ *
+ * @return bool Returns true if valid, false if invalid
+ */
+ protected function handle200Response(RequestInterface $request, Response $validateResponse)
+ {
+ $request->setResponse($validateResponse);
+ if ($this->canCache->canCacheResponse($validateResponse)) {
+ $this->storage->cache($request, $validateResponse);
+ }
+
+ return false;
+ }
+
+ /**
+ * Handle a 304 response and ensure that it is still valid
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param Response $validateResponse Response received
+ * @param Response $response Original cached response
+ *
+ * @return bool Returns true if valid, false if invalid
+ */
+ protected function handle304Response(RequestInterface $request, Response $validateResponse, Response $response)
+ {
+ static $replaceHeaders = array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified');
+
+ // Make sure that this response has the same ETag
+ if ($validateResponse->getEtag() != $response->getEtag()) {
+ return false;
+ }
+
+ // Replace cached headers with any of these headers from the
+ // origin server that might be more up to date
+ $modified = false;
+ foreach ($replaceHeaders as $name) {
+ if ($validateResponse->hasHeader($name)) {
+ $modified = true;
+ $response->setHeader($name, $validateResponse->getHeader($name));
+ }
+ }
+
+ // Store the updated response in cache
+ if ($modified && $this->canCache->canCacheResponse($response)) {
+ $this->storage->cache($request, $response);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php
new file mode 100644
index 0000000..88b86f3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Never performs cache revalidation and just assumes the request is invalid
+ */
+class DenyRevalidation extends DefaultRevalidation
+{
+ public function __construct() {}
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php
new file mode 100644
index 0000000..52353d8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Cache revalidation interface
+ */
+interface RevalidationInterface
+{
+ /**
+ * Performs a cache revalidation
+ *
+ * @param RequestInterface $request Request to revalidate
+ * @param Response $response Response that was received
+ *
+ * @return bool Returns true if the request can be cached
+ */
+ public function revalidate(RequestInterface $request, Response $response);
+
+ /**
+ * Returns true if the response should be revalidated
+ *
+ * @param RequestInterface $request Request to check
+ * @param Response $response Response to check
+ *
+ * @return bool
+ */
+ public function shouldRevalidate(RequestInterface $request, Response $response);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php
new file mode 100644
index 0000000..10b5c11
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Never performs cache revalidation and just assumes the request is still ok
+ */
+class SkipRevalidation extends DefaultRevalidation
+{
+ public function __construct() {}
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json
new file mode 100644
index 0000000..674d8c4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "guzzle/plugin-cache",
+ "description": "Guzzle HTTP cache plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version",
+ "guzzle/cache": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Cache": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Cache",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php
new file mode 100644
index 0000000..5218e5f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php
@@ -0,0 +1,538 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie;
+
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * Set-Cookie object
+ */
+class Cookie implements ToArrayInterface
+{
+ /** @var array Cookie data */
+ protected $data;
+
+ /**
+ * @var string ASCII codes not valid for for use in a cookie name
+ *
+ * Cookie names are defined as 'token', according to RFC 2616, Section 2.2
+ * A valid token may contain any CHAR except CTLs (ASCII 0 - 31 or 127)
+ * or any of the following separators
+ */
+ protected static $invalidCharString;
+
+ /**
+ * Gets an array of invalid cookie characters
+ *
+ * @return array
+ */
+ protected static function getInvalidCharacters()
+ {
+ if (!self::$invalidCharString) {
+ self::$invalidCharString = implode('', array_map('chr', array_merge(
+ range(0, 32),
+ array(34, 40, 41, 44, 47),
+ array(58, 59, 60, 61, 62, 63, 64, 91, 92, 93, 123, 125, 127)
+ )));
+ }
+
+ return self::$invalidCharString;
+ }
+
+ /**
+ * @param array $data Array of cookie data provided by a Cookie parser
+ */
+ public function __construct(array $data = array())
+ {
+ static $defaults = array(
+ 'name' => '',
+ 'value' => '',
+ 'domain' => '',
+ 'path' => '/',
+ 'expires' => null,
+ 'max_age' => 0,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'port' => array(),
+ 'version' => null,
+ 'secure' => false,
+ 'discard' => false,
+ 'http_only' => false
+ );
+
+ $this->data = array_merge($defaults, $data);
+ // Extract the expires value and turn it into a UNIX timestamp if needed
+ if (!$this->getExpires() && $this->getMaxAge()) {
+ // Calculate the expires date
+ $this->setExpires(time() + (int) $this->getMaxAge());
+ } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
+ $this->setExpires(strtotime($this->getExpires()));
+ }
+ }
+
+ /**
+ * Get the cookie as an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->data['name'];
+ }
+
+ /**
+ * Set the cookie name
+ *
+ * @param string $name Cookie name
+ *
+ * @return Cookie
+ */
+ public function setName($name)
+ {
+ return $this->setData('name', $name);
+ }
+
+ /**
+ * Get the cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data['value'];
+ }
+
+ /**
+ * Set the cookie value
+ *
+ * @param string $value Cookie value
+ *
+ * @return Cookie
+ */
+ public function setValue($value)
+ {
+ return $this->setData('value', $value);
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->data['domain'];
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ *
+ * @return Cookie
+ */
+ public function setDomain($domain)
+ {
+ return $this->setData('domain', $domain);
+ }
+
+ /**
+ * Get the path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->data['path'];
+ }
+
+ /**
+ * Set the path of the cookie
+ *
+ * @param string $path Path of the cookie
+ *
+ * @return Cookie
+ */
+ public function setPath($path)
+ {
+ return $this->setData('path', $path);
+ }
+
+ /**
+ * Maximum lifetime of the cookie in seconds
+ *
+ * @return int|null
+ */
+ public function getMaxAge()
+ {
+ return $this->data['max_age'];
+ }
+
+ /**
+ * Set the max-age of the cookie
+ *
+ * @param int $maxAge Max age of the cookie in seconds
+ *
+ * @return Cookie
+ */
+ public function setMaxAge($maxAge)
+ {
+ return $this->setData('max_age', $maxAge);
+ }
+
+ /**
+ * The UNIX timestamp when the cookie expires
+ *
+ * @return mixed
+ */
+ public function getExpires()
+ {
+ return $this->data['expires'];
+ }
+
+ /**
+ * Set the unix timestamp for which the cookie will expire
+ *
+ * @param int $timestamp Unix timestamp
+ *
+ * @return Cookie
+ */
+ public function setExpires($timestamp)
+ {
+ return $this->setData('expires', $timestamp);
+ }
+
+ /**
+ * Version of the cookie specification. RFC 2965 is 1
+ *
+ * @return mixed
+ */
+ public function getVersion()
+ {
+ return $this->data['version'];
+ }
+
+ /**
+ * Set the cookie version
+ *
+ * @param string|int $version Version to set
+ *
+ * @return Cookie
+ */
+ public function setVersion($version)
+ {
+ return $this->setData('version', $version);
+ }
+
+ /**
+ * Get whether or not this is a secure cookie
+ *
+ * @return null|bool
+ */
+ public function getSecure()
+ {
+ return $this->data['secure'];
+ }
+
+ /**
+ * Set whether or not the cookie is secure
+ *
+ * @param bool $secure Set to true or false if secure
+ *
+ * @return Cookie
+ */
+ public function setSecure($secure)
+ {
+ return $this->setData('secure', (bool) $secure);
+ }
+
+ /**
+ * Get whether or not this is a session cookie
+ *
+ * @return null|bool
+ */
+ public function getDiscard()
+ {
+ return $this->data['discard'];
+ }
+
+ /**
+ * Set whether or not this is a session cookie
+ *
+ * @param bool $discard Set to true or false if this is a session cookie
+ *
+ * @return Cookie
+ */
+ public function setDiscard($discard)
+ {
+ return $this->setData('discard', $discard);
+ }
+
+ /**
+ * Get the comment
+ *
+ * @return string|null
+ */
+ public function getComment()
+ {
+ return $this->data['comment'];
+ }
+
+ /**
+ * Set the comment of the cookie
+ *
+ * @param string $comment Cookie comment
+ *
+ * @return Cookie
+ */
+ public function setComment($comment)
+ {
+ return $this->setData('comment', $comment);
+ }
+
+ /**
+ * Get the comment URL of the cookie
+ *
+ * @return string|null
+ */
+ public function getCommentUrl()
+ {
+ return $this->data['comment_url'];
+ }
+
+ /**
+ * Set the comment URL of the cookie
+ *
+ * @param string $commentUrl Cookie comment URL for more information
+ *
+ * @return Cookie
+ */
+ public function setCommentUrl($commentUrl)
+ {
+ return $this->setData('comment_url', $commentUrl);
+ }
+
+ /**
+ * Get an array of acceptable ports this cookie can be used with
+ *
+ * @return array
+ */
+ public function getPorts()
+ {
+ return $this->data['port'];
+ }
+
+ /**
+ * Set a list of acceptable ports this cookie can be used with
+ *
+ * @param array $ports Array of acceptable ports
+ *
+ * @return Cookie
+ */
+ public function setPorts(array $ports)
+ {
+ return $this->setData('port', $ports);
+ }
+
+ /**
+ * Get whether or not this is an HTTP only cookie
+ *
+ * @return bool
+ */
+ public function getHttpOnly()
+ {
+ return $this->data['http_only'];
+ }
+
+ /**
+ * Set whether or not this is an HTTP only cookie
+ *
+ * @param bool $httpOnly Set to true or false if this is HTTP only
+ *
+ * @return Cookie
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ return $this->setData('http_only', $httpOnly);
+ }
+
+ /**
+ * Get an array of extra cookie data
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->data['data'];
+ }
+
+ /**
+ * Get a specific data point from the extra cookie data
+ *
+ * @param string $name Name of the data point to retrieve
+ *
+ * @return null|string
+ */
+ public function getAttribute($name)
+ {
+ return array_key_exists($name, $this->data['data']) ? $this->data['data'][$name] : null;
+ }
+
+ /**
+ * Set a cookie data attribute
+ *
+ * @param string $name Name of the attribute to set
+ * @param string $value Value to set
+ *
+ * @return Cookie
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->data['data'][$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Check if the cookie matches a path value
+ *
+ * @param string $path Path to check against
+ *
+ * @return bool
+ */
+ public function matchesPath($path)
+ {
+ // RFC6265 http://tools.ietf.org/search/rfc6265#section-5.1.4
+ // A request-path path-matches a given cookie-path if at least one of
+ // the following conditions holds:
+
+ // o The cookie-path and the request-path are identical.
+ if ($path == $this->getPath()) {
+ return true;
+ }
+
+ $pos = stripos($path, $this->getPath());
+ if ($pos === 0) {
+ // o The cookie-path is a prefix of the request-path, and the last
+ // character of the cookie-path is %x2F ("/").
+ if (substr($this->getPath(), -1, 1) === "/") {
+ return true;
+ }
+
+ // o The cookie-path is a prefix of the request-path, and the first
+ // character of the request-path that is not included in the cookie-
+ // path is a %x2F ("/") character.
+ if (substr($path, strlen($this->getPath()), 1) === "/") {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the cookie matches a domain value
+ *
+ * @param string $domain Domain to check against
+ *
+ * @return bool
+ */
+ public function matchesDomain($domain)
+ {
+ // Remove the leading '.' as per spec in RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.2.3
+ $cookieDomain = ltrim($this->getDomain(), '.');
+
+ // Domain not set or exact match.
+ if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
+ return true;
+ }
+
+ // Matching the subdomain according to RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.1.3
+ if (filter_var($domain, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/i', $domain);
+ }
+
+ /**
+ * Check if the cookie is compatible with a specific port
+ *
+ * @param int $port Port to check
+ *
+ * @return bool
+ */
+ public function matchesPort($port)
+ {
+ return count($this->getPorts()) == 0 || in_array($port, $this->getPorts());
+ }
+
+ /**
+ * Check if the cookie is expired
+ *
+ * @return bool
+ */
+ public function isExpired()
+ {
+ return $this->getExpires() && time() > $this->getExpires();
+ }
+
+ /**
+ * Check if the cookie is valid according to RFC 6265
+ *
+ * @return bool|string Returns true if valid or an error message if invalid
+ */
+ public function validate()
+ {
+ // Names must not be empty, but can be 0
+ $name = $this->getName();
+ if (empty($name) && !is_numeric($name)) {
+ return 'The cookie name must not be empty';
+ }
+
+ // Check if any of the invalid characters are present in the cookie name
+ if (strpbrk($name, self::getInvalidCharacters()) !== false) {
+ return 'The cookie name must not contain invalid characters: ' . $name;
+ }
+
+ // Value must not be empty, but can be 0
+ $value = $this->getValue();
+ if (empty($value) && !is_numeric($value)) {
+ return 'The cookie value must not be empty';
+ }
+
+ // Domains must not be empty, but can be 0
+ // A "0" is not a valid internet domain, but may be used as server name in a private network
+ $domain = $this->getDomain();
+ if (empty($domain) && !is_numeric($domain)) {
+ return 'The cookie domain must not be empty';
+ }
+
+ return true;
+ }
+
+ /**
+ * Set a value and return the cookie object
+ *
+ * @param string $key Key to set
+ * @param string $value Value to set
+ *
+ * @return Cookie
+ */
+ private function setData($key, $value)
+ {
+ $this->data[$key] = $value;
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php
new file mode 100644
index 0000000..6b67503
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php
@@ -0,0 +1,237 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie\CookieJar;
+
+use Guzzle\Plugin\Cookie\Cookie;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Parser\ParserRegistry;
+use Guzzle\Plugin\Cookie\Exception\InvalidCookieException;
+
+/**
+ * Cookie cookieJar that stores cookies an an array
+ */
+class ArrayCookieJar implements CookieJarInterface, \Serializable
+{
+ /** @var array Loaded cookie data */
+ protected $cookies = array();
+
+ /** @var bool Whether or not strict mode is enabled. When enabled, exceptions will be thrown for invalid cookies */
+ protected $strictMode;
+
+ /**
+ * @param bool $strictMode Set to true to throw exceptions when invalid cookies are added to the cookie jar
+ */
+ public function __construct($strictMode = false)
+ {
+ $this->strictMode = $strictMode;
+ }
+
+ /**
+ * Enable or disable strict mode on the cookie jar
+ *
+ * @param bool $strictMode Set to true to throw exceptions when invalid cookies are added. False to ignore them.
+ *
+ * @return self
+ */
+ public function setStrictMode($strictMode)
+ {
+ $this->strictMode = $strictMode;
+ }
+
+ public function remove($domain = null, $path = null, $name = null)
+ {
+ $cookies = $this->all($domain, $path, $name, false, false);
+ $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($cookies) {
+ return !in_array($cookie, $cookies, true);
+ });
+
+ return $this;
+ }
+
+ public function removeTemporary()
+ {
+ $this->cookies = array_filter($this->cookies, function (Cookie $cookie) {
+ return !$cookie->getDiscard() && $cookie->getExpires();
+ });
+
+ return $this;
+ }
+
+ public function removeExpired()
+ {
+ $currentTime = time();
+ $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($currentTime) {
+ return !$cookie->getExpires() || $currentTime < $cookie->getExpires();
+ });
+
+ return $this;
+ }
+
+ public function all($domain = null, $path = null, $name = null, $skipDiscardable = false, $skipExpired = true)
+ {
+ return array_values(array_filter($this->cookies, function (Cookie $cookie) use (
+ $domain,
+ $path,
+ $name,
+ $skipDiscardable,
+ $skipExpired
+ ) {
+ return false === (($name && $cookie->getName() != $name) ||
+ ($skipExpired && $cookie->isExpired()) ||
+ ($skipDiscardable && ($cookie->getDiscard() || !$cookie->getExpires())) ||
+ ($path && !$cookie->matchesPath($path)) ||
+ ($domain && !$cookie->matchesDomain($domain)));
+ }));
+ }
+
+ public function add(Cookie $cookie)
+ {
+ // Only allow cookies with set and valid domain, name, value
+ $result = $cookie->validate();
+ if ($result !== true) {
+ if ($this->strictMode) {
+ throw new InvalidCookieException($result);
+ } else {
+ $this->removeCookieIfEmpty($cookie);
+ return false;
+ }
+ }
+
+ // Resolve conflicts with previously set cookies
+ foreach ($this->cookies as $i => $c) {
+
+ // Two cookies are identical, when their path, domain, port and name are identical
+ if ($c->getPath() != $cookie->getPath() ||
+ $c->getDomain() != $cookie->getDomain() ||
+ $c->getPorts() != $cookie->getPorts() ||
+ $c->getName() != $cookie->getName()
+ ) {
+ continue;
+ }
+
+ // The previously set cookie is a discard cookie and this one is not so allow the new cookie to be set
+ if (!$cookie->getDiscard() && $c->getDiscard()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the new cookie's expiration is further into the future, then replace the old cookie
+ if ($cookie->getExpires() > $c->getExpires()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the value has changed, we better change it
+ if ($cookie->getValue() !== $c->getValue()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // The cookie exists, so no need to continue
+ return false;
+ }
+
+ $this->cookies[] = $cookie;
+
+ return true;
+ }
+
+ /**
+ * Serializes the cookie cookieJar
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ // Only serialize long term cookies and unexpired cookies
+ return json_encode(array_map(function (Cookie $cookie) {
+ return $cookie->toArray();
+ }, $this->all(null, null, null, true, true)));
+ }
+
+ /**
+ * Unserializes the cookie cookieJar
+ */
+ public function unserialize($data)
+ {
+ $data = json_decode($data, true);
+ if (empty($data)) {
+ $this->cookies = array();
+ } else {
+ $this->cookies = array_map(function (array $cookie) {
+ return new Cookie($cookie);
+ }, $data);
+ }
+ }
+
+ /**
+ * Returns the total number of stored cookies
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->cookies);
+ }
+
+ /**
+ * Returns an iterator
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->cookies);
+ }
+
+ public function addCookiesFromResponse(Response $response, RequestInterface $request = null)
+ {
+ if ($cookieHeader = $response->getHeader('Set-Cookie')) {
+ $parser = ParserRegistry::getInstance()->getParser('cookie');
+ foreach ($cookieHeader as $cookie) {
+ if ($parsed = $request
+ ? $parser->parseCookie($cookie, $request->getHost(), $request->getPath())
+ : $parser->parseCookie($cookie)
+ ) {
+ // Break up cookie v2 into multiple cookies
+ foreach ($parsed['cookies'] as $key => $value) {
+ $row = $parsed;
+ $row['name'] = $key;
+ $row['value'] = $value;
+ unset($row['cookies']);
+ $this->add(new Cookie($row));
+ }
+ }
+ }
+ }
+ }
+
+ public function getMatchingCookies(RequestInterface $request)
+ {
+ // Find cookies that match this request
+ $cookies = $this->all($request->getHost(), $request->getPath());
+ // Remove ineligible cookies
+ foreach ($cookies as $index => $cookie) {
+ if (!$cookie->matchesPort($request->getPort()) || ($cookie->getSecure() && $request->getScheme() != 'https')) {
+ unset($cookies[$index]);
+ }
+ };
+
+ return $cookies;
+ }
+
+ /**
+ * If a cookie already exists and the server asks to set it again with a null value, the
+ * cookie must be deleted.
+ *
+ * @param \Guzzle\Plugin\Cookie\Cookie $cookie
+ */
+ private function removeCookieIfEmpty(Cookie $cookie)
+ {
+ $cookieValue = $cookie->getValue();
+ if ($cookieValue === null || $cookieValue === '') {
+ $this->remove($cookie->getDomain(), $cookie->getPath(), $cookie->getName());
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php
new file mode 100644
index 0000000..7faa7d2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie\CookieJar;
+
+use Guzzle\Plugin\Cookie\Cookie;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Interface for persisting cookies
+ */
+interface CookieJarInterface extends \Countable, \IteratorAggregate
+{
+ /**
+ * Remove cookies currently held in the Cookie cookieJar.
+ *
+ * Invoking this method without arguments will empty the whole Cookie cookieJar. If given a $domain argument only
+ * cookies belonging to that domain will be removed. If given a $domain and $path argument, cookies belonging to
+ * the specified path within that domain are removed. If given all three arguments, then the cookie with the
+ * specified name, path and domain is removed.
+ *
+ * @param string $domain Set to clear only cookies matching a domain
+ * @param string $path Set to clear only cookies matching a domain and path
+ * @param string $name Set to clear only cookies matching a domain, path, and name
+ *
+ * @return CookieJarInterface
+ */
+ public function remove($domain = null, $path = null, $name = null);
+
+ /**
+ * Discard all temporary cookies.
+ *
+ * Scans for all cookies in the cookieJar with either no expire field or a true discard flag. To be called when the
+ * user agent shuts down according to RFC 2965.
+ *
+ * @return CookieJarInterface
+ */
+ public function removeTemporary();
+
+ /**
+ * Delete any expired cookies
+ *
+ * @return CookieJarInterface
+ */
+ public function removeExpired();
+
+ /**
+ * Add a cookie to the cookie cookieJar
+ *
+ * @param Cookie $cookie Cookie to add
+ *
+ * @return bool Returns true on success or false on failure
+ */
+ public function add(Cookie $cookie);
+
+ /**
+ * Add cookies from a {@see Guzzle\Http\Message\Response} object
+ *
+ * @param Response $response Response object
+ * @param RequestInterface $request Request that received the response
+ */
+ public function addCookiesFromResponse(Response $response, RequestInterface $request = null);
+
+ /**
+ * Get cookies matching a request object
+ *
+ * @param RequestInterface $request Request object to match
+ *
+ * @return array
+ */
+ public function getMatchingCookies(RequestInterface $request);
+
+ /**
+ * Get all of the matching cookies
+ *
+ * @param string $domain Domain of the cookie
+ * @param string $path Path of the cookie
+ * @param string $name Name of the cookie
+ * @param bool $skipDiscardable Set to TRUE to skip cookies with the Discard attribute.
+ * @param bool $skipExpired Set to FALSE to include expired
+ *
+ * @return array Returns an array of Cookie objects
+ */
+ public function all($domain = null, $path = null, $name = null, $skipDiscardable = false, $skipExpired = true);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/FileCookieJar.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/FileCookieJar.php
new file mode 100644
index 0000000..99344cd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/FileCookieJar.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie\CookieJar;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Persists non-session cookies using a JSON formatted file
+ */
+class FileCookieJar extends ArrayCookieJar
+{
+ /** @var string filename */
+ protected $filename;
+
+ /**
+ * Create a new FileCookieJar object
+ *
+ * @param string $cookieFile File to store the cookie data
+ *
+ * @throws RuntimeException if the file cannot be found or created
+ */
+ public function __construct($cookieFile)
+ {
+ $this->filename = $cookieFile;
+ $this->load();
+ }
+
+ /**
+ * Saves the file when shutting down
+ */
+ public function __destruct()
+ {
+ $this->persist();
+ }
+
+ /**
+ * Save the contents of the data array to the file
+ *
+ * @throws RuntimeException if the file cannot be found or created
+ */
+ protected function persist()
+ {
+ if (false === file_put_contents($this->filename, $this->serialize())) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Unable to open file ' . $this->filename);
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ /**
+ * Load the contents of the json formatted file into the data array and discard any unsaved state
+ */
+ protected function load()
+ {
+ $json = file_get_contents($this->filename);
+ if (false === $json) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Unable to open file ' . $this->filename);
+ // @codeCoverageIgnoreEnd
+ }
+
+ $this->unserialize($json);
+ $this->cookies = $this->cookies ?: array();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php
new file mode 100644
index 0000000..df3210e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie;
+
+use Guzzle\Common\Event;
+use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;
+use Guzzle\Plugin\Cookie\CookieJar\CookieJarInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Adds, extracts, and persists cookies between HTTP requests
+ */
+class CookiePlugin implements EventSubscriberInterface
+{
+ /** @var CookieJarInterface Cookie cookieJar used to hold cookies */
+ protected $cookieJar;
+
+ /**
+ * @param CookieJarInterface $cookieJar Cookie jar used to hold cookies. Creates an ArrayCookieJar by default.
+ */
+ public function __construct(CookieJarInterface $cookieJar = null)
+ {
+ $this->cookieJar = $cookieJar ?: new ArrayCookieJar();
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', 125),
+ 'request.sent' => array('onRequestSent', 125)
+ );
+ }
+
+ /**
+ * Get the cookie cookieJar
+ *
+ * @return CookieJarInterface
+ */
+ public function getCookieJar()
+ {
+ return $this->cookieJar;
+ }
+
+ /**
+ * Add cookies before a request is sent
+ *
+ * @param Event $event
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ $request = $event['request'];
+ if (!$request->getParams()->get('cookies.disable')) {
+ $request->removeHeader('Cookie');
+ // Find cookies that match this request
+ foreach ($this->cookieJar->getMatchingCookies($request) as $cookie) {
+ $request->addCookie($cookie->getName(), $cookie->getValue());
+ }
+ }
+ }
+
+ /**
+ * Extract cookies from a sent request
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ $this->cookieJar->addCookiesFromResponse($event['response'], $event['request']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php
new file mode 100644
index 0000000..b1fa6fd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Plugin\Cookie\Exception;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+class InvalidCookieException extends InvalidArgumentException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/composer.json
new file mode 100644
index 0000000..f0b5230
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-cookie",
+ "description": "Guzzle cookie plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Cookie": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Cookie",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php
new file mode 100644
index 0000000..610e60c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Plugin\CurlAuth;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\Version;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Adds specified curl auth to all requests sent from a client. Defaults to CURLAUTH_BASIC if none supplied.
+ * @deprecated Use $client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');
+ */
+class CurlAuthPlugin implements EventSubscriberInterface
+{
+ private $username;
+ private $password;
+ private $scheme;
+
+ /**
+ * @param string $username HTTP basic auth username
+ * @param string $password Password
+ * @param int $scheme Curl auth scheme
+ */
+ public function __construct($username, $password, $scheme=CURLAUTH_BASIC)
+ {
+ Version::warn(__CLASS__ . " is deprecated. Use \$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');");
+ $this->username = $username;
+ $this->password = $password;
+ $this->scheme = $scheme;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array('client.create_request' => array('onRequestCreate', 255));
+ }
+
+ /**
+ * Add basic auth
+ *
+ * @param Event $event
+ */
+ public function onRequestCreate(Event $event)
+ {
+ $event['request']->setAuth($this->username, $this->password, $this->scheme);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json
new file mode 100644
index 0000000..edc8b24
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-curlauth",
+ "description": "Guzzle cURL authorization plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "curl", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\CurlAuth": "" }
+ },
+ "target-dir": "Guzzle/Plugin/CurlAuth",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php
new file mode 100644
index 0000000..5dce8bd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Guzzle\Plugin\ErrorResponse;
+
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Interface used to create an exception from an error response
+ */
+interface ErrorResponseExceptionInterface
+{
+ /**
+ * Create an exception for a command based on a command and an error response definition
+ *
+ * @param CommandInterface $command Command that was sent
+ * @param Response $response The error response
+ *
+ * @return self
+ */
+ public static function fromCommand(CommandInterface $command, Response $response);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponsePlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponsePlugin.php
new file mode 100644
index 0000000..588b9c3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponsePlugin.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Guzzle\Plugin\ErrorResponse;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Converts generic Guzzle response exceptions into errorResponse exceptions
+ */
+class ErrorResponsePlugin implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('command.before_send' => array('onCommandBeforeSend', -1));
+ }
+
+ /**
+ * Adds a listener to requests before they sent from a command
+ *
+ * @param Event $event Event emitted
+ */
+ public function onCommandBeforeSend(Event $event)
+ {
+ $command = $event['command'];
+ if ($operation = $command->getOperation()) {
+ if ($operation->getErrorResponses()) {
+ $request = $command->getRequest();
+ $request->getEventDispatcher()
+ ->addListener('request.complete', $this->getErrorClosure($request, $command, $operation));
+ }
+ }
+ }
+
+ /**
+ * @param RequestInterface $request Request that received an error
+ * @param CommandInterface $command Command that created the request
+ * @param Operation $operation Operation that defines the request and errors
+ *
+ * @return \Closure Returns a closure
+ * @throws ErrorResponseException
+ */
+ protected function getErrorClosure(RequestInterface $request, CommandInterface $command, Operation $operation)
+ {
+ return function (Event $event) use ($request, $command, $operation) {
+ $response = $event['response'];
+ foreach ($operation->getErrorResponses() as $error) {
+ if (!isset($error['class'])) {
+ continue;
+ }
+ if (isset($error['code']) && $response->getStatusCode() != $error['code']) {
+ continue;
+ }
+ if (isset($error['reason']) && $response->getReasonPhrase() != $error['reason']) {
+ continue;
+ }
+ $className = $error['class'];
+ $errorClassInterface = __NAMESPACE__ . '\\ErrorResponseExceptionInterface';
+ if (!class_exists($className)) {
+ throw new ErrorResponseException("{$className} does not exist");
+ } elseif (!(in_array($errorClassInterface, class_implements($className)))) {
+ throw new ErrorResponseException("{$className} must implement {$errorClassInterface}");
+ }
+ throw $className::fromCommand($command, $response);
+ }
+ };
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php
new file mode 100644
index 0000000..1d89e40
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Plugin\ErrorResponse\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class ErrorResponseException extends RuntimeException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/composer.json
new file mode 100644
index 0000000..607e861
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-error-response",
+ "description": "Guzzle errorResponse plugin for creating error exceptions based on a service description",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/service": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\ErrorResponse": "" }
+ },
+ "target-dir": "Guzzle/Plugin/ErrorResponse",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php
new file mode 100644
index 0000000..7375e89
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Guzzle\Plugin\History;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Maintains a list of requests and responses sent using a request or client
+ */
+class HistoryPlugin implements EventSubscriberInterface, \IteratorAggregate, \Countable
+{
+ /** @var int The maximum number of requests to maintain in the history */
+ protected $limit = 10;
+
+ /** @var array Requests and responses that have passed through the plugin */
+ protected $transactions = array();
+
+ public static function getSubscribedEvents()
+ {
+ return array('request.sent' => array('onRequestSent', 9999));
+ }
+
+ /**
+ * Convert to a string that contains all request and response headers
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $lines = array();
+ foreach ($this->transactions as $entry) {
+ $response = isset($entry['response']) ? $entry['response'] : '';
+ $lines[] = '> ' . trim($entry['request']) . "\n\n< " . trim($response) . "\n";
+ }
+
+ return implode("\n", $lines);
+ }
+
+ /**
+ * Add a request to the history
+ *
+ * @param RequestInterface $request Request to add
+ * @param Response $response Response of the request
+ *
+ * @return HistoryPlugin
+ */
+ public function add(RequestInterface $request, Response $response = null)
+ {
+ if (!$response && $request->getResponse()) {
+ $response = $request->getResponse();
+ }
+
+ $this->transactions[] = array('request' => $request, 'response' => $response);
+ if (count($this->transactions) > $this->getlimit()) {
+ array_shift($this->transactions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the max number of requests to store
+ *
+ * @param int $limit Limit
+ *
+ * @return HistoryPlugin
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = (int) $limit;
+
+ return $this;
+ }
+
+ /**
+ * Get the request limit
+ *
+ * @return int
+ */
+ public function getLimit()
+ {
+ return $this->limit;
+ }
+
+ /**
+ * Get all of the raw transactions in the form of an array of associative arrays containing
+ * 'request' and 'response' keys.
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->transactions;
+ }
+
+ /**
+ * Get the requests in the history
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ // Return an iterator just like the old iteration of the HistoryPlugin for BC compatibility (use getAll())
+ return new \ArrayIterator(array_map(function ($entry) {
+ $entry['request']->getParams()->set('actual_response', $entry['response']);
+ return $entry['request'];
+ }, $this->transactions));
+ }
+
+ /**
+ * Get the number of requests in the history
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->transactions);
+ }
+
+ /**
+ * Get the last request sent
+ *
+ * @return RequestInterface
+ */
+ public function getLastRequest()
+ {
+ $last = end($this->transactions);
+
+ return $last['request'];
+ }
+
+ /**
+ * Get the last response in the history
+ *
+ * @return Response|null
+ */
+ public function getLastResponse()
+ {
+ $last = end($this->transactions);
+
+ return isset($last['response']) ? $last['response'] : null;
+ }
+
+ /**
+ * Clears the history
+ *
+ * @return HistoryPlugin
+ */
+ public function clear()
+ {
+ $this->transactions = array();
+
+ return $this;
+ }
+
+ public function onRequestSent(Event $event)
+ {
+ $this->add($event['request'], $event['response']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json
new file mode 100644
index 0000000..ba0bf2c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-history",
+ "description": "Guzzle history plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\History": "" }
+ },
+ "target-dir": "Guzzle/Plugin/History",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php
new file mode 100644
index 0000000..cabdea8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace Guzzle\Plugin\Log;
+
+use Guzzle\Common\Event;
+use Guzzle\Log\LogAdapterInterface;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Plugin class that will add request and response logging to an HTTP request.
+ *
+ * The log plugin uses a message formatter that allows custom messages via template variable substitution.
+ *
+ * @see MessageLogger for a list of available log template variable substitutions
+ */
+class LogPlugin implements EventSubscriberInterface
+{
+ /** @var LogAdapterInterface Adapter responsible for writing log data */
+ protected $logAdapter;
+
+ /** @var MessageFormatter Formatter used to format messages before logging */
+ protected $formatter;
+
+ /** @var bool Whether or not to wire request and response bodies */
+ protected $wireBodies;
+
+ /**
+ * @param LogAdapterInterface $logAdapter Adapter object used to log message
+ * @param string|MessageFormatter $formatter Formatter used to format log messages or the formatter template
+ * @param bool $wireBodies Set to true to track request and response bodies using a temporary
+ * buffer if the bodies are not repeatable.
+ */
+ public function __construct(
+ LogAdapterInterface $logAdapter,
+ $formatter = null,
+ $wireBodies = false
+ ) {
+ $this->logAdapter = $logAdapter;
+ $this->formatter = $formatter instanceof MessageFormatter ? $formatter : new MessageFormatter($formatter);
+ $this->wireBodies = $wireBodies;
+ }
+
+ /**
+ * Get a log plugin that outputs full request, response, and curl error information to stderr
+ *
+ * @param bool $wireBodies Set to false to disable request/response body output when they use are not repeatable
+ * @param resource $stream Stream to write to when logging. Defaults to STDERR when it is available
+ *
+ * @return self
+ */
+ public static function getDebugPlugin($wireBodies = true, $stream = null)
+ {
+ if ($stream === null) {
+ if (defined('STDERR')) {
+ $stream = STDERR;
+ } else {
+ $stream = fopen('php://output', 'w');
+ }
+ }
+
+ return new self(new ClosureLogAdapter(function ($m) use ($stream) {
+ fwrite($stream, $m . PHP_EOL);
+ }), "# Request:\n{request}\n\n# Response:\n{response}\n\n# Errors: {curl_code} {curl_error}", $wireBodies);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'curl.callback.write' => array('onCurlWrite', 255),
+ 'curl.callback.read' => array('onCurlRead', 255),
+ 'request.before_send' => array('onRequestBeforeSend', 255),
+ 'request.sent' => array('onRequestSent', 255)
+ );
+ }
+
+ /**
+ * Event triggered when curl data is read from a request
+ *
+ * @param Event $event
+ */
+ public function onCurlRead(Event $event)
+ {
+ // Stream the request body to the log if the body is not repeatable
+ if ($wire = $event['request']->getParams()->get('request_wire')) {
+ $wire->write($event['read']);
+ }
+ }
+
+ /**
+ * Event triggered when curl data is written to a response
+ *
+ * @param Event $event
+ */
+ public function onCurlWrite(Event $event)
+ {
+ // Stream the response body to the log if the body is not repeatable
+ if ($wire = $event['request']->getParams()->get('response_wire')) {
+ $wire->write($event['write']);
+ }
+ }
+
+ /**
+ * Called before a request is sent
+ *
+ * @param Event $event
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ if ($this->wireBodies) {
+ $request = $event['request'];
+ // Ensure that curl IO events are emitted
+ $request->getCurlOptions()->set('emit_io', true);
+ // We need to make special handling for content wiring and non-repeatable streams.
+ if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()
+ && (!$request->getBody()->isSeekable() || !$request->getBody()->isReadable())
+ ) {
+ // The body of the request cannot be recalled so logging the body will require us to buffer it
+ $request->getParams()->set('request_wire', EntityBody::factory());
+ }
+ if (!$request->getResponseBody()->isRepeatable()) {
+ // The body of the response cannot be recalled so logging the body will require us to buffer it
+ $request->getParams()->set('response_wire', EntityBody::factory());
+ }
+ }
+ }
+
+ /**
+ * Triggers the actual log write when a request completes
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ $request = $event['request'];
+ $response = $event['response'];
+ $handle = $event['handle'];
+
+ if ($wire = $request->getParams()->get('request_wire')) {
+ $request = clone $request;
+ $request->setBody($wire);
+ }
+
+ if ($wire = $request->getParams()->get('response_wire')) {
+ $response = clone $response;
+ $response->setBody($wire);
+ }
+
+ // Send the log message to the adapter, adding a category and host
+ $priority = $response && $response->isError() ? LOG_ERR : LOG_DEBUG;
+ $message = $this->formatter->format($request, $response, $handle);
+ $this->logAdapter->log($message, $priority, array(
+ 'request' => $request,
+ 'response' => $response,
+ 'handle' => $handle
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json
new file mode 100644
index 0000000..130e6da
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "guzzle/plugin-log",
+ "description": "Guzzle log plugin for over the wire logging",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "log", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version",
+ "guzzle/log": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Log": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Log",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php
new file mode 100644
index 0000000..8512424
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Guzzle\Plugin\Md5;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Listener used to add a ContentMD5 header to the body of a command and adds ContentMD5 validation if the
+ * ValidateMD5 option is not set to false on a command
+ */
+class CommandContentMd5Plugin implements EventSubscriberInterface
+{
+ /** @var string Parameter used to check if the ContentMD5 value is being added */
+ protected $contentMd5Param;
+
+ /** @var string Parameter used to check if validation should occur on the response */
+ protected $validateMd5Param;
+
+ /**
+ * @param string $contentMd5Param Parameter used to check if the ContentMD5 value is being added
+ * @param string $validateMd5Param Parameter used to check if validation should occur on the response
+ */
+ public function __construct($contentMd5Param = 'ContentMD5', $validateMd5Param = 'ValidateMD5')
+ {
+ $this->contentMd5Param = $contentMd5Param;
+ $this->validateMd5Param = $validateMd5Param;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array('command.before_send' => array('onCommandBeforeSend', -255));
+ }
+
+ public function onCommandBeforeSend(Event $event)
+ {
+ $command = $event['command'];
+ $request = $command->getRequest();
+
+ // Only add an MD5 is there is a MD5 option on the operation and it has a payload
+ if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()
+ && $command->getOperation()->hasParam($this->contentMd5Param)) {
+ // Check if an MD5 checksum value should be passed along to the request
+ if ($command[$this->contentMd5Param] === true) {
+ if (false !== ($md5 = $request->getBody()->getContentMd5(true, true))) {
+ $request->setHeader('Content-MD5', $md5);
+ }
+ }
+ }
+
+ // Check if MD5 validation should be used with the response
+ if ($command[$this->validateMd5Param] === true) {
+ $request->addSubscriber(new Md5ValidatorPlugin(true, false));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php
new file mode 100644
index 0000000..5d7a378
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Guzzle\Plugin\Md5;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\Exception\UnexpectedValueException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Ensures that an the MD5 hash of an entity body matches the Content-MD5
+ * header (if set) of an HTTP response. An exception is thrown if the
+ * calculated MD5 does not match the expected MD5.
+ */
+class Md5ValidatorPlugin implements EventSubscriberInterface
+{
+ /** @var int Maximum Content-Length in bytes to validate */
+ protected $contentLengthCutoff;
+
+ /** @var bool Whether or not to compare when a Content-Encoding is present */
+ protected $contentEncoded;
+
+ /**
+ * @param bool $contentEncoded Calculating the MD5 hash of an entity body where a Content-Encoding was
+ * applied is a more expensive comparison because the entity body will need to
+ * be compressed in order to get the correct hash. Set to FALSE to not
+ * validate the MD5 hash of an entity body with an applied Content-Encoding.
+ * @param bool|int $contentLengthCutoff Maximum Content-Length (bytes) in which a MD5 hash will be validated. Any
+ * response with a Content-Length greater than this value will not be validated
+ * because it will be deemed too memory intensive.
+ */
+ public function __construct($contentEncoded = true, $contentLengthCutoff = false)
+ {
+ $this->contentLengthCutoff = $contentLengthCutoff;
+ $this->contentEncoded = $contentEncoded;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array('request.complete' => array('onRequestComplete', 255));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws UnexpectedValueException
+ */
+ public function onRequestComplete(Event $event)
+ {
+ $response = $event['response'];
+
+ if (!$contentMd5 = $response->getContentMd5()) {
+ return;
+ }
+
+ $contentEncoding = $response->getContentEncoding();
+ if ($contentEncoding && !$this->contentEncoded) {
+ return false;
+ }
+
+ // Make sure that the size of the request is under the cutoff size
+ if ($this->contentLengthCutoff) {
+ $size = $response->getContentLength() ?: $response->getBody()->getSize();
+ if (!$size || $size > $this->contentLengthCutoff) {
+ return;
+ }
+ }
+
+ if (!$contentEncoding) {
+ $hash = $response->getBody()->getContentMd5();
+ } elseif ($contentEncoding == 'gzip') {
+ $response->getBody()->compress('zlib.deflate');
+ $hash = $response->getBody()->getContentMd5();
+ $response->getBody()->uncompress();
+ } elseif ($contentEncoding == 'compress') {
+ $response->getBody()->compress('bzip2.compress');
+ $hash = $response->getBody()->getContentMd5();
+ $response->getBody()->uncompress();
+ } else {
+ return;
+ }
+
+ if ($contentMd5 !== $hash) {
+ throw new UnexpectedValueException(
+ "The response entity body may have been modified over the wire. The Content-MD5 "
+ . "received ({$contentMd5}) did not match the calculated MD5 hash ({$hash})."
+ );
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json
new file mode 100644
index 0000000..0602d06
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-md5",
+ "description": "Guzzle MD5 plugins",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Md5": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Md5",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php
new file mode 100644
index 0000000..2440578
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php
@@ -0,0 +1,245 @@
+<?php
+
+namespace Guzzle\Plugin\Mock;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\Response;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Queues mock responses or exceptions and delivers mock responses or exceptions in a fifo order.
+ */
+class MockPlugin extends AbstractHasDispatcher implements EventSubscriberInterface, \Countable
+{
+ /** @var array Array of mock responses / exceptions */
+ protected $queue = array();
+
+ /** @var bool Whether or not to remove the plugin when the queue is empty */
+ protected $temporary = false;
+
+ /** @var array Array of requests that were mocked */
+ protected $received = array();
+
+ /** @var bool Whether or not to consume an entity body when a mock response is served */
+ protected $readBodies;
+
+ /**
+ * @param array $items Array of responses or exceptions to queue
+ * @param bool $temporary Set to TRUE to remove the plugin when the queue is empty
+ * @param bool $readBodies Set to TRUE to consume the entity body when a mock is served
+ */
+ public function __construct(array $items = null, $temporary = false, $readBodies = false)
+ {
+ $this->readBodies = $readBodies;
+ $this->temporary = $temporary;
+ if ($items) {
+ foreach ($items as $item) {
+ if ($item instanceof \Exception) {
+ $this->addException($item);
+ } else {
+ $this->addResponse($item);
+ }
+ }
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ // Use a number lower than the CachePlugin
+ return array('request.before_send' => array('onRequestBeforeSend', -999));
+ }
+
+ public static function getAllEvents()
+ {
+ return array('mock.request');
+ }
+
+ /**
+ * Get a mock response from a file
+ *
+ * @param string $path File to retrieve a mock response from
+ *
+ * @return Response
+ * @throws InvalidArgumentException if the file is not found
+ */
+ public static function getMockFile($path)
+ {
+ if (!file_exists($path)) {
+ throw new InvalidArgumentException('Unable to open mock file: ' . $path);
+ }
+
+ return Response::fromMessage(file_get_contents($path));
+ }
+
+ /**
+ * Set whether or not to consume the entity body of a request when a mock
+ * response is used
+ *
+ * @param bool $readBodies Set to true to read and consume entity bodies
+ *
+ * @return self
+ */
+ public function readBodies($readBodies)
+ {
+ $this->readBodies = $readBodies;
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of remaining mock responses
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->queue);
+ }
+
+ /**
+ * Add a response to the end of the queue
+ *
+ * @param string|Response $response Response object or path to response file
+ *
+ * @return MockPlugin
+ * @throws InvalidArgumentException if a string or Response is not passed
+ */
+ public function addResponse($response)
+ {
+ if (!($response instanceof Response)) {
+ if (!is_string($response)) {
+ throw new InvalidArgumentException('Invalid response');
+ }
+ $response = self::getMockFile($response);
+ }
+
+ $this->queue[] = $response;
+
+ return $this;
+ }
+
+ /**
+ * Add an exception to the end of the queue
+ *
+ * @param CurlException $e Exception to throw when the request is executed
+ *
+ * @return MockPlugin
+ */
+ public function addException(CurlException $e)
+ {
+ $this->queue[] = $e;
+
+ return $this;
+ }
+
+ /**
+ * Clear the queue
+ *
+ * @return MockPlugin
+ */
+ public function clearQueue()
+ {
+ $this->queue = array();
+
+ return $this;
+ }
+
+ /**
+ * Returns an array of mock responses remaining in the queue
+ *
+ * @return array
+ */
+ public function getQueue()
+ {
+ return $this->queue;
+ }
+
+ /**
+ * Check if this is a temporary plugin
+ *
+ * @return bool
+ */
+ public function isTemporary()
+ {
+ return $this->temporary;
+ }
+
+ /**
+ * Get a response from the front of the list and add it to a request
+ *
+ * @param RequestInterface $request Request to mock
+ *
+ * @return self
+ * @throws CurlException When request.send is called and an exception is queued
+ */
+ public function dequeue(RequestInterface $request)
+ {
+ $this->dispatch('mock.request', array('plugin' => $this, 'request' => $request));
+
+ $item = array_shift($this->queue);
+ if ($item instanceof Response) {
+ if ($this->readBodies && $request instanceof EntityEnclosingRequestInterface) {
+ $request->getEventDispatcher()->addListener('request.sent', $f = function (Event $event) use (&$f) {
+ while ($data = $event['request']->getBody()->read(8096));
+ // Remove the listener after one-time use
+ $event['request']->getEventDispatcher()->removeListener('request.sent', $f);
+ });
+ }
+ $request->setResponse($item);
+ } elseif ($item instanceof CurlException) {
+ // Emulates exceptions encountered while transferring requests
+ $item->setRequest($request);
+ $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $item));
+ // Only throw if the exception wasn't handled
+ if ($state == RequestInterface::STATE_ERROR) {
+ throw $item;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear the array of received requests
+ */
+ public function flush()
+ {
+ $this->received = array();
+ }
+
+ /**
+ * Get an array of requests that were mocked by this plugin
+ *
+ * @return array
+ */
+ public function getReceivedRequests()
+ {
+ return $this->received;
+ }
+
+ /**
+ * Called when a request is about to be sent
+ *
+ * @param Event $event
+ * @throws \OutOfBoundsException When queue is empty
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ if (!$this->queue) {
+ throw new \OutOfBoundsException('Mock queue is empty');
+ }
+
+ $request = $event['request'];
+ $this->received[] = $request;
+ // Detach the filter from the client so it's a one-time use
+ if ($this->temporary && count($this->queue) == 1 && $request->getClient()) {
+ $request->getClient()->getEventDispatcher()->removeSubscriber($this);
+ }
+ $this->dequeue($request);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json
new file mode 100644
index 0000000..f8201e3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-mock",
+ "description": "Guzzle Mock plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["mock", "plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Mock": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Mock",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php
new file mode 100644
index 0000000..95e0c3e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php
@@ -0,0 +1,306 @@
+<?php
+
+namespace Guzzle\Plugin\Oauth;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\Collection;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\QueryString;
+use Guzzle\Http\Url;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * OAuth signing plugin
+ * @link http://oauth.net/core/1.0/#rfc.section.9.1.1
+ */
+class OauthPlugin implements EventSubscriberInterface
+{
+ /**
+ * Consumer request method constants. See http://oauth.net/core/1.0/#consumer_req_param
+ */
+ const REQUEST_METHOD_HEADER = 'header';
+ const REQUEST_METHOD_QUERY = 'query';
+
+ /** @var Collection Configuration settings */
+ protected $config;
+
+ /**
+ * Create a new OAuth 1.0 plugin
+ *
+ * @param array $config Configuration array containing these parameters:
+ * - string 'request_method' Consumer request method. Use the class constants.
+ * - string 'callback' OAuth callback
+ * - string 'consumer_key' Consumer key
+ * - string 'consumer_secret' Consumer secret
+ * - string 'token' Token
+ * - string 'token_secret' Token secret
+ * - string 'verifier' OAuth verifier.
+ * - string 'version' OAuth version. Defaults to 1.0
+ * - string 'signature_method' Custom signature method
+ * - bool 'disable_post_params' Set to true to prevent POST parameters from being signed
+ * - array|Closure 'signature_callback' Custom signature callback that accepts a string to sign and a signing key
+ */
+ public function __construct($config)
+ {
+ $this->config = Collection::fromConfig($config, array(
+ 'version' => '1.0',
+ 'request_method' => self::REQUEST_METHOD_HEADER,
+ 'consumer_key' => 'anonymous',
+ 'consumer_secret' => 'anonymous',
+ 'signature_method' => 'HMAC-SHA1',
+ 'signature_callback' => function($stringToSign, $key) {
+ return hash_hmac('sha1', $stringToSign, $key, true);
+ }
+ ), array(
+ 'signature_method', 'signature_callback', 'version',
+ 'consumer_key', 'consumer_secret'
+ ));
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', -1000)
+ );
+ }
+
+ /**
+ * Request before-send event handler
+ *
+ * @param Event $event Event received
+ * @return array
+ * @throws \InvalidArgumentException
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ $timestamp = $this->getTimestamp($event);
+ $request = $event['request'];
+ $nonce = $this->generateNonce($request);
+ $authorizationParams = $this->getOauthParams($timestamp, $nonce);
+ $authorizationParams['oauth_signature'] = $this->getSignature($request, $timestamp, $nonce);
+
+ switch ($this->config['request_method']) {
+ case self::REQUEST_METHOD_HEADER:
+ $request->setHeader(
+ 'Authorization',
+ $this->buildAuthorizationHeader($authorizationParams)
+ );
+ break;
+ case self::REQUEST_METHOD_QUERY:
+ foreach ($authorizationParams as $key => $value) {
+ $request->getQuery()->set($key, $value);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid consumer method "%s"',
+ $this->config['request_method']
+ ));
+ }
+
+ return $authorizationParams;
+ }
+
+ /**
+ * Builds the Authorization header for a request
+ *
+ * @param array $authorizationParams Associative array of authorization parameters
+ *
+ * @return string
+ */
+ private function buildAuthorizationHeader($authorizationParams)
+ {
+ $authorizationString = 'OAuth ';
+ foreach ($authorizationParams as $key => $val) {
+ if ($val) {
+ $authorizationString .= $key . '="' . urlencode($val) . '", ';
+ }
+ }
+
+ return substr($authorizationString, 0, -2);
+ }
+
+ /**
+ * Calculate signature for request
+ *
+ * @param RequestInterface $request Request to generate a signature for
+ * @param integer $timestamp Timestamp to use for nonce
+ * @param string $nonce
+ *
+ * @return string
+ */
+ public function getSignature(RequestInterface $request, $timestamp, $nonce)
+ {
+ $string = $this->getStringToSign($request, $timestamp, $nonce);
+ $key = urlencode($this->config['consumer_secret']) . '&' . urlencode($this->config['token_secret']);
+
+ return base64_encode(call_user_func($this->config['signature_callback'], $string, $key));
+ }
+
+ /**
+ * Calculate string to sign
+ *
+ * @param RequestInterface $request Request to generate a signature for
+ * @param int $timestamp Timestamp to use for nonce
+ * @param string $nonce
+ *
+ * @return string
+ */
+ public function getStringToSign(RequestInterface $request, $timestamp, $nonce)
+ {
+ $params = $this->getParamsToSign($request, $timestamp, $nonce);
+
+ // Convert booleans to strings.
+ $params = $this->prepareParameters($params);
+
+ // Build signing string from combined params
+ $parameterString = clone $request->getQuery();
+ $parameterString->replace($params);
+
+ $url = Url::factory($request->getUrl())->setQuery('')->setFragment(null);
+
+ return strtoupper($request->getMethod()) . '&'
+ . rawurlencode($url) . '&'
+ . rawurlencode((string) $parameterString);
+ }
+
+ /**
+ * Get the oauth parameters as named by the oauth spec
+ *
+ * @param $timestamp
+ * @param $nonce
+ * @return Collection
+ */
+ protected function getOauthParams($timestamp, $nonce)
+ {
+ $params = new Collection(array(
+ 'oauth_consumer_key' => $this->config['consumer_key'],
+ 'oauth_nonce' => $nonce,
+ 'oauth_signature_method' => $this->config['signature_method'],
+ 'oauth_timestamp' => $timestamp,
+ ));
+
+ // Optional parameters should not be set if they have not been set in the config as
+ // the parameter may be considered invalid by the Oauth service.
+ $optionalParams = array(
+ 'callback' => 'oauth_callback',
+ 'token' => 'oauth_token',
+ 'verifier' => 'oauth_verifier',
+ 'version' => 'oauth_version'
+ );
+
+ foreach ($optionalParams as $optionName => $oauthName) {
+ if (isset($this->config[$optionName]) == true) {
+ $params[$oauthName] = $this->config[$optionName];
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Get all of the parameters required to sign a request including:
+ * * The oauth params
+ * * The request GET params
+ * * The params passed in the POST body (with a content-type of application/x-www-form-urlencoded)
+ *
+ * @param RequestInterface $request Request to generate a signature for
+ * @param integer $timestamp Timestamp to use for nonce
+ * @param string $nonce
+ *
+ * @return array
+ */
+ public function getParamsToSign(RequestInterface $request, $timestamp, $nonce)
+ {
+ $params = $this->getOauthParams($timestamp, $nonce);
+
+ // Add query string parameters
+ $params->merge($request->getQuery());
+
+ // Add POST fields to signing string if required
+ if ($this->shouldPostFieldsBeSigned($request))
+ {
+ $params->merge($request->getPostFields());
+ }
+
+ // Sort params
+ $params = $params->toArray();
+ uksort($params, 'strcmp');
+
+ return $params;
+ }
+
+ /**
+ * Decide whether the post fields should be added to the base string that Oauth signs.
+ * This implementation is correct. Non-conformant APIs may require that this method be
+ * overwritten e.g. the Flickr API incorrectly adds the post fields when the Content-Type
+ * is 'application/x-www-form-urlencoded'
+ *
+ * @param $request
+ * @return bool Whether the post fields should be signed or not
+ */
+ public function shouldPostFieldsBeSigned($request)
+ {
+ if (!$this->config->get('disable_post_params') &&
+ $request instanceof EntityEnclosingRequestInterface &&
+ false !== strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded'))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a Nonce Based on the unique id and URL. This will allow for multiple requests in parallel with the same
+ * exact timestamp to use separate nonce's.
+ *
+ * @param RequestInterface $request Request to generate a nonce for
+ *
+ * @return string
+ */
+ public function generateNonce(RequestInterface $request)
+ {
+ return sha1(uniqid('', true) . $request->getUrl());
+ }
+
+ /**
+ * Gets timestamp from event or create new timestamp
+ *
+ * @param Event $event Event containing contextual information
+ *
+ * @return int
+ */
+ public function getTimestamp(Event $event)
+ {
+ return $event['timestamp'] ?: time();
+ }
+
+ /**
+ * Convert booleans to strings, removed unset parameters, and sorts the array
+ *
+ * @param array $data Data array
+ *
+ * @return array
+ */
+ protected function prepareParameters($data)
+ {
+ ksort($data);
+ foreach ($data as $key => &$value) {
+ switch (gettype($value)) {
+ case 'NULL':
+ unset($data[$key]);
+ break;
+ case 'array':
+ $data[$key] = self::prepareParameters($value);
+ break;
+ case 'boolean':
+ $data[$key] = $value ? 'true' : 'false';
+ break;
+ }
+ }
+
+ return $data;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json
new file mode 100644
index 0000000..c9766ba
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "guzzle/plugin-oauth",
+ "description": "Guzzle OAuth plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["oauth", "plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Oauth": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Oauth",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json
new file mode 100644
index 0000000..2bbe64c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "guzzle/plugin",
+ "description": "Guzzle plugin component containing all Guzzle HTTP plugins",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["http", "client", "plugin", "extension", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version"
+ },
+ "suggest": {
+ "guzzle/cache": "self.version",
+ "guzzle/log": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin": "" }
+ },
+ "target-dir": "Guzzle/Plugin",
+ "replace": {
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php b/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php
new file mode 100644
index 0000000..cd06f57
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace Guzzle\Service;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Abstract config loader
+ */
+abstract class AbstractConfigLoader implements ConfigLoaderInterface
+{
+ /** @var array Array of aliases for actual filenames */
+ protected $aliases = array();
+
+ /** @var array Hash of previously loaded filenames */
+ protected $loadedFiles = array();
+
+ /** @var array JSON error code mappings */
+ protected static $jsonErrors = array(
+ JSON_ERROR_NONE => 'JSON_ERROR_NONE - No errors',
+ JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
+ JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
+ JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
+ JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
+ );
+
+ public function load($config, array $options = array())
+ {
+ // Reset the array of loaded files because this is a new config
+ $this->loadedFiles = array();
+
+ if (is_string($config)) {
+ $config = $this->loadFile($config);
+ } elseif (!is_array($config)) {
+ throw new InvalidArgumentException('Unknown type passed to configuration loader: ' . gettype($config));
+ } else {
+ $this->mergeIncludes($config);
+ }
+
+ return $this->build($config, $options);
+ }
+
+ /**
+ * Add an include alias to the loader
+ *
+ * @param string $filename Filename to alias (e.g. _foo)
+ * @param string $alias Actual file to use (e.g. /path/to/foo.json)
+ *
+ * @return self
+ */
+ public function addAlias($filename, $alias)
+ {
+ $this->aliases[$filename] = $alias;
+
+ return $this;
+ }
+
+ /**
+ * Remove an alias from the loader
+ *
+ * @param string $alias Alias to remove
+ *
+ * @return self
+ */
+ public function removeAlias($alias)
+ {
+ unset($this->aliases[$alias]);
+
+ return $this;
+ }
+
+ /**
+ * Perform the parsing of a config file and create the end result
+ *
+ * @param array $config Configuration data
+ * @param array $options Options to use when building
+ *
+ * @return mixed
+ */
+ protected abstract function build($config, array $options);
+
+ /**
+ * Load a configuration file (can load JSON or PHP files that return an array when included)
+ *
+ * @param string $filename File to load
+ *
+ * @return array
+ * @throws InvalidArgumentException
+ * @throws RuntimeException when the JSON cannot be parsed
+ */
+ protected function loadFile($filename)
+ {
+ if (isset($this->aliases[$filename])) {
+ $filename = $this->aliases[$filename];
+ }
+
+ switch (pathinfo($filename, PATHINFO_EXTENSION)) {
+ case 'js':
+ case 'json':
+ $level = error_reporting(0);
+ $json = file_get_contents($filename);
+ error_reporting($level);
+
+ if ($json === false) {
+ $err = error_get_last();
+ throw new InvalidArgumentException("Unable to open {$filename}: " . $err['message']);
+ }
+
+ $config = json_decode($json, true);
+ // Throw an exception if there was an error loading the file
+ if ($error = json_last_error()) {
+ $message = isset(self::$jsonErrors[$error]) ? self::$jsonErrors[$error] : 'Unknown error';
+ throw new RuntimeException("Error loading JSON data from {$filename}: ({$error}) - {$message}");
+ }
+ break;
+ case 'php':
+ if (!is_readable($filename)) {
+ throw new InvalidArgumentException("Unable to open {$filename} for reading");
+ }
+ $config = require $filename;
+ if (!is_array($config)) {
+ throw new InvalidArgumentException('PHP files must return an array of configuration data');
+ }
+ break;
+ default:
+ throw new InvalidArgumentException('Unknown file extension: ' . $filename);
+ }
+
+ // Keep track of this file being loaded to prevent infinite recursion
+ $this->loadedFiles[$filename] = true;
+
+ // Merge include files into the configuration array
+ $this->mergeIncludes($config, dirname($filename));
+
+ return $config;
+ }
+
+ /**
+ * Merges in all include files
+ *
+ * @param array $config Config data that contains includes
+ * @param string $basePath Base path to use when a relative path is encountered
+ *
+ * @return array Returns the merged and included data
+ */
+ protected function mergeIncludes(&$config, $basePath = null)
+ {
+ if (!empty($config['includes'])) {
+ foreach ($config['includes'] as &$path) {
+ // Account for relative paths
+ if ($path[0] != DIRECTORY_SEPARATOR && !isset($this->aliases[$path]) && $basePath) {
+ $path = "{$basePath}/{$path}";
+ }
+ // Don't load the same files more than once
+ if (!isset($this->loadedFiles[$path])) {
+ $this->loadedFiles[$path] = true;
+ $config = $this->mergeData($this->loadFile($path), $config);
+ }
+ }
+ }
+ }
+
+ /**
+ * Default implementation for merging two arrays of data (uses array_merge_recursive)
+ *
+ * @param array $a Original data
+ * @param array $b Data to merge into the original and overwrite existing values
+ *
+ * @return array
+ */
+ protected function mergeData(array $a, array $b)
+ {
+ return array_merge_recursive($a, $b);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php
new file mode 100644
index 0000000..38150db
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace Guzzle\Service\Builder;
+
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Service\ClientInterface;
+use Guzzle\Service\Exception\ServiceBuilderException;
+use Guzzle\Service\Exception\ServiceNotFoundException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * {@inheritdoc}
+ *
+ * Clients and data can be set, retrieved, and removed by accessing the service builder like an associative array.
+ */
+class ServiceBuilder extends AbstractHasDispatcher implements ServiceBuilderInterface, \ArrayAccess, \Serializable
+{
+ /** @var array Service builder configuration data */
+ protected $builderConfig = array();
+
+ /** @var array Instantiated client objects */
+ protected $clients = array();
+
+ /** @var ServiceBuilderLoader Cached instance of the service builder loader */
+ protected static $cachedFactory;
+
+ /** @var array Plugins to attach to each client created by the service builder */
+ protected $plugins = array();
+
+ /**
+ * Create a new ServiceBuilder using configuration data sourced from an
+ * array, .js|.json or .php file.
+ *
+ * @param array|string $config The full path to an .json|.js or .php file, or an associative array
+ * @param array $globalParameters Array of global parameters to pass to every service as it is instantiated.
+ *
+ * @return ServiceBuilderInterface
+ * @throws ServiceBuilderException if a file cannot be opened
+ * @throws ServiceNotFoundException when trying to extend a missing client
+ */
+ public static function factory($config = null, array $globalParameters = array())
+ {
+ // @codeCoverageIgnoreStart
+ if (!static::$cachedFactory) {
+ static::$cachedFactory = new ServiceBuilderLoader();
+ }
+ // @codeCoverageIgnoreEnd
+
+ return self::$cachedFactory->load($config, $globalParameters);
+ }
+
+ /**
+ * @param array $serviceBuilderConfig Service configuration settings:
+ * - name: Name of the service
+ * - class: Client class to instantiate using a factory method
+ * - params: array of key value pair configuration settings for the builder
+ */
+ public function __construct(array $serviceBuilderConfig = array())
+ {
+ $this->builderConfig = $serviceBuilderConfig;
+ }
+
+ public static function getAllEvents()
+ {
+ return array('service_builder.create_client');
+ }
+
+ public function unserialize($serialized)
+ {
+ $this->builderConfig = json_decode($serialized, true);
+ }
+
+ public function serialize()
+ {
+ return json_encode($this->builderConfig);
+ }
+
+ /**
+ * Attach a plugin to every client created by the builder
+ *
+ * @param EventSubscriberInterface $plugin Plugin to attach to each client
+ *
+ * @return self
+ */
+ public function addGlobalPlugin(EventSubscriberInterface $plugin)
+ {
+ $this->plugins[] = $plugin;
+
+ return $this;
+ }
+
+ /**
+ * Get data from the service builder without triggering the building of a service
+ *
+ * @param string $name Name of the service to retrieve
+ *
+ * @return array|null
+ */
+ public function getData($name)
+ {
+ return isset($this->builderConfig[$name]) ? $this->builderConfig[$name] : null;
+ }
+
+ public function get($name, $throwAway = false)
+ {
+ if (!isset($this->builderConfig[$name])) {
+
+ // Check to see if arbitrary data is being referenced
+ if (isset($this->clients[$name])) {
+ return $this->clients[$name];
+ }
+
+ // Check aliases and return a match if found
+ foreach ($this->builderConfig as $actualName => $config) {
+ if (isset($config['alias']) && $config['alias'] == $name) {
+ return $this->get($actualName, $throwAway);
+ }
+ }
+ throw new ServiceNotFoundException('No service is registered as ' . $name);
+ }
+
+ if (!$throwAway && isset($this->clients[$name])) {
+ return $this->clients[$name];
+ }
+
+ $builder =& $this->builderConfig[$name];
+
+ // Convert references to the actual client
+ foreach ($builder['params'] as &$v) {
+ if (is_string($v) && substr($v, 0, 1) == '{' && substr($v, -1) == '}') {
+ $v = $this->get(trim($v, '{} '));
+ }
+ }
+
+ // Get the configured parameters and merge in any parameters provided for throw-away clients
+ $config = $builder['params'];
+ if (is_array($throwAway)) {
+ $config = $throwAway + $config;
+ }
+
+ $client = $builder['class']::factory($config);
+
+ if (!$throwAway) {
+ $this->clients[$name] = $client;
+ }
+
+ if ($client instanceof ClientInterface) {
+ foreach ($this->plugins as $plugin) {
+ $client->addSubscriber($plugin);
+ }
+ // Dispatch an event letting listeners know a client was created
+ $this->dispatch('service_builder.create_client', array('client' => $client));
+ }
+
+ return $client;
+ }
+
+ public function set($key, $service)
+ {
+ if (is_array($service) && isset($service['class']) && isset($service['params'])) {
+ $this->builderConfig[$key] = $service;
+ } else {
+ $this->clients[$key] = $service;
+ }
+
+ return $this;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->builderConfig[$offset]);
+ unset($this->clients[$offset]);
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->builderConfig[$offset]) || isset($this->clients[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php
new file mode 100644
index 0000000..4fc310a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Service\Builder;
+
+use Guzzle\Service\Exception\ServiceNotFoundException;
+
+/**
+ * Service builder used to store and build clients or arbitrary data. Client configuration data can be supplied to tell
+ * the service builder how to create and cache {@see \Guzzle\Service\ClientInterface} objects. Arbitrary data can be
+ * supplied and accessed from a service builder. Arbitrary data and other clients can be referenced by name in client
+ * configuration arrays to make them input for building other clients (e.g. "{key}").
+ */
+interface ServiceBuilderInterface
+{
+ /**
+ * Get a ClientInterface object or arbitrary data from the service builder
+ *
+ * @param string $name Name of the registered service or data to retrieve
+ * @param bool|array $throwAway Only pertains to retrieving client objects built using a configuration array.
+ * Set to TRUE to not store the client for later retrieval from the ServiceBuilder.
+ * If an array is specified, that data will overwrite the configured params of the
+ * client if the client implements {@see \Guzzle\Common\FromConfigInterface} and will
+ * not store the client for later retrieval.
+ *
+ * @return \Guzzle\Service\ClientInterface|mixed
+ * @throws ServiceNotFoundException when a client or data cannot be found by the given name
+ */
+ public function get($name, $throwAway = false);
+
+ /**
+ * Register a service or arbitrary data by name with the service builder
+ *
+ * @param string $key Name of the client or data to register
+ * @param mixed $service Client configuration array or arbitrary data to register. The client configuration array
+ * must include a 'class' (string) and 'params' (array) key.
+ *
+ * @return ServiceBuilderInterface
+ */
+ public function set($key, $service);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderLoader.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderLoader.php
new file mode 100644
index 0000000..c561a3d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderLoader.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Guzzle\Service\Builder;
+
+use Guzzle\Service\AbstractConfigLoader;
+use Guzzle\Service\Exception\ServiceNotFoundException;
+
+/**
+ * Service builder config loader
+ */
+class ServiceBuilderLoader extends AbstractConfigLoader
+{
+ protected function build($config, array $options)
+ {
+ // A service builder class can be specified in the class field
+ $class = !empty($config['class']) ? $config['class'] : __NAMESPACE__ . '\\ServiceBuilder';
+
+ // Account for old style configs that do not have a services array
+ $services = isset($config['services']) ? $config['services'] : $config;
+
+ // Validate the configuration and handle extensions
+ foreach ($services as $name => &$service) {
+
+ $service['params'] = isset($service['params']) ? $service['params'] : array();
+
+ // Check if this client builder extends another client
+ if (!empty($service['extends'])) {
+
+ // Make sure that the service it's extending has been defined
+ if (!isset($services[$service['extends']])) {
+ throw new ServiceNotFoundException(
+ "{$name} is trying to extend a non-existent service: {$service['extends']}"
+ );
+ }
+
+ $extended = &$services[$service['extends']];
+
+ // Use the correct class attribute
+ if (empty($service['class'])) {
+ $service['class'] = isset($extended['class']) ? $extended['class'] : '';
+ }
+ if ($extendsParams = isset($extended['params']) ? $extended['params'] : false) {
+ $service['params'] = $service['params'] + $extendsParams;
+ }
+ }
+
+ // Overwrite default values with global parameter values
+ if (!empty($options)) {
+ $service['params'] = $options + $service['params'];
+ }
+
+ $service['class'] = isset($service['class']) ? $service['class'] : '';
+ }
+
+ return new $class($services);
+ }
+
+ protected function mergeData(array $a, array $b)
+ {
+ $result = $b + $a;
+
+ // Merge services using a recursive union of arrays
+ if (isset($a['services']) && $b['services']) {
+
+ // Get a union of the services of the two arrays
+ $result['services'] = $b['services'] + $a['services'];
+
+ // Merge each service in using a union of the two arrays
+ foreach ($result['services'] as $name => &$service) {
+
+ // By default, services completely override a previously defined service unless it extends itself
+ if (isset($a['services'][$name]['extends'])
+ && isset($b['services'][$name]['extends'])
+ && $b['services'][$name]['extends'] == $name
+ ) {
+ $service += $a['services'][$name];
+ // Use the `extends` attribute of the parent
+ $service['extends'] = $a['services'][$name]['extends'];
+ // Merge parameters using a union if both have parameters
+ if (isset($a['services'][$name]['params'])) {
+ $service['params'] += $a['services'][$name]['params'];
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php b/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php
new file mode 100644
index 0000000..26f8360
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Service;
+
+use Guzzle\Cache\CacheAdapterInterface;
+
+/**
+ * Decorator that adds caching to a service description loader
+ */
+class CachingConfigLoader implements ConfigLoaderInterface
+{
+ /** @var ConfigLoaderInterface */
+ protected $loader;
+
+ /** @var CacheAdapterInterface */
+ protected $cache;
+
+ /**
+ * @param ConfigLoaderInterface $loader Loader used to load the config when there is a cache miss
+ * @param CacheAdapterInterface $cache Object used to cache the loaded result
+ */
+ public function __construct(ConfigLoaderInterface $loader, CacheAdapterInterface $cache)
+ {
+ $this->loader = $loader;
+ $this->cache = $cache;
+ }
+
+ public function load($config, array $options = array())
+ {
+ if (!is_string($config)) {
+ $key = false;
+ } else {
+ $key = 'loader_' . crc32($config);
+ if ($result = $this->cache->fetch($key)) {
+ return $result;
+ }
+ }
+
+ $result = $this->loader->load($config, $options);
+ if ($key) {
+ $this->cache->save($key, $result);
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php
new file mode 100644
index 0000000..3e5f8e5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace Guzzle\Service;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\BadMethodCallException;
+use Guzzle\Common\Version;
+use Guzzle\Inflection\InflectorInterface;
+use Guzzle\Inflection\Inflector;
+use Guzzle\Http\Client as HttpClient;
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Service\Exception\CommandTransferException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Command\Factory\CompositeFactory;
+use Guzzle\Service\Command\Factory\FactoryInterface as CommandFactoryInterface;
+use Guzzle\Service\Resource\ResourceIteratorClassFactory;
+use Guzzle\Service\Resource\ResourceIteratorFactoryInterface;
+use Guzzle\Service\Description\ServiceDescriptionInterface;
+
+/**
+ * Client object for executing commands on a web service.
+ */
+class Client extends HttpClient implements ClientInterface
+{
+ const COMMAND_PARAMS = 'command.params';
+
+ /** @var ServiceDescriptionInterface Description of the service and possible commands */
+ protected $serviceDescription;
+
+ /** @var CommandFactoryInterface */
+ protected $commandFactory;
+
+ /** @var ResourceIteratorFactoryInterface */
+ protected $resourceIteratorFactory;
+
+ /** @var InflectorInterface Inflector associated with the service/client */
+ protected $inflector;
+
+ /**
+ * Basic factory method to create a new client. Extend this method in subclasses to build more complex clients.
+ *
+ * @param array|Collection $config Configuration data
+ *
+ * @return Client
+ */
+ public static function factory($config = array())
+ {
+ return new static(isset($config['base_url']) ? $config['base_url'] : null, $config);
+ }
+
+ public static function getAllEvents()
+ {
+ return array_merge(HttpClient::getAllEvents(), array(
+ 'client.command.create',
+ 'command.before_prepare',
+ 'command.after_prepare',
+ 'command.before_send',
+ 'command.after_send',
+ 'command.parse_response'
+ ));
+ }
+
+ /**
+ * Magic method used to retrieve a command
+ *
+ * @param string $method Name of the command object to instantiate
+ * @param array $args Arguments to pass to the command
+ *
+ * @return mixed Returns the result of the command
+ * @throws BadMethodCallException when a command is not found
+ */
+ public function __call($method, $args)
+ {
+ return $this->getCommand($method, isset($args[0]) ? $args[0] : array())->getResult();
+ }
+
+ public function getCommand($name, array $args = array())
+ {
+ // Add global client options to the command
+ if ($options = $this->getConfig(self::COMMAND_PARAMS)) {
+ $args += $options;
+ }
+
+ if (!($command = $this->getCommandFactory()->factory($name, $args))) {
+ throw new InvalidArgumentException("Command was not found matching {$name}");
+ }
+
+ $command->setClient($this);
+ $this->dispatch('client.command.create', array('client' => $this, 'command' => $command));
+
+ return $command;
+ }
+
+ /**
+ * Set the command factory used to create commands by name
+ *
+ * @param CommandFactoryInterface $factory Command factory
+ *
+ * @return self
+ */
+ public function setCommandFactory(CommandFactoryInterface $factory)
+ {
+ $this->commandFactory = $factory;
+
+ return $this;
+ }
+
+ /**
+ * Set the resource iterator factory associated with the client
+ *
+ * @param ResourceIteratorFactoryInterface $factory Resource iterator factory
+ *
+ * @return self
+ */
+ public function setResourceIteratorFactory(ResourceIteratorFactoryInterface $factory)
+ {
+ $this->resourceIteratorFactory = $factory;
+
+ return $this;
+ }
+
+ public function getIterator($command, array $commandOptions = null, array $iteratorOptions = array())
+ {
+ if (!($command instanceof CommandInterface)) {
+ $command = $this->getCommand($command, $commandOptions ?: array());
+ }
+
+ return $this->getResourceIteratorFactory()->build($command, $iteratorOptions);
+ }
+
+ public function execute($command)
+ {
+ if ($command instanceof CommandInterface) {
+ $this->send($this->prepareCommand($command));
+ $this->dispatch('command.after_send', array('command' => $command));
+ return $command->getResult();
+ } elseif (is_array($command) || $command instanceof \Traversable) {
+ return $this->executeMultiple($command);
+ } else {
+ throw new InvalidArgumentException('Command must be a command or array of commands');
+ }
+ }
+
+ public function setDescription(ServiceDescriptionInterface $service)
+ {
+ $this->serviceDescription = $service;
+
+ if ($this->getCommandFactory() && $this->getCommandFactory() instanceof CompositeFactory) {
+ $this->commandFactory->add(new Command\Factory\ServiceDescriptionFactory($service));
+ }
+
+ // If a baseUrl was set on the description, then update the client
+ if ($baseUrl = $service->getBaseUrl()) {
+ $this->setBaseUrl($baseUrl);
+ }
+
+ return $this;
+ }
+
+ public function getDescription()
+ {
+ return $this->serviceDescription;
+ }
+
+ /**
+ * Set the inflector used with the client
+ *
+ * @param InflectorInterface $inflector Inflection object
+ *
+ * @return self
+ */
+ public function setInflector(InflectorInterface $inflector)
+ {
+ $this->inflector = $inflector;
+
+ return $this;
+ }
+
+ /**
+ * Get the inflector used with the client
+ *
+ * @return self
+ */
+ public function getInflector()
+ {
+ if (!$this->inflector) {
+ $this->inflector = Inflector::getDefault();
+ }
+
+ return $this->inflector;
+ }
+
+ /**
+ * Prepare a command for sending and get the RequestInterface object created by the command
+ *
+ * @param CommandInterface $command Command to prepare
+ *
+ * @return RequestInterface
+ */
+ protected function prepareCommand(CommandInterface $command)
+ {
+ // Set the client and prepare the command
+ $request = $command->setClient($this)->prepare();
+ // Set the state to new if the command was previously executed
+ $request->setState(RequestInterface::STATE_NEW);
+ $this->dispatch('command.before_send', array('command' => $command));
+
+ return $request;
+ }
+
+ /**
+ * Execute multiple commands in parallel
+ *
+ * @param array|Traversable $commands Array of CommandInterface objects to execute
+ *
+ * @return array Returns an array of the executed commands
+ * @throws Exception\CommandTransferException
+ */
+ protected function executeMultiple($commands)
+ {
+ $requests = array();
+ $commandRequests = new \SplObjectStorage();
+
+ foreach ($commands as $command) {
+ $request = $this->prepareCommand($command);
+ $commandRequests[$request] = $command;
+ $requests[] = $request;
+ }
+
+ try {
+ $this->send($requests);
+ foreach ($commands as $command) {
+ $this->dispatch('command.after_send', array('command' => $command));
+ }
+ return $commands;
+ } catch (MultiTransferException $failureException) {
+ // Throw a CommandTransferException using the successful and failed commands
+ $e = CommandTransferException::fromMultiTransferException($failureException);
+
+ // Remove failed requests from the successful requests array and add to the failures array
+ foreach ($failureException->getFailedRequests() as $request) {
+ if (isset($commandRequests[$request])) {
+ $e->addFailedCommand($commandRequests[$request]);
+ unset($commandRequests[$request]);
+ }
+ }
+
+ // Always emit the command after_send events for successful commands
+ foreach ($commandRequests as $success) {
+ $e->addSuccessfulCommand($commandRequests[$success]);
+ $this->dispatch('command.after_send', array('command' => $commandRequests[$success]));
+ }
+
+ throw $e;
+ }
+ }
+
+ protected function getResourceIteratorFactory()
+ {
+ if (!$this->resourceIteratorFactory) {
+ // Build the default resource iterator factory if one is not set
+ $clientClass = get_class($this);
+ $prefix = substr($clientClass, 0, strrpos($clientClass, '\\'));
+ $this->resourceIteratorFactory = new ResourceIteratorClassFactory(array(
+ "{$prefix}\\Iterator",
+ "{$prefix}\\Model"
+ ));
+ }
+
+ return $this->resourceIteratorFactory;
+ }
+
+ /**
+ * Get the command factory associated with the client
+ *
+ * @return CommandFactoryInterface
+ */
+ protected function getCommandFactory()
+ {
+ if (!$this->commandFactory) {
+ $this->commandFactory = CompositeFactory::getDefaultChain($this);
+ }
+
+ return $this->commandFactory;
+ }
+
+ /**
+ * @deprecated
+ * @codeCoverageIgnore
+ */
+ public function enableMagicMethods($isEnabled)
+ {
+ Version::warn(__METHOD__ . ' is deprecated');
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php
new file mode 100644
index 0000000..814154f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Guzzle\Service;
+
+use Guzzle\Common\FromConfigInterface;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\ClientInterface as HttpClientInterface;
+use Guzzle\Service\Exception\CommandTransferException;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\ServiceDescriptionInterface;
+use Guzzle\Service\Resource\ResourceIteratorInterface;
+
+/**
+ * Client interface for executing commands on a web service.
+ */
+interface ClientInterface extends HttpClientInterface, FromConfigInterface
+{
+ /**
+ * Get a command by name. First, the client will see if it has a service description and if the service description
+ * defines a command by the supplied name. If no dynamic command is found, the client will look for a concrete
+ * command class exists matching the name supplied. If neither are found, an InvalidArgumentException is thrown.
+ *
+ * @param string $name Name of the command to retrieve
+ * @param array $args Arguments to pass to the command
+ *
+ * @return CommandInterface
+ * @throws InvalidArgumentException if no command can be found by name
+ */
+ public function getCommand($name, array $args = array());
+
+ /**
+ * Execute one or more commands
+ *
+ * @param CommandInterface|array|Traversable $command Command, array of commands or Traversable object containing commands to execute
+ *
+ * @return mixed Returns the result of the executed command or an array of commands if executing multiple commands
+ * @throws InvalidArgumentException if an invalid command is passed
+ * @throws CommandTransferException if an exception is encountered when transferring multiple commands
+ */
+ public function execute($command);
+
+ /**
+ * Set the service description of the client
+ *
+ * @param ServiceDescriptionInterface $service Service description
+ *
+ * @return ClientInterface
+ */
+ public function setDescription(ServiceDescriptionInterface $service);
+
+ /**
+ * Get the service description of the client
+ *
+ * @return ServiceDescriptionInterface|null
+ */
+ public function getDescription();
+
+ /**
+ * Get a resource iterator from the client.
+ *
+ * @param string|CommandInterface $command Command class or command name.
+ * @param array $commandOptions Command options used when creating commands.
+ * @param array $iteratorOptions Iterator options passed to the iterator when it is instantiated.
+ *
+ * @return ResourceIteratorInterface
+ */
+ public function getIterator($command, array $commandOptions = null, array $iteratorOptions = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/AbstractCommand.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/AbstractCommand.php
new file mode 100644
index 0000000..e42ff90
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/AbstractCommand.php
@@ -0,0 +1,390 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Service\Client;
+use Guzzle\Service\ClientInterface;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\OperationInterface;
+use Guzzle\Service\Description\ValidatorInterface;
+use Guzzle\Service\Description\SchemaValidator;
+use Guzzle\Service\Exception\CommandException;
+use Guzzle\Service\Exception\ValidationException;
+
+/**
+ * Command object to handle preparing and processing client requests and responses of the requests
+ */
+abstract class AbstractCommand extends Collection implements CommandInterface
+{
+ // @deprecated: Option used to specify custom headers to add to the generated request
+ const HEADERS_OPTION = 'command.headers';
+ // @deprecated: Option used to add an onComplete method to a command
+ const ON_COMPLETE = 'command.on_complete';
+ // @deprecated: Option used to change the entity body used to store a response
+ const RESPONSE_BODY = 'command.response_body';
+
+ // Option used to add request options to the request created by a command
+ const REQUEST_OPTIONS = 'command.request_options';
+ // command values to not count as additionalParameters
+ const HIDDEN_PARAMS = 'command.hidden_params';
+ // Option used to disable any pre-sending command validation
+ const DISABLE_VALIDATION = 'command.disable_validation';
+ // Option used to override how a command result will be formatted
+ const RESPONSE_PROCESSING = 'command.response_processing';
+ // Different response types that commands can use
+ const TYPE_RAW = 'raw';
+ const TYPE_MODEL = 'model';
+ const TYPE_NO_TRANSLATION = 'no_translation';
+
+ /** @var ClientInterface Client object used to execute the command */
+ protected $client;
+
+ /** @var RequestInterface The request object associated with the command */
+ protected $request;
+
+ /** @var mixed The result of the command */
+ protected $result;
+
+ /** @var OperationInterface API information about the command */
+ protected $operation;
+
+ /** @var mixed callable */
+ protected $onComplete;
+
+ /** @var ValidatorInterface Validator used to prepare and validate properties against a JSON schema */
+ protected $validator;
+
+ /**
+ * @param array|Collection $parameters Collection of parameters to set on the command
+ * @param OperationInterface $operation Command definition from description
+ */
+ public function __construct($parameters = array(), OperationInterface $operation = null)
+ {
+ parent::__construct($parameters);
+ $this->operation = $operation ?: $this->createOperation();
+ foreach ($this->operation->getParams() as $name => $arg) {
+ $currentValue = $this[$name];
+ $configValue = $arg->getValue($currentValue);
+ // If default or static values are set, then this should always be updated on the config object
+ if ($currentValue !== $configValue) {
+ $this[$name] = $configValue;
+ }
+ }
+
+ $headers = $this[self::HEADERS_OPTION];
+ if (!$headers instanceof Collection) {
+ $this[self::HEADERS_OPTION] = new Collection((array) $headers);
+ }
+
+ // You can set a command.on_complete option in your parameters to set an onComplete callback
+ if ($onComplete = $this['command.on_complete']) {
+ unset($this['command.on_complete']);
+ $this->setOnComplete($onComplete);
+ }
+
+ // Set the hidden additional parameters
+ if (!$this[self::HIDDEN_PARAMS]) {
+ $this[self::HIDDEN_PARAMS] = array(
+ self::HEADERS_OPTION,
+ self::RESPONSE_PROCESSING,
+ self::HIDDEN_PARAMS,
+ self::REQUEST_OPTIONS
+ );
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Custom clone behavior
+ */
+ public function __clone()
+ {
+ $this->request = null;
+ $this->result = null;
+ }
+
+ /**
+ * Execute the command in the same manner as calling a function
+ *
+ * @return mixed Returns the result of {@see AbstractCommand::execute}
+ */
+ public function __invoke()
+ {
+ return $this->execute();
+ }
+
+ public function getName()
+ {
+ return $this->operation->getName();
+ }
+
+ /**
+ * Get the API command information about the command
+ *
+ * @return OperationInterface
+ */
+ public function getOperation()
+ {
+ return $this->operation;
+ }
+
+ public function setOnComplete($callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('The onComplete function must be callable');
+ }
+
+ $this->onComplete = $callable;
+
+ return $this;
+ }
+
+ public function execute()
+ {
+ if (!$this->client) {
+ throw new CommandException('A client must be associated with the command before it can be executed.');
+ }
+
+ return $this->client->execute($this);
+ }
+
+ public function getClient()
+ {
+ return $this->client;
+ }
+
+ public function setClient(ClientInterface $client)
+ {
+ $this->client = $client;
+
+ return $this;
+ }
+
+ public function getRequest()
+ {
+ if (!$this->request) {
+ throw new CommandException('The command must be prepared before retrieving the request');
+ }
+
+ return $this->request;
+ }
+
+ public function getResponse()
+ {
+ if (!$this->isExecuted()) {
+ $this->execute();
+ }
+
+ return $this->request->getResponse();
+ }
+
+ public function getResult()
+ {
+ if (!$this->isExecuted()) {
+ $this->execute();
+ }
+
+ if (null === $this->result) {
+ $this->process();
+ // Call the onComplete method if one is set
+ if ($this->onComplete) {
+ call_user_func($this->onComplete, $this);
+ }
+ }
+
+ return $this->result;
+ }
+
+ public function setResult($result)
+ {
+ $this->result = $result;
+
+ return $this;
+ }
+
+ public function isPrepared()
+ {
+ return $this->request !== null;
+ }
+
+ public function isExecuted()
+ {
+ return $this->request !== null && $this->request->getState() == 'complete';
+ }
+
+ public function prepare()
+ {
+ if (!$this->isPrepared()) {
+ if (!$this->client) {
+ throw new CommandException('A client must be associated with the command before it can be prepared.');
+ }
+
+ // If no response processing value was specified, then attempt to use the highest level of processing
+ if (!isset($this[self::RESPONSE_PROCESSING])) {
+ $this[self::RESPONSE_PROCESSING] = self::TYPE_MODEL;
+ }
+
+ // Notify subscribers of the client that the command is being prepared
+ $this->client->dispatch('command.before_prepare', array('command' => $this));
+
+ // Fail on missing required arguments, and change parameters via filters
+ $this->validate();
+ // Delegate to the subclass that implements the build method
+ $this->build();
+
+ // Add custom request headers set on the command
+ if ($headers = $this[self::HEADERS_OPTION]) {
+ foreach ($headers as $key => $value) {
+ $this->request->setHeader($key, $value);
+ }
+ }
+
+ // Add any curl options to the request
+ if ($options = $this[Client::CURL_OPTIONS]) {
+ $this->request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($options));
+ }
+
+ // Set a custom response body
+ if ($responseBody = $this[self::RESPONSE_BODY]) {
+ $this->request->setResponseBody($responseBody);
+ }
+
+ $this->client->dispatch('command.after_prepare', array('command' => $this));
+ }
+
+ return $this->request;
+ }
+
+ /**
+ * Set the validator used to validate and prepare command parameters and nested JSON schemas. If no validator is
+ * set, then the command will validate using the default {@see SchemaValidator}.
+ *
+ * @param ValidatorInterface $validator Validator used to prepare and validate properties against a JSON schema
+ *
+ * @return self
+ */
+ public function setValidator(ValidatorInterface $validator)
+ {
+ $this->validator = $validator;
+
+ return $this;
+ }
+
+ public function getRequestHeaders()
+ {
+ return $this[self::HEADERS_OPTION];
+ }
+
+ /**
+ * Initialize the command (hook that can be implemented in subclasses)
+ */
+ protected function init() {}
+
+ /**
+ * Create the request object that will carry out the command
+ */
+ abstract protected function build();
+
+ /**
+ * Hook used to create an operation for concrete commands that are not associated with a service description
+ *
+ * @return OperationInterface
+ */
+ protected function createOperation()
+ {
+ return new Operation(array('name' => get_class($this)));
+ }
+
+ /**
+ * Create the result of the command after the request has been completed.
+ * Override this method in subclasses to customize this behavior
+ */
+ protected function process()
+ {
+ $this->result = $this[self::RESPONSE_PROCESSING] != self::TYPE_RAW
+ ? DefaultResponseParser::getInstance()->parse($this)
+ : $this->request->getResponse();
+ }
+
+ /**
+ * Validate and prepare the command based on the schema and rules defined by the command's Operation object
+ *
+ * @throws ValidationException when validation errors occur
+ */
+ protected function validate()
+ {
+ // Do not perform request validation/transformation if it is disable
+ if ($this[self::DISABLE_VALIDATION]) {
+ return;
+ }
+
+ $errors = array();
+ $validator = $this->getValidator();
+ foreach ($this->operation->getParams() as $name => $schema) {
+ $value = $this[$name];
+ if (!$validator->validate($schema, $value)) {
+ $errors = array_merge($errors, $validator->getErrors());
+ } elseif ($value !== $this[$name]) {
+ // Update the config value if it changed and no validation errors were encountered
+ $this->data[$name] = $value;
+ }
+ }
+
+ // Validate additional parameters
+ $hidden = $this[self::HIDDEN_PARAMS];
+
+ if ($properties = $this->operation->getAdditionalParameters()) {
+ foreach ($this->toArray() as $name => $value) {
+ // It's only additional if it isn't defined in the schema
+ if (!$this->operation->hasParam($name) && !in_array($name, $hidden)) {
+ // Always set the name so that error messages are useful
+ $properties->setName($name);
+ if (!$validator->validate($properties, $value)) {
+ $errors = array_merge($errors, $validator->getErrors());
+ } elseif ($value !== $this[$name]) {
+ $this->data[$name] = $value;
+ }
+ }
+ }
+ }
+
+ if (!empty($errors)) {
+ $e = new ValidationException('Validation errors: ' . implode("\n", $errors));
+ $e->setErrors($errors);
+ throw $e;
+ }
+ }
+
+ /**
+ * Get the validator used to prepare and validate properties. If no validator has been set on the command, then
+ * the default {@see SchemaValidator} will be used.
+ *
+ * @return ValidatorInterface
+ */
+ protected function getValidator()
+ {
+ if (!$this->validator) {
+ $this->validator = SchemaValidator::getInstance();
+ }
+
+ return $this->validator;
+ }
+
+ /**
+ * Get array of any validation errors
+ * If no validator has been set then return false
+ */
+ public function getValidationErrors()
+ {
+ if (!$this->validator) {
+ return false;
+ }
+
+ return $this->validator->getErrors();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php
new file mode 100644
index 0000000..cb6ac40
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\UnexpectedValueException;
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * A ClosureCommand is a command that allows dynamic commands to be created at runtime using a closure to prepare the
+ * request. A closure key and \Closure value must be passed to the command in the constructor. The closure must
+ * accept the command object as an argument.
+ */
+class ClosureCommand extends AbstractCommand
+{
+ /**
+ * {@inheritdoc}
+ * @throws InvalidArgumentException if a closure was not passed
+ */
+ protected function init()
+ {
+ if (!$this['closure']) {
+ throw new InvalidArgumentException('A closure must be passed in the parameters array');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws UnexpectedValueException If the closure does not return a request
+ */
+ protected function build()
+ {
+ $closure = $this['closure'];
+ /** @var $closure \Closure */
+ $this->request = $closure($this, $this->operation);
+
+ if (!$this->request || !$this->request instanceof RequestInterface) {
+ throw new UnexpectedValueException('Closure command did not return a RequestInterface object');
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php
new file mode 100644
index 0000000..fbb61d2
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Exception\CommandException;
+use Guzzle\Service\Description\OperationInterface;
+use Guzzle\Service\ClientInterface;
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * A command object that contains parameters that can be modified and accessed like an array and turned into an array
+ */
+interface CommandInterface extends \ArrayAccess, ToArrayInterface
+{
+ /**
+ * Get the short form name of the command
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Get the API operation information about the command
+ *
+ * @return OperationInterface
+ */
+ public function getOperation();
+
+ /**
+ * Execute the command and return the result
+ *
+ * @return mixed Returns the result of {@see CommandInterface::execute}
+ * @throws CommandException if a client has not been associated with the command
+ */
+ public function execute();
+
+ /**
+ * Get the client object that will execute the command
+ *
+ * @return ClientInterface|null
+ */
+ public function getClient();
+
+ /**
+ * Set the client object that will execute the command
+ *
+ * @param ClientInterface $client The client object that will execute the command
+ *
+ * @return self
+ */
+ public function setClient(ClientInterface $client);
+
+ /**
+ * Get the request object associated with the command
+ *
+ * @return RequestInterface
+ * @throws CommandException if the command has not been executed
+ */
+ public function getRequest();
+
+ /**
+ * Get the response object associated with the command
+ *
+ * @return Response
+ * @throws CommandException if the command has not been executed
+ */
+ public function getResponse();
+
+ /**
+ * Get the result of the command
+ *
+ * @return Response By default, commands return a Response object unless overridden in a subclass
+ * @throws CommandException if the command has not been executed
+ */
+ public function getResult();
+
+ /**
+ * Set the result of the command
+ *
+ * @param mixed $result Result to set
+ *
+ * @return self
+ */
+ public function setResult($result);
+
+ /**
+ * Returns TRUE if the command has been prepared for executing
+ *
+ * @return bool
+ */
+ public function isPrepared();
+
+ /**
+ * Returns TRUE if the command has been executed
+ *
+ * @return bool
+ */
+ public function isExecuted();
+
+ /**
+ * Prepare the command for executing and create a request object.
+ *
+ * @return RequestInterface Returns the generated request
+ * @throws CommandException if a client object has not been set previously or in the prepare()
+ */
+ public function prepare();
+
+ /**
+ * Get the object that manages the request headers that will be set on any outbound requests from the command
+ *
+ * @return Collection
+ */
+ public function getRequestHeaders();
+
+ /**
+ * Specify a callable to execute when the command completes
+ *
+ * @param mixed $callable Callable to execute when the command completes. The callable must accept a
+ * {@see CommandInterface} object as the only argument.
+ * @return self
+ * @throws InvalidArgumentException
+ */
+ public function setOnComplete($callable);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CreateResponseClassEvent.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CreateResponseClassEvent.php
new file mode 100644
index 0000000..e050678
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CreateResponseClassEvent.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Common\Event;
+
+/**
+ * Event class emitted with the operation.parse_class event
+ */
+class CreateResponseClassEvent extends Event
+{
+ /**
+ * Set the result of the object creation
+ *
+ * @param mixed $result Result value to set
+ */
+ public function setResult($result)
+ {
+ $this['result'] = $result;
+ $this->stopPropagation();
+ }
+
+ /**
+ * Get the created object
+ *
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this['result'];
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php
new file mode 100644
index 0000000..2dc4acd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php
@@ -0,0 +1,169 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\LocationVisitor\Request\RequestVisitorInterface;
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+use Guzzle\Service\Description\OperationInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Default request serializer that transforms command options and operation parameters into a request
+ */
+class DefaultRequestSerializer implements RequestSerializerInterface
+{
+ /** @var VisitorFlyweight $factory Visitor factory */
+ protected $factory;
+
+ /** @var self */
+ protected static $instance;
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self(VisitorFlyweight::getInstance());
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @param VisitorFlyweight $factory Factory to use when creating visitors
+ */
+ public function __construct(VisitorFlyweight $factory)
+ {
+ $this->factory = $factory;
+ }
+
+ /**
+ * Add a location visitor to the serializer
+ *
+ * @param string $location Location to associate with the visitor
+ * @param RequestVisitorInterface $visitor Visitor to attach
+ *
+ * @return self
+ */
+ public function addVisitor($location, RequestVisitorInterface $visitor)
+ {
+ $this->factory->addRequestVisitor($location, $visitor);
+
+ return $this;
+ }
+
+ public function prepare(CommandInterface $command)
+ {
+ $request = $this->createRequest($command);
+ // Keep an array of visitors found in the operation
+ $foundVisitors = array();
+ $operation = $command->getOperation();
+
+ // Add arguments to the request using the location attribute
+ foreach ($operation->getParams() as $name => $arg) {
+ /** @var $arg \Guzzle\Service\Description\Parameter */
+ $location = $arg->getLocation();
+ // Skip 'uri' locations because they've already been processed
+ if ($location && $location != 'uri') {
+ // Instantiate visitors as they are detected in the properties
+ if (!isset($foundVisitors[$location])) {
+ $foundVisitors[$location] = $this->factory->getRequestVisitor($location);
+ }
+ // Ensure that a value has been set for this parameter
+ $value = $command[$name];
+ if ($value !== null) {
+ // Apply the parameter value with the location visitor
+ $foundVisitors[$location]->visit($command, $request, $arg, $value);
+ }
+ }
+ }
+
+ // Serialize additional parameters
+ if ($additional = $operation->getAdditionalParameters()) {
+ if ($visitor = $this->prepareAdditionalParameters($operation, $command, $request, $additional)) {
+ $foundVisitors[$additional->getLocation()] = $visitor;
+ }
+ }
+
+ // Call the after method on each visitor found in the operation
+ foreach ($foundVisitors as $visitor) {
+ $visitor->after($command, $request);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Serialize additional parameters
+ *
+ * @param OperationInterface $operation Operation that owns the command
+ * @param CommandInterface $command Command to prepare
+ * @param RequestInterface $request Request to serialize
+ * @param Parameter $additional Additional parameters
+ *
+ * @return null|RequestVisitorInterface
+ */
+ protected function prepareAdditionalParameters(
+ OperationInterface $operation,
+ CommandInterface $command,
+ RequestInterface $request,
+ Parameter $additional
+ ) {
+ if (!($location = $additional->getLocation())) {
+ return;
+ }
+
+ $visitor = $this->factory->getRequestVisitor($location);
+ $hidden = $command[$command::HIDDEN_PARAMS];
+
+ foreach ($command->toArray() as $key => $value) {
+ // Ignore values that are null or built-in command options
+ if ($value !== null
+ && !in_array($key, $hidden)
+ && !$operation->hasParam($key)
+ ) {
+ $additional->setName($key);
+ $visitor->visit($command, $request, $additional, $value);
+ }
+ }
+
+ return $visitor;
+ }
+
+ /**
+ * Create a request for the command and operation
+ *
+ * @param CommandInterface $command Command to create a request for
+ *
+ * @return RequestInterface
+ */
+ protected function createRequest(CommandInterface $command)
+ {
+ $operation = $command->getOperation();
+ $client = $command->getClient();
+ $options = $command[AbstractCommand::REQUEST_OPTIONS] ?: array();
+
+ // If the command does not specify a template, then assume the base URL of the client
+ if (!($uri = $operation->getUri())) {
+ return $client->createRequest($operation->getHttpMethod(), $client->getBaseUrl(), null, null, $options);
+ }
+
+ // Get the path values and use the client config settings
+ $variables = array();
+ foreach ($operation->getParams() as $name => $arg) {
+ if ($arg->getLocation() == 'uri') {
+ if (isset($command[$name])) {
+ $variables[$name] = $arg->filter($command[$name]);
+ if (!is_array($variables[$name])) {
+ $variables[$name] = (string) $variables[$name];
+ }
+ }
+ }
+ }
+
+ return $client->createRequest($operation->getHttpMethod(), array($uri, $variables), null, null, $options);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php
new file mode 100644
index 0000000..4fe3803
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Http\Message\Response;
+
+/**
+ * Default HTTP response parser used to marshal JSON responses into arrays and XML responses into SimpleXMLElement
+ */
+class DefaultResponseParser implements ResponseParserInterface
+{
+ /** @var self */
+ protected static $instance;
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self;
+ }
+
+ return self::$instance;
+ }
+
+ public function parse(CommandInterface $command)
+ {
+ $response = $command->getRequest()->getResponse();
+
+ // Account for hard coded content-type values specified in service descriptions
+ if ($contentType = $command['command.expects']) {
+ $response->setHeader('Content-Type', $contentType);
+ } else {
+ $contentType = (string) $response->getHeader('Content-Type');
+ }
+
+ return $this->handleParsing($command, $response, $contentType);
+ }
+
+ protected function handleParsing(CommandInterface $command, Response $response, $contentType)
+ {
+ $result = $response;
+ if ($result->getBody()) {
+ if (stripos($contentType, 'json') !== false) {
+ $result = $result->json();
+ } elseif (stripos($contentType, 'xml') !== false) {
+ $result = $result->xml();
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php
new file mode 100644
index 0000000..1c5ce07
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Service\ClientInterface;
+
+/**
+ * Command factory used when you need to provide aliases to commands
+ */
+class AliasFactory implements FactoryInterface
+{
+ /** @var array Associative array mapping command aliases to the aliased command */
+ protected $aliases;
+
+ /** @var ClientInterface Client used to retry using aliases */
+ protected $client;
+
+ /**
+ * @param ClientInterface $client Client used to retry with the alias
+ * @param array $aliases Associative array mapping aliases to the alias
+ */
+ public function __construct(ClientInterface $client, array $aliases)
+ {
+ $this->client = $client;
+ $this->aliases = $aliases;
+ }
+
+ public function factory($name, array $args = array())
+ {
+ if (isset($this->aliases[$name])) {
+ try {
+ return $this->client->getCommand($this->aliases[$name], $args);
+ } catch (InvalidArgumentException $e) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php
new file mode 100644
index 0000000..8c46983
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\ClientInterface;
+
+/**
+ * Composite factory used by a client object to create command objects utilizing multiple factories
+ */
+class CompositeFactory implements \IteratorAggregate, \Countable, FactoryInterface
+{
+ /** @var array Array of command factories */
+ protected $factories;
+
+ /**
+ * Get the default chain to use with clients
+ *
+ * @param ClientInterface $client Client to base the chain on
+ *
+ * @return self
+ */
+ public static function getDefaultChain(ClientInterface $client)
+ {
+ $factories = array();
+ if ($description = $client->getDescription()) {
+ $factories[] = new ServiceDescriptionFactory($description);
+ }
+ $factories[] = new ConcreteClassFactory($client);
+
+ return new self($factories);
+ }
+
+ /**
+ * @param array $factories Array of command factories
+ */
+ public function __construct(array $factories = array())
+ {
+ $this->factories = $factories;
+ }
+
+ /**
+ * Add a command factory to the chain
+ *
+ * @param FactoryInterface $factory Factory to add
+ * @param string|FactoryInterface $before Insert the new command factory before a command factory class or object
+ * matching a class name.
+ * @return CompositeFactory
+ */
+ public function add(FactoryInterface $factory, $before = null)
+ {
+ $pos = null;
+
+ if ($before) {
+ foreach ($this->factories as $i => $f) {
+ if ($before instanceof FactoryInterface) {
+ if ($f === $before) {
+ $pos = $i;
+ break;
+ }
+ } elseif (is_string($before)) {
+ if ($f instanceof $before) {
+ $pos = $i;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($pos === null) {
+ $this->factories[] = $factory;
+ } else {
+ array_splice($this->factories, $i, 0, array($factory));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Check if the chain contains a specific command factory
+ *
+ * @param FactoryInterface|string $factory Factory to check
+ *
+ * @return bool
+ */
+ public function has($factory)
+ {
+ return (bool) $this->find($factory);
+ }
+
+ /**
+ * Remove a specific command factory from the chain
+ *
+ * @param string|FactoryInterface $factory Factory to remove by name or instance
+ *
+ * @return CompositeFactory
+ */
+ public function remove($factory = null)
+ {
+ if (!($factory instanceof FactoryInterface)) {
+ $factory = $this->find($factory);
+ }
+
+ $this->factories = array_values(array_filter($this->factories, function($f) use ($factory) {
+ return $f !== $factory;
+ }));
+
+ return $this;
+ }
+
+ /**
+ * Get a command factory by class name
+ *
+ * @param string|FactoryInterface $factory Command factory class or instance
+ *
+ * @return null|FactoryInterface
+ */
+ public function find($factory)
+ {
+ foreach ($this->factories as $f) {
+ if ($factory === $f || (is_string($factory) && $f instanceof $factory)) {
+ return $f;
+ }
+ }
+ }
+
+ /**
+ * Create a command using the associated command factories
+ *
+ * @param string $name Name of the command
+ * @param array $args Command arguments
+ *
+ * @return CommandInterface
+ */
+ public function factory($name, array $args = array())
+ {
+ foreach ($this->factories as $factory) {
+ $command = $factory->factory($name, $args);
+ if ($command) {
+ return $command;
+ }
+ }
+ }
+
+ public function count()
+ {
+ return count($this->factories);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->factories);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php
new file mode 100644
index 0000000..0e93dea
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+use Guzzle\Inflection\InflectorInterface;
+use Guzzle\Inflection\Inflector;
+use Guzzle\Service\ClientInterface;
+
+/**
+ * Command factory used to create commands referencing concrete command classes
+ */
+class ConcreteClassFactory implements FactoryInterface
+{
+ /** @var ClientInterface */
+ protected $client;
+
+ /** @var InflectorInterface */
+ protected $inflector;
+
+ /**
+ * @param ClientInterface $client Client that owns the commands
+ * @param InflectorInterface $inflector Inflector used to resolve class names
+ */
+ public function __construct(ClientInterface $client, InflectorInterface $inflector = null)
+ {
+ $this->client = $client;
+ $this->inflector = $inflector ?: Inflector::getDefault();
+ }
+
+ public function factory($name, array $args = array())
+ {
+ // Determine the class to instantiate based on the namespace of the current client and the default directory
+ $prefix = $this->client->getConfig('command.prefix');
+ if (!$prefix) {
+ // The prefix can be specified in a factory method and is cached
+ $prefix = implode('\\', array_slice(explode('\\', get_class($this->client)), 0, -1)) . '\\Command\\';
+ $this->client->getConfig()->set('command.prefix', $prefix);
+ }
+
+ $class = $prefix . str_replace(' ', '\\', ucwords(str_replace('.', ' ', $this->inflector->camel($name))));
+
+ // Create the concrete command if it exists
+ if (class_exists($class)) {
+ return new $class($args);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php
new file mode 100644
index 0000000..35c299d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Interface for creating commands by name
+ */
+interface FactoryInterface
+{
+ /**
+ * Create a command by name
+ *
+ * @param string $name Command to create
+ * @param array $args Command arguments
+ *
+ * @return CommandInterface|null
+ */
+ public function factory($name, array $args = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/MapFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/MapFactory.php
new file mode 100644
index 0000000..0ad80bc
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/MapFactory.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+/**
+ * Command factory used when explicitly mapping strings to command classes
+ */
+class MapFactory implements FactoryInterface
+{
+ /** @var array Associative array mapping command names to classes */
+ protected $map;
+
+ /** @param array $map Associative array mapping command names to classes */
+ public function __construct(array $map)
+ {
+ $this->map = $map;
+ }
+
+ public function factory($name, array $args = array())
+ {
+ if (isset($this->map[$name])) {
+ $class = $this->map[$name];
+
+ return new $class($args);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php
new file mode 100644
index 0000000..b943a5b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Guzzle\Service\Command\Factory;
+
+use Guzzle\Service\Description\ServiceDescriptionInterface;
+use Guzzle\Inflection\InflectorInterface;
+
+/**
+ * Command factory used to create commands based on service descriptions
+ */
+class ServiceDescriptionFactory implements FactoryInterface
+{
+ /** @var ServiceDescriptionInterface */
+ protected $description;
+
+ /** @var InflectorInterface */
+ protected $inflector;
+
+ /**
+ * @param ServiceDescriptionInterface $description Service description
+ * @param InflectorInterface $inflector Optional inflector to use if the command is not at first found
+ */
+ public function __construct(ServiceDescriptionInterface $description, InflectorInterface $inflector = null)
+ {
+ $this->setServiceDescription($description);
+ $this->inflector = $inflector;
+ }
+
+ /**
+ * Change the service description used with the factory
+ *
+ * @param ServiceDescriptionInterface $description Service description to use
+ *
+ * @return FactoryInterface
+ */
+ public function setServiceDescription(ServiceDescriptionInterface $description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Returns the service description
+ *
+ * @return ServiceDescriptionInterface
+ */
+ public function getServiceDescription()
+ {
+ return $this->description;
+ }
+
+ public function factory($name, array $args = array())
+ {
+ $command = $this->description->getOperation($name);
+
+ // If a command wasn't found, then try to uppercase the first letter and try again
+ if (!$command) {
+ $command = $this->description->getOperation(ucfirst($name));
+ // If an inflector was passed, then attempt to get the command using snake_case inflection
+ if (!$command && $this->inflector) {
+ $command = $this->description->getOperation($this->inflector->snake($name));
+ }
+ }
+
+ if ($command) {
+ $class = $command->getClass();
+ return new $class($args, $command, $this->description);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php
new file mode 100644
index 0000000..adcfca1
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Description\Parameter;
+
+abstract class AbstractRequestVisitor implements RequestVisitorInterface
+{
+ /**
+ * @codeCoverageIgnore
+ */
+ public function after(CommandInterface $command, RequestInterface $request) {}
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) {}
+
+ /**
+ * Prepare (filter and set desired name for request item) the value for request.
+ *
+ * @param mixed $value
+ * @param \Guzzle\Service\Description\Parameter $param
+ *
+ * @return array|mixed
+ */
+ protected function prepareValue($value, Parameter $param)
+ {
+ return is_array($value)
+ ? $this->resolveRecursively($value, $param)
+ : $param->filter($value);
+ }
+
+ /**
+ * Map nested parameters into the location_key based parameters
+ *
+ * @param array $value Value to map
+ * @param Parameter $param Parameter that holds information about the current key
+ *
+ * @return array Returns the mapped array
+ */
+ protected function resolveRecursively(array $value, Parameter $param)
+ {
+ foreach ($value as $name => &$v) {
+ switch ($param->getType()) {
+ case 'object':
+ if ($subParam = $param->getProperty($name)) {
+ $key = $subParam->getWireName();
+ $value[$key] = $this->prepareValue($v, $subParam);
+ if ($name != $key) {
+ unset($value[$name]);
+ }
+ } elseif ($param->getAdditionalProperties() instanceof Parameter) {
+ $v = $this->prepareValue($v, $param->getAdditionalProperties());
+ }
+ break;
+ case 'array':
+ if ($items = $param->getItems()) {
+ $v = $this->prepareValue($v, $items);
+ }
+ break;
+ }
+ }
+
+ return $param->filter($value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php
new file mode 100644
index 0000000..168d780
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a body to a request
+ *
+ * This visitor can use a data parameter of 'expect' to control the Expect header. Set the expect data parameter to
+ * false to disable the expect header, or set the value to an integer so that the expect 100-continue header is only
+ * added if the Content-Length of the entity body is greater than the value.
+ */
+class BodyVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $value = $param->filter($value);
+ $entityBody = EntityBody::factory($value);
+ $request->setBody($entityBody);
+ $this->addExpectHeader($request, $entityBody, $param->getData('expect_header'));
+ // Add the Content-Encoding header if one is set on the EntityBody
+ if ($encoding = $entityBody->getContentEncoding()) {
+ $request->setHeader('Content-Encoding', $encoding);
+ }
+ }
+
+ /**
+ * Add the appropriate expect header to a request
+ *
+ * @param EntityEnclosingRequestInterface $request Request to update
+ * @param EntityBodyInterface $body Entity body of the request
+ * @param string|int $expect Expect header setting
+ */
+ protected function addExpectHeader(EntityEnclosingRequestInterface $request, EntityBodyInterface $body, $expect)
+ {
+ // Allow the `expect` data parameter to be set to remove the Expect header from the request
+ if ($expect === false) {
+ $request->removeHeader('Expect');
+ } elseif ($expect !== true) {
+ // Default to using a MB as the point in which to start using the expect header
+ $expect = $expect ?: 1048576;
+ // If the expect_header value is numeric then only add if the size is greater than the cutoff
+ if (is_numeric($expect) && $body->getSize()) {
+ if ($body->getSize() < $expect) {
+ $request->removeHeader('Expect');
+ } else {
+ $request->setHeader('Expect', '100-Continue');
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php
new file mode 100644
index 0000000..2a53754
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a parameter to a header value
+ */
+class HeaderVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $value = $param->filter($value);
+ if ($param->getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) {
+ $this->addPrefixedHeaders($request, $param, $value);
+ } else {
+ $request->setHeader($param->getWireName(), $value);
+ }
+ }
+
+ /**
+ * Add a prefixed array of headers to the request
+ *
+ * @param RequestInterface $request Request to update
+ * @param Parameter $param Parameter object
+ * @param array $value Header array to add
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function addPrefixedHeaders(RequestInterface $request, Parameter $param, $value)
+ {
+ if (!is_array($value)) {
+ throw new InvalidArgumentException('An array of mapped headers expected, but received a single value');
+ }
+ $prefix = $param->getSentAs();
+ foreach ($value as $headerName => $headerValue) {
+ $request->setHeader($prefix . $headerName, $headerValue);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php
new file mode 100644
index 0000000..757e1c5
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a parameter to an array that will be serialized as a top level key-value pair in a JSON body
+ */
+class JsonVisitor extends AbstractRequestVisitor
+{
+ /** @var bool Whether or not to add a Content-Type header when JSON is found */
+ protected $jsonContentType = 'application/json';
+
+ /** @var \SplObjectStorage Data object for persisting JSON data */
+ protected $data;
+
+ public function __construct()
+ {
+ $this->data = new \SplObjectStorage();
+ }
+
+ /**
+ * Set the Content-Type header to add to the request if JSON is added to the body. This visitor does not add a
+ * Content-Type header unless you specify one here.
+ *
+ * @param string $header Header to set when JSON is added (e.g. application/json)
+ *
+ * @return self
+ */
+ public function setContentTypeHeader($header = 'application/json')
+ {
+ $this->jsonContentType = $header;
+
+ return $this;
+ }
+
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ if (isset($this->data[$command])) {
+ $json = $this->data[$command];
+ } else {
+ $json = array();
+ }
+ $json[$param->getWireName()] = $this->prepareValue($value, $param);
+ $this->data[$command] = $json;
+ }
+
+ public function after(CommandInterface $command, RequestInterface $request)
+ {
+ if (isset($this->data[$command])) {
+ // Don't overwrite the Content-Type if one is set
+ if ($this->jsonContentType && !$request->hasHeader('Content-Type')) {
+ $request->setHeader('Content-Type', $this->jsonContentType);
+ }
+
+ $request->setBody(json_encode($this->data[$command]));
+ unset($this->data[$command]);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php
new file mode 100644
index 0000000..975850b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a parameter to a POST field
+ */
+class PostFieldVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $request->setPostField($param->getWireName(), $this->prepareValue($value, $param));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php
new file mode 100644
index 0000000..0853ebe
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\PostFileInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a parameter to a POST file
+ */
+class PostFileVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $value = $param->filter($value);
+ if ($value instanceof PostFileInterface) {
+ $request->addPostFile($value);
+ } else {
+ $request->addPostFile($param->getWireName(), $value);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php
new file mode 100644
index 0000000..315877a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to apply a parameter to a request's query string
+ */
+class QueryVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $request->getQuery()->set($param->getWireName(), $this->prepareValue($value, $param));
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php
new file mode 100644
index 0000000..14e0b2d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to add values to different locations in a request with different behaviors as needed
+ */
+interface RequestVisitorInterface
+{
+ /**
+ * Called after visiting all parameters
+ *
+ * @param CommandInterface $command Command being visited
+ * @param RequestInterface $request Request being visited
+ */
+ public function after(CommandInterface $command, RequestInterface $request);
+
+ /**
+ * Called once for each parameter being visited that matches the location type
+ *
+ * @param CommandInterface $command Command being visited
+ * @param RequestInterface $request Request being visited
+ * @param Parameter $param Parameter being visited
+ * @param mixed $value Value to set
+ */
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/ResponseBodyVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/ResponseBodyVisitor.php
new file mode 100644
index 0000000..09f35f8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/ResponseBodyVisitor.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to change the location in which a response body is saved
+ */
+class ResponseBodyVisitor extends AbstractRequestVisitor
+{
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $request->setResponseBody($value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php
new file mode 100644
index 0000000..5b71487
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php
@@ -0,0 +1,252 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Location visitor used to serialize XML bodies
+ */
+class XmlVisitor extends AbstractRequestVisitor
+{
+ /** @var \SplObjectStorage Data object for persisting XML data */
+ protected $data;
+
+ /** @var bool Content-Type header added when XML is found */
+ protected $contentType = 'application/xml';
+
+ public function __construct()
+ {
+ $this->data = new \SplObjectStorage();
+ }
+
+ /**
+ * Change the content-type header that is added when XML is found
+ *
+ * @param string $header Header to set when XML is found
+ *
+ * @return self
+ */
+ public function setContentTypeHeader($header)
+ {
+ $this->contentType = $header;
+
+ return $this;
+ }
+
+ public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
+ {
+ $xml = isset($this->data[$command])
+ ? $this->data[$command]
+ : $this->createRootElement($param->getParent());
+ $this->addXml($xml, $param, $value);
+
+ $this->data[$command] = $xml;
+ }
+
+ public function after(CommandInterface $command, RequestInterface $request)
+ {
+ $xml = null;
+
+ // If data was found that needs to be serialized, then do so
+ if (isset($this->data[$command])) {
+ $xml = $this->finishDocument($this->data[$command]);
+ unset($this->data[$command]);
+ } else {
+ // Check if XML should always be sent for the command
+ $operation = $command->getOperation();
+ if ($operation->getData('xmlAllowEmpty')) {
+ $xmlWriter = $this->createRootElement($operation);
+ $xml = $this->finishDocument($xmlWriter);
+ }
+ }
+
+ if ($xml) {
+ // Don't overwrite the Content-Type if one is set
+ if ($this->contentType && !$request->hasHeader('Content-Type')) {
+ $request->setHeader('Content-Type', $this->contentType);
+ }
+ $request->setBody($xml);
+ }
+ }
+
+ /**
+ * Create the root XML element to use with a request
+ *
+ * @param Operation $operation Operation object
+ *
+ * @return \XMLWriter
+ */
+ protected function createRootElement(Operation $operation)
+ {
+ static $defaultRoot = array('name' => 'Request');
+ // If no root element was specified, then just wrap the XML in 'Request'
+ $root = $operation->getData('xmlRoot') ?: $defaultRoot;
+ // Allow the XML declaration to be customized with xmlEncoding
+ $encoding = $operation->getData('xmlEncoding');
+
+ $xmlWriter = $this->startDocument($encoding);
+
+ $xmlWriter->startElement($root['name']);
+ // Create the wrapping element with no namespaces if no namespaces were present
+ if (!empty($root['namespaces'])) {
+ // Create the wrapping element with an array of one or more namespaces
+ foreach ((array) $root['namespaces'] as $prefix => $uri) {
+ $nsLabel = 'xmlns';
+ if (!is_numeric($prefix)) {
+ $nsLabel .= ':'.$prefix;
+ }
+ $xmlWriter->writeAttribute($nsLabel, $uri);
+ }
+ }
+ return $xmlWriter;
+ }
+
+ /**
+ * Recursively build the XML body
+ *
+ * @param \XMLWriter $xmlWriter XML to modify
+ * @param Parameter $param API Parameter
+ * @param mixed $value Value to add
+ */
+ protected function addXml(\XMLWriter $xmlWriter, Parameter $param, $value)
+ {
+ if ($value === null) {
+ return;
+ }
+
+ $value = $param->filter($value);
+ $type = $param->getType();
+ $name = $param->getWireName();
+ $prefix = null;
+ $namespace = $param->getData('xmlNamespace');
+ if (false !== strpos($name, ':')) {
+ list($prefix, $name) = explode(':', $name, 2);
+ }
+
+ if ($type == 'object' || $type == 'array') {
+ if (!$param->getData('xmlFlattened')) {
+ $xmlWriter->startElementNS(null, $name, $namespace);
+ }
+ if ($param->getType() == 'array') {
+ $this->addXmlArray($xmlWriter, $param, $value);
+ } elseif ($param->getType() == 'object') {
+ $this->addXmlObject($xmlWriter, $param, $value);
+ }
+ if (!$param->getData('xmlFlattened')) {
+ $xmlWriter->endElement();
+ }
+ return;
+ }
+ if ($param->getData('xmlAttribute')) {
+ $this->writeAttribute($xmlWriter, $prefix, $name, $namespace, $value);
+ } else {
+ $this->writeElement($xmlWriter, $prefix, $name, $namespace, $value);
+ }
+ }
+
+ /**
+ * Write an attribute with namespace if used
+ *
+ * @param \XMLWriter $xmlWriter XMLWriter instance
+ * @param string $prefix Namespace prefix if any
+ * @param string $name Attribute name
+ * @param string $namespace The uri of the namespace
+ * @param string $value The attribute content
+ */
+ protected function writeAttribute($xmlWriter, $prefix, $name, $namespace, $value)
+ {
+ if (empty($namespace)) {
+ $xmlWriter->writeAttribute($name, $value);
+ } else {
+ $xmlWriter->writeAttributeNS($prefix, $name, $namespace, $value);
+ }
+ }
+
+ /**
+ * Write an element with namespace if used
+ *
+ * @param \XMLWriter $xmlWriter XML writer resource
+ * @param string $prefix Namespace prefix if any
+ * @param string $name Element name
+ * @param string $namespace The uri of the namespace
+ * @param string $value The element content
+ */
+ protected function writeElement(\XMLWriter $xmlWriter, $prefix, $name, $namespace, $value)
+ {
+ $xmlWriter->startElementNS($prefix, $name, $namespace);
+ if (strpbrk($value, '<>&')) {
+ $xmlWriter->writeCData($value);
+ } else {
+ $xmlWriter->writeRaw($value);
+ }
+ $xmlWriter->endElement();
+ }
+
+ /**
+ * Create a new xml writer and start a document
+ *
+ * @param string $encoding document encoding
+ *
+ * @return \XMLWriter the writer resource
+ */
+ protected function startDocument($encoding)
+ {
+ $xmlWriter = new \XMLWriter();
+ $xmlWriter->openMemory();
+ $xmlWriter->startDocument('1.0', $encoding);
+
+ return $xmlWriter;
+ }
+
+ /**
+ * End the document and return the output
+ *
+ * @param \XMLWriter $xmlWriter
+ *
+ * @return \string the writer resource
+ */
+ protected function finishDocument($xmlWriter)
+ {
+ $xmlWriter->endDocument();
+
+ return $xmlWriter->outputMemory();
+ }
+
+ /**
+ * Add an array to the XML
+ */
+ protected function addXmlArray(\XMLWriter $xmlWriter, Parameter $param, &$value)
+ {
+ if ($items = $param->getItems()) {
+ foreach ($value as $v) {
+ $this->addXml($xmlWriter, $items, $v);
+ }
+ }
+ }
+
+ /**
+ * Add an object to the XML
+ */
+ protected function addXmlObject(\XMLWriter $xmlWriter, Parameter $param, &$value)
+ {
+ $noAttributes = array();
+ // add values which have attributes
+ foreach ($value as $name => $v) {
+ if ($property = $param->getProperty($name)) {
+ if ($property->getData('xmlAttribute')) {
+ $this->addXml($xmlWriter, $property, $v);
+ } else {
+ $noAttributes[] = array('value' => $v, 'property' => $property);
+ }
+ }
+ }
+ // now add values with no attributes
+ foreach ($noAttributes as $element) {
+ $this->addXml($xmlWriter, $element['property'], $element['value']);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php
new file mode 100644
index 0000000..d87eeb9
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * {@inheritdoc}
+ * @codeCoverageIgnore
+ */
+abstract class AbstractResponseVisitor implements ResponseVisitorInterface
+{
+ public function before(CommandInterface $command, array &$result) {}
+
+ public function after(CommandInterface $command) {}
+
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {}
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/BodyVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/BodyVisitor.php
new file mode 100644
index 0000000..f70b727
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/BodyVisitor.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Visitor used to add the body of a response to a particular key
+ */
+class BodyVisitor extends AbstractResponseVisitor
+{
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ $value[$param->getName()] = $param->filter($response->getBody());
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php
new file mode 100644
index 0000000..0f8737c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to add a particular header of a response to a key in the result
+ */
+class HeaderVisitor extends AbstractResponseVisitor
+{
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ if ($param->getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) {
+ $this->processPrefixedHeaders($response, $param, $value);
+ } else {
+ $value[$param->getName()] = $param->filter((string) $response->getHeader($param->getWireName()));
+ }
+ }
+
+ /**
+ * Process a prefixed header array
+ *
+ * @param Response $response Response that contains the headers
+ * @param Parameter $param Parameter object
+ * @param array $value Value response array to modify
+ */
+ protected function processPrefixedHeaders(Response $response, Parameter $param, &$value)
+ {
+ // Grab prefixed headers that should be placed into an array with the prefix stripped
+ if ($prefix = $param->getSentAs()) {
+ $container = $param->getName();
+ $len = strlen($prefix);
+ // Find all matching headers and place them into the containing element
+ foreach ($response->getHeaders()->toArray() as $key => $header) {
+ if (stripos($key, $prefix) === 0) {
+ // Account for multi-value headers
+ $value[$container][substr($key, $len)] = count($header) == 1 ? end($header) : $header;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php
new file mode 100644
index 0000000..a609ebd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to marshal JSON response data into a formatted array.
+ *
+ * Allows top level JSON parameters to be inserted into the result of a command. The top level attributes are grabbed
+ * from the response's JSON data using the name value by default. Filters can be applied to parameters as they are
+ * traversed. This allows data to be normalized before returning it to users (for example converting timestamps to
+ * DateTime objects).
+ */
+class JsonVisitor extends AbstractResponseVisitor
+{
+ public function before(CommandInterface $command, array &$result)
+ {
+ // Ensure that the result of the command is always rooted with the parsed JSON data
+ $result = $command->getResponse()->json();
+ }
+
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ $name = $param->getName();
+ $key = $param->getWireName();
+ if (isset($value[$key])) {
+ $this->recursiveProcess($param, $value[$key]);
+ if ($key != $name) {
+ $value[$name] = $value[$key];
+ unset($value[$key]);
+ }
+ }
+ }
+
+ /**
+ * Recursively process a parameter while applying filters
+ *
+ * @param Parameter $param API parameter being validated
+ * @param mixed $value Value to validate and process. The value may change during this process.
+ */
+ protected function recursiveProcess(Parameter $param, &$value)
+ {
+ if ($value === null) {
+ return;
+ }
+
+ if (is_array($value)) {
+ $type = $param->getType();
+ if ($type == 'array') {
+ foreach ($value as &$item) {
+ $this->recursiveProcess($param->getItems(), $item);
+ }
+ } elseif ($type == 'object' && !isset($value[0])) {
+ // On the above line, we ensure that the array is associative and not numerically indexed
+ $knownProperties = array();
+ if ($properties = $param->getProperties()) {
+ foreach ($properties as $property) {
+ $name = $property->getName();
+ $key = $property->getWireName();
+ $knownProperties[$name] = 1;
+ if (isset($value[$key])) {
+ $this->recursiveProcess($property, $value[$key]);
+ if ($key != $name) {
+ $value[$name] = $value[$key];
+ unset($value[$key]);
+ }
+ }
+ }
+ }
+
+ // Remove any unknown and potentially unsafe properties
+ if ($param->getAdditionalProperties() === false) {
+ $value = array_intersect_key($value, $knownProperties);
+ } elseif (($additional = $param->getAdditionalProperties()) !== true) {
+ // Validate and filter additional properties
+ foreach ($value as &$v) {
+ $this->recursiveProcess($additional, $v);
+ }
+ }
+ }
+ }
+
+ $value = $param->filter($value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php
new file mode 100644
index 0000000..1b10ebc
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to add the reason phrase of a response to a key in the result
+ */
+class ReasonPhraseVisitor extends AbstractResponseVisitor
+{
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ $value[$param->getName()] = $response->getReasonPhrase();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php
new file mode 100644
index 0000000..033f40c
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to parse values out of a response into an associative array
+ */
+interface ResponseVisitorInterface
+{
+ /**
+ * Called before visiting all parameters. This can be used for seeding the result of a command with default
+ * data (e.g. populating with JSON data in the response then adding to the parsed data).
+ *
+ * @param CommandInterface $command Command being visited
+ * @param array $result Result value to update if needed (e.g. parsing XML or JSON)
+ */
+ public function before(CommandInterface $command, array &$result);
+
+ /**
+ * Called after visiting all parameters
+ *
+ * @param CommandInterface $command Command being visited
+ */
+ public function after(CommandInterface $command);
+
+ /**
+ * Called once for each parameter being visited that matches the location type
+ *
+ * @param CommandInterface $command Command being visited
+ * @param Response $response Response being visited
+ * @param Parameter $param Parameter being visited
+ * @param mixed $value Result associative array value being updated by reference
+ * @param mixed $context Parsing context
+ */
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ );
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/StatusCodeVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/StatusCodeVisitor.php
new file mode 100644
index 0000000..00c5ce0
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/StatusCodeVisitor.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to add the status code of a response to a key in the result
+ */
+class StatusCodeVisitor extends AbstractResponseVisitor
+{
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ $value[$param->getName()] = $response->getStatusCode();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php
new file mode 100644
index 0000000..bb7124b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Location visitor used to marshal XML response data into a formatted array
+ */
+class XmlVisitor extends AbstractResponseVisitor
+{
+ public function before(CommandInterface $command, array &$result)
+ {
+ // Set the result of the command to the array conversion of the XML body
+ $result = json_decode(json_encode($command->getResponse()->xml()), true);
+ }
+
+ public function visit(
+ CommandInterface $command,
+ Response $response,
+ Parameter $param,
+ &$value,
+ $context = null
+ ) {
+ $sentAs = $param->getWireName();
+ $name = $param->getName();
+ if (isset($value[$sentAs])) {
+ $this->recursiveProcess($param, $value[$sentAs]);
+ if ($name != $sentAs) {
+ $value[$name] = $value[$sentAs];
+ unset($value[$sentAs]);
+ }
+ }
+ }
+
+ /**
+ * Recursively process a parameter while applying filters
+ *
+ * @param Parameter $param API parameter being processed
+ * @param mixed $value Value to validate and process. The value may change during this process.
+ */
+ protected function recursiveProcess(Parameter $param, &$value)
+ {
+ $type = $param->getType();
+
+ if (!is_array($value)) {
+ if ($type == 'array') {
+ // Cast to an array if the value was a string, but should be an array
+ $this->recursiveProcess($param->getItems(), $value);
+ $value = array($value);
+ }
+ } elseif ($type == 'object') {
+ $this->processObject($param, $value);
+ } elseif ($type == 'array') {
+ $this->processArray($param, $value);
+ } elseif ($type == 'string' && gettype($value) == 'array') {
+ $value = '';
+ }
+
+ if ($value !== null) {
+ $value = $param->filter($value);
+ }
+ }
+
+ /**
+ * Process an array
+ *
+ * @param Parameter $param API parameter being parsed
+ * @param mixed $value Value to process
+ */
+ protected function processArray(Parameter $param, &$value)
+ {
+ // Convert the node if it was meant to be an array
+ if (!isset($value[0])) {
+ // Collections fo nodes are sometimes wrapped in an additional array. For example:
+ // <Items><Item><a>1</a></Item><Item><a>2</a></Item></Items> should become:
+ // array('Items' => array(array('a' => 1), array('a' => 2))
+ // Some nodes are not wrapped. For example: <Foo><a>1</a></Foo><Foo><a>2</a></Foo>
+ // should become array('Foo' => array(array('a' => 1), array('a' => 2))
+ if ($param->getItems() && isset($value[$param->getItems()->getWireName()])) {
+ // Account for the case of a collection wrapping wrapped nodes: Items => Item[]
+ $value = $value[$param->getItems()->getWireName()];
+ // If the wrapped node only had one value, then make it an array of nodes
+ if (!isset($value[0]) || !is_array($value)) {
+ $value = array($value);
+ }
+ } elseif (!empty($value)) {
+ // Account for repeated nodes that must be an array: Foo => Baz, Foo => Baz, but only if the
+ // value is set and not empty
+ $value = array($value);
+ }
+ }
+
+ foreach ($value as &$item) {
+ $this->recursiveProcess($param->getItems(), $item);
+ }
+ }
+
+ /**
+ * Process an object
+ *
+ * @param Parameter $param API parameter being parsed
+ * @param mixed $value Value to process
+ */
+ protected function processObject(Parameter $param, &$value)
+ {
+ // Ensure that the array is associative and not numerically indexed
+ if (!isset($value[0]) && ($properties = $param->getProperties())) {
+ $knownProperties = array();
+ foreach ($properties as $property) {
+ $name = $property->getName();
+ $sentAs = $property->getWireName();
+ $knownProperties[$name] = 1;
+ if ($property->getData('xmlAttribute')) {
+ $this->processXmlAttribute($property, $value);
+ } elseif (isset($value[$sentAs])) {
+ $this->recursiveProcess($property, $value[$sentAs]);
+ if ($name != $sentAs) {
+ $value[$name] = $value[$sentAs];
+ unset($value[$sentAs]);
+ }
+ }
+ }
+
+ // Remove any unknown and potentially unsafe properties
+ if ($param->getAdditionalProperties() === false) {
+ $value = array_intersect_key($value, $knownProperties);
+ }
+ }
+ }
+
+ /**
+ * Process an XML attribute property
+ *
+ * @param Parameter $property Property to process
+ * @param array $value Value to process and update
+ */
+ protected function processXmlAttribute(Parameter $property, array &$value)
+ {
+ $sentAs = $property->getWireName();
+ if (isset($value['@attributes'][$sentAs])) {
+ $value[$property->getName()] = $value['@attributes'][$sentAs];
+ unset($value['@attributes'][$sentAs]);
+ if (empty($value['@attributes'])) {
+ unset($value['@attributes']);
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php
new file mode 100644
index 0000000..74cb628
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Guzzle\Service\Command\LocationVisitor;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Service\Command\LocationVisitor\Request\RequestVisitorInterface;
+use Guzzle\Service\Command\LocationVisitor\Response\ResponseVisitorInterface;
+
+/**
+ * Flyweight factory used to instantiate request and response visitors
+ */
+class VisitorFlyweight
+{
+ /** @var self Singleton instance of self */
+ protected static $instance;
+
+ /** @var array Default array of mappings of location names to classes */
+ protected static $defaultMappings = array(
+ 'request.body' => 'Guzzle\Service\Command\LocationVisitor\Request\BodyVisitor',
+ 'request.header' => 'Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor',
+ 'request.json' => 'Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor',
+ 'request.postField' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFieldVisitor',
+ 'request.postFile' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFileVisitor',
+ 'request.query' => 'Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor',
+ 'request.response_body' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor',
+ 'request.responseBody' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor',
+ 'request.xml' => 'Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor',
+ 'response.body' => 'Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor',
+ 'response.header' => 'Guzzle\Service\Command\LocationVisitor\Response\HeaderVisitor',
+ 'response.json' => 'Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor',
+ 'response.reasonPhrase' => 'Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor',
+ 'response.statusCode' => 'Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor',
+ 'response.xml' => 'Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor'
+ );
+
+ /** @var array Array of mappings of location names to classes */
+ protected $mappings;
+
+ /** @var array Cache of instantiated visitors */
+ protected $cache = array();
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @param array $mappings Array mapping request.name and response.name to location visitor classes. Leave null to
+ * use the default values.
+ */
+ public function __construct(array $mappings = null)
+ {
+ $this->mappings = $mappings === null ? self::$defaultMappings : $mappings;
+ }
+
+ /**
+ * Get an instance of a request visitor by location name
+ *
+ * @param string $visitor Visitor name
+ *
+ * @return RequestVisitorInterface
+ */
+ public function getRequestVisitor($visitor)
+ {
+ return $this->getKey('request.' . $visitor);
+ }
+
+ /**
+ * Get an instance of a response visitor by location name
+ *
+ * @param string $visitor Visitor name
+ *
+ * @return ResponseVisitorInterface
+ */
+ public function getResponseVisitor($visitor)
+ {
+ return $this->getKey('response.' . $visitor);
+ }
+
+ /**
+ * Add a response visitor to the factory by name
+ *
+ * @param string $name Name of the visitor
+ * @param RequestVisitorInterface $visitor Visitor to add
+ *
+ * @return self
+ */
+ public function addRequestVisitor($name, RequestVisitorInterface $visitor)
+ {
+ $this->cache['request.' . $name] = $visitor;
+
+ return $this;
+ }
+
+ /**
+ * Add a response visitor to the factory by name
+ *
+ * @param string $name Name of the visitor
+ * @param ResponseVisitorInterface $visitor Visitor to add
+ *
+ * @return self
+ */
+ public function addResponseVisitor($name, ResponseVisitorInterface $visitor)
+ {
+ $this->cache['response.' . $name] = $visitor;
+
+ return $this;
+ }
+
+ /**
+ * Get a visitor by key value name
+ *
+ * @param string $key Key name to retrieve
+ *
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ private function getKey($key)
+ {
+ if (!isset($this->cache[$key])) {
+ if (!isset($this->mappings[$key])) {
+ list($type, $name) = explode('.', $key);
+ throw new InvalidArgumentException("No {$type} visitor has been mapped for {$name}");
+ }
+ $this->cache[$key] = new $this->mappings[$key];
+ }
+
+ return $this->cache[$key];
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php
new file mode 100644
index 0000000..0748b5a
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+/**
+ * A command that creates requests based on {@see Guzzle\Service\Description\OperationInterface} objects, and if the
+ * matching operation uses a service description model in the responseClass attribute, then this command will marshal
+ * the response into an associative array based on the JSON schema of the model.
+ */
+class OperationCommand extends AbstractCommand
+{
+ /** @var RequestSerializerInterface */
+ protected $requestSerializer;
+
+ /** @var ResponseParserInterface Response parser */
+ protected $responseParser;
+
+ /**
+ * Set the response parser used with the command
+ *
+ * @param ResponseParserInterface $parser Response parser
+ *
+ * @return self
+ */
+ public function setResponseParser(ResponseParserInterface $parser)
+ {
+ $this->responseParser = $parser;
+
+ return $this;
+ }
+
+ /**
+ * Set the request serializer used with the command
+ *
+ * @param RequestSerializerInterface $serializer Request serializer
+ *
+ * @return self
+ */
+ public function setRequestSerializer(RequestSerializerInterface $serializer)
+ {
+ $this->requestSerializer = $serializer;
+
+ return $this;
+ }
+
+ /**
+ * Get the request serializer used with the command
+ *
+ * @return RequestSerializerInterface
+ */
+ public function getRequestSerializer()
+ {
+ if (!$this->requestSerializer) {
+ // Use the default request serializer if none was found
+ $this->requestSerializer = DefaultRequestSerializer::getInstance();
+ }
+
+ return $this->requestSerializer;
+ }
+
+ /**
+ * Get the response parser used for the operation
+ *
+ * @return ResponseParserInterface
+ */
+ public function getResponseParser()
+ {
+ if (!$this->responseParser) {
+ // Use the default response parser if none was found
+ $this->responseParser = OperationResponseParser::getInstance();
+ }
+
+ return $this->responseParser;
+ }
+
+ protected function build()
+ {
+ // Prepare and serialize the request
+ $this->request = $this->getRequestSerializer()->prepare($this);
+ }
+
+ protected function process()
+ {
+ // Do not process the response if 'command.response_processing' is set to 'raw'
+ $this->result = $this[self::RESPONSE_PROCESSING] == self::TYPE_RAW
+ ? $this->request->getResponse()
+ : $this->getResponseParser()->parse($this);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php
new file mode 100644
index 0000000..ca00bc0
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+use Guzzle\Service\Command\LocationVisitor\Response\ResponseVisitorInterface;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\OperationInterface;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Exception\ResponseClassException;
+use Guzzle\Service\Resource\Model;
+
+/**
+ * Response parser that attempts to marshal responses into an associative array based on models in a service description
+ */
+class OperationResponseParser extends DefaultResponseParser
+{
+ /** @var VisitorFlyweight $factory Visitor factory */
+ protected $factory;
+
+ /** @var self */
+ protected static $instance;
+
+ /** @var bool */
+ private $schemaInModels;
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!static::$instance) {
+ static::$instance = new static(VisitorFlyweight::getInstance());
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * @param VisitorFlyweight $factory Factory to use when creating visitors
+ * @param bool $schemaInModels Set to true to inject schemas into models
+ */
+ public function __construct(VisitorFlyweight $factory, $schemaInModels = false)
+ {
+ $this->factory = $factory;
+ $this->schemaInModels = $schemaInModels;
+ }
+
+ /**
+ * Add a location visitor to the command
+ *
+ * @param string $location Location to associate with the visitor
+ * @param ResponseVisitorInterface $visitor Visitor to attach
+ *
+ * @return self
+ */
+ public function addVisitor($location, ResponseVisitorInterface $visitor)
+ {
+ $this->factory->addResponseVisitor($location, $visitor);
+
+ return $this;
+ }
+
+ protected function handleParsing(CommandInterface $command, Response $response, $contentType)
+ {
+ $operation = $command->getOperation();
+ $type = $operation->getResponseType();
+ $model = null;
+
+ if ($type == OperationInterface::TYPE_MODEL) {
+ $model = $operation->getServiceDescription()->getModel($operation->getResponseClass());
+ } elseif ($type == OperationInterface::TYPE_CLASS) {
+ return $this->parseClass($command);
+ }
+
+ if (!$model) {
+ // Return basic processing if the responseType is not model or the model cannot be found
+ return parent::handleParsing($command, $response, $contentType);
+ } elseif ($command[AbstractCommand::RESPONSE_PROCESSING] != AbstractCommand::TYPE_MODEL) {
+ // Returns a model with no visiting if the command response processing is not model
+ return new Model(parent::handleParsing($command, $response, $contentType));
+ } else {
+ // Only inject the schema into the model if "schemaInModel" is true
+ return new Model($this->visitResult($model, $command, $response), $this->schemaInModels ? $model : null);
+ }
+ }
+
+ /**
+ * Parse a class object
+ *
+ * @param CommandInterface $command Command to parse into an object
+ *
+ * @return mixed
+ * @throws ResponseClassException
+ */
+ protected function parseClass(CommandInterface $command)
+ {
+ // Emit the operation.parse_class event. If a listener injects a 'result' property, then that will be the result
+ $event = new CreateResponseClassEvent(array('command' => $command));
+ $command->getClient()->getEventDispatcher()->dispatch('command.parse_response', $event);
+ if ($result = $event->getResult()) {
+ return $result;
+ }
+
+ $className = $command->getOperation()->getResponseClass();
+ if (!method_exists($className, 'fromCommand')) {
+ throw new ResponseClassException("{$className} must exist and implement a static fromCommand() method");
+ }
+
+ return $className::fromCommand($command);
+ }
+
+ /**
+ * Perform transformations on the result array
+ *
+ * @param Parameter $model Model that defines the structure
+ * @param CommandInterface $command Command that performed the operation
+ * @param Response $response Response received
+ *
+ * @return array Returns the array of result data
+ */
+ protected function visitResult(Parameter $model, CommandInterface $command, Response $response)
+ {
+ $foundVisitors = $result = $knownProps = array();
+ $props = $model->getProperties();
+
+ foreach ($props as $schema) {
+ if ($location = $schema->getLocation()) {
+ // Trigger the before method on the first found visitor of this type
+ if (!isset($foundVisitors[$location])) {
+ $foundVisitors[$location] = $this->factory->getResponseVisitor($location);
+ $foundVisitors[$location]->before($command, $result);
+ }
+ }
+ }
+
+ // Visit additional properties when it is an actual schema
+ if (($additional = $model->getAdditionalProperties()) instanceof Parameter) {
+ $this->visitAdditionalProperties($model, $command, $response, $additional, $result, $foundVisitors);
+ }
+
+ // Apply the parameter value with the location visitor
+ foreach ($props as $schema) {
+ $knownProps[$schema->getName()] = 1;
+ if ($location = $schema->getLocation()) {
+ $foundVisitors[$location]->visit($command, $response, $schema, $result);
+ }
+ }
+
+ // Remove any unknown and potentially unsafe top-level properties
+ if ($additional === false) {
+ $result = array_intersect_key($result, $knownProps);
+ }
+
+ // Call the after() method of each found visitor
+ foreach ($foundVisitors as $visitor) {
+ $visitor->after($command);
+ }
+
+ return $result;
+ }
+
+ protected function visitAdditionalProperties(
+ Parameter $model,
+ CommandInterface $command,
+ Response $response,
+ Parameter $additional,
+ &$result,
+ array &$foundVisitors
+ ) {
+ // Only visit when a location is specified
+ if ($location = $additional->getLocation()) {
+ if (!isset($foundVisitors[$location])) {
+ $foundVisitors[$location] = $this->factory->getResponseVisitor($location);
+ $foundVisitors[$location]->before($command, $result);
+ }
+ // Only traverse if an array was parsed from the before() visitors
+ if (is_array($result)) {
+ // Find each additional property
+ foreach (array_keys($result) as $key) {
+ // Check if the model actually knows this property. If so, then it is not additional
+ if (!$model->getProperty($key)) {
+ // Set the name to the key so that we can parse it with each visitor
+ $additional->setName($key);
+ $foundVisitors[$location]->visit($command, $response, $additional, $result);
+ }
+ }
+ // Reset the additionalProperties name to null
+ $additional->setName(null);
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php
new file mode 100644
index 0000000..60b9334
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Translates command options and operation parameters into a request object
+ */
+interface RequestSerializerInterface
+{
+ /**
+ * Create a request for a command
+ *
+ * @param CommandInterface $command Command that will own the request
+ *
+ * @return RequestInterface
+ */
+ public function prepare(CommandInterface $command);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseClassInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseClassInterface.php
new file mode 100644
index 0000000..325dd08
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseClassInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+/**
+ * Interface used to accept a completed OperationCommand and parse the result into a specific response type
+ */
+interface ResponseClassInterface
+{
+ /**
+ * Create a response model object from a completed command
+ *
+ * @param OperationCommand $command That serialized the request
+ *
+ * @return self
+ */
+ public static function fromCommand(OperationCommand $command);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseParserInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseParserInterface.php
new file mode 100644
index 0000000..015f0bb
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ResponseParserInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Guzzle\Service\Command;
+
+/**
+ * Parses the HTTP response of a command and sets the appropriate result on a command object
+ */
+interface ResponseParserInterface
+{
+ /**
+ * Parse the HTTP response received by the command and update the command's result contents
+ *
+ * @param CommandInterface $command Command to parse and update
+ *
+ * @return mixed Returns the result to set on the command
+ */
+ public function parse(CommandInterface $command);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/ConfigLoaderInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/ConfigLoaderInterface.php
new file mode 100644
index 0000000..304100d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/ConfigLoaderInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Guzzle\Service;
+
+/**
+ * Interface used for loading configuration data (service descriptions, service builder configs, etc)
+ *
+ * If a loaded configuration data sets includes a top level key containing an 'includes' section, then the data in the
+ * file will extend the merged result of all of the included config files.
+ */
+interface ConfigLoaderInterface
+{
+ /**
+ * Loads configuration data and returns an array of the loaded result
+ *
+ * @param mixed $config Data to load (filename or array of data)
+ * @param array $options Array of options to use when loading
+ *
+ * @return mixed
+ */
+ public function load($config, array $options = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Operation.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Operation.php
new file mode 100644
index 0000000..81a1134
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Operation.php
@@ -0,0 +1,547 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * Data object holding the information of an API command
+ */
+class Operation implements OperationInterface
+{
+ /** @var string Default command class to use when none is specified */
+ const DEFAULT_COMMAND_CLASS = 'Guzzle\\Service\\Command\\OperationCommand';
+
+ /** @var array Hashmap of properties that can be specified. Represented as a hash to speed up constructor. */
+ protected static $properties = array(
+ 'name' => true, 'httpMethod' => true, 'uri' => true, 'class' => true, 'responseClass' => true,
+ 'responseType' => true, 'responseNotes' => true, 'notes' => true, 'summary' => true, 'documentationUrl' => true,
+ 'deprecated' => true, 'data' => true, 'parameters' => true, 'additionalParameters' => true,
+ 'errorResponses' => true
+ );
+
+ /** @var array Parameters */
+ protected $parameters = array();
+
+ /** @var Parameter Additional parameters schema */
+ protected $additionalParameters;
+
+ /** @var string Name of the command */
+ protected $name;
+
+ /** @var string HTTP method */
+ protected $httpMethod;
+
+ /** @var string This is a short summary of what the operation does */
+ protected $summary;
+
+ /** @var string A longer text field to explain the behavior of the operation. */
+ protected $notes;
+
+ /** @var string Reference URL providing more information about the operation */
+ protected $documentationUrl;
+
+ /** @var string HTTP URI of the command */
+ protected $uri;
+
+ /** @var string Class of the command object */
+ protected $class;
+
+ /** @var string This is what is returned from the method */
+ protected $responseClass;
+
+ /** @var string Type information about the response */
+ protected $responseType;
+
+ /** @var string Information about the response returned by the operation */
+ protected $responseNotes;
+
+ /** @var bool Whether or not the command is deprecated */
+ protected $deprecated;
+
+ /** @var array Array of errors that could occur when running the command */
+ protected $errorResponses;
+
+ /** @var ServiceDescriptionInterface */
+ protected $description;
+
+ /** @var array Extra operation information */
+ protected $data;
+
+ /**
+ * Builds an Operation object using an array of configuration data:
+ * - name: (string) Name of the command
+ * - httpMethod: (string) HTTP method of the operation
+ * - uri: (string) URI template that can create a relative or absolute URL
+ * - class: (string) Concrete class that implements this command
+ * - parameters: (array) Associative array of parameters for the command. {@see Parameter} for information.
+ * - summary: (string) This is a short summary of what the operation does
+ * - notes: (string) A longer text field to explain the behavior of the operation.
+ * - documentationUrl: (string) Reference URL providing more information about the operation
+ * - responseClass: (string) This is what is returned from the method. Can be a primitive, PSR-0 compliant
+ * class name, or model.
+ * - responseNotes: (string) Information about the response returned by the operation
+ * - responseType: (string) One of 'primitive', 'class', 'model', or 'documentation'. If not specified, this
+ * value will be automatically inferred based on whether or not there is a model matching the
+ * name, if a matching PSR-0 compliant class name is found, or set to 'primitive' by default.
+ * - deprecated: (bool) Set to true if this is a deprecated command
+ * - errorResponses: (array) Errors that could occur when executing the command. Array of hashes, each with a
+ * 'code' (the HTTP response code), 'reason' (response reason phrase or description of the
+ * error), and 'class' (a custom exception class that would be thrown if the error is
+ * encountered).
+ * - data: (array) Any extra data that might be used to help build or serialize the operation
+ * - additionalParameters: (null|array) Parameter schema to use when an option is passed to the operation that is
+ * not in the schema
+ *
+ * @param array $config Array of configuration data
+ * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found
+ */
+ public function __construct(array $config = array(), ServiceDescriptionInterface $description = null)
+ {
+ $this->description = $description;
+
+ // Get the intersection of the available properties and properties set on the operation
+ foreach (array_intersect_key($config, self::$properties) as $key => $value) {
+ $this->{$key} = $value;
+ }
+
+ $this->class = $this->class ?: self::DEFAULT_COMMAND_CLASS;
+ $this->deprecated = (bool) $this->deprecated;
+ $this->errorResponses = $this->errorResponses ?: array();
+ $this->data = $this->data ?: array();
+
+ if (!$this->responseClass) {
+ $this->responseClass = 'array';
+ $this->responseType = 'primitive';
+ } elseif ($this->responseType) {
+ // Set the response type to perform validation
+ $this->setResponseType($this->responseType);
+ } else {
+ // A response class was set and no response type was set, so guess what the type is
+ $this->inferResponseType();
+ }
+
+ // Parameters need special handling when adding
+ if ($this->parameters) {
+ foreach ($this->parameters as $name => $param) {
+ if ($param instanceof Parameter) {
+ $param->setName($name)->setParent($this);
+ } elseif (is_array($param)) {
+ $param['name'] = $name;
+ $this->addParam(new Parameter($param, $this->description));
+ }
+ }
+ }
+
+ if ($this->additionalParameters) {
+ if ($this->additionalParameters instanceof Parameter) {
+ $this->additionalParameters->setParent($this);
+ } elseif (is_array($this->additionalParameters)) {
+ $this->setadditionalParameters(new Parameter($this->additionalParameters, $this->description));
+ }
+ }
+ }
+
+ public function toArray()
+ {
+ $result = array();
+ // Grab valid properties and filter out values that weren't set
+ foreach (array_keys(self::$properties) as $check) {
+ if ($value = $this->{$check}) {
+ $result[$check] = $value;
+ }
+ }
+ // Remove the name property
+ unset($result['name']);
+ // Parameters need to be converted to arrays
+ $result['parameters'] = array();
+ foreach ($this->parameters as $key => $param) {
+ $result['parameters'][$key] = $param->toArray();
+ }
+ // Additional parameters need to be cast to an array
+ if ($this->additionalParameters instanceof Parameter) {
+ $result['additionalParameters'] = $this->additionalParameters->toArray();
+ }
+
+ return $result;
+ }
+
+ public function getServiceDescription()
+ {
+ return $this->description;
+ }
+
+ public function setServiceDescription(ServiceDescriptionInterface $description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ public function getParams()
+ {
+ return $this->parameters;
+ }
+
+ public function getParamNames()
+ {
+ return array_keys($this->parameters);
+ }
+
+ public function hasParam($name)
+ {
+ return isset($this->parameters[$name]);
+ }
+
+ public function getParam($param)
+ {
+ return isset($this->parameters[$param]) ? $this->parameters[$param] : null;
+ }
+
+ /**
+ * Add a parameter to the command
+ *
+ * @param Parameter $param Parameter to add
+ *
+ * @return self
+ */
+ public function addParam(Parameter $param)
+ {
+ $this->parameters[$param->getName()] = $param;
+ $param->setParent($this);
+
+ return $this;
+ }
+
+ /**
+ * Remove a parameter from the command
+ *
+ * @param string $name Name of the parameter to remove
+ *
+ * @return self
+ */
+ public function removeParam($name)
+ {
+ unset($this->parameters[$name]);
+
+ return $this;
+ }
+
+ public function getHttpMethod()
+ {
+ return $this->httpMethod;
+ }
+
+ /**
+ * Set the HTTP method of the command
+ *
+ * @param string $httpMethod Method to set
+ *
+ * @return self
+ */
+ public function setHttpMethod($httpMethod)
+ {
+ $this->httpMethod = $httpMethod;
+
+ return $this;
+ }
+
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ /**
+ * Set the concrete class of the command
+ *
+ * @param string $className Concrete class name
+ *
+ * @return self
+ */
+ public function setClass($className)
+ {
+ $this->class = $className;
+
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the name of the command
+ *
+ * @param string $name Name of the command
+ *
+ * @return self
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getSummary()
+ {
+ return $this->summary;
+ }
+
+ /**
+ * Set a short summary of what the operation does
+ *
+ * @param string $summary Short summary of the operation
+ *
+ * @return self
+ */
+ public function setSummary($summary)
+ {
+ $this->summary = $summary;
+
+ return $this;
+ }
+
+ public function getNotes()
+ {
+ return $this->notes;
+ }
+
+ /**
+ * Set a longer text field to explain the behavior of the operation.
+ *
+ * @param string $notes Notes on the operation
+ *
+ * @return self
+ */
+ public function setNotes($notes)
+ {
+ $this->notes = $notes;
+
+ return $this;
+ }
+
+ public function getDocumentationUrl()
+ {
+ return $this->documentationUrl;
+ }
+
+ /**
+ * Set the URL pointing to additional documentation on the command
+ *
+ * @param string $docUrl Documentation URL
+ *
+ * @return self
+ */
+ public function setDocumentationUrl($docUrl)
+ {
+ $this->documentationUrl = $docUrl;
+
+ return $this;
+ }
+
+ public function getResponseClass()
+ {
+ return $this->responseClass;
+ }
+
+ /**
+ * Set what is returned from the method. Can be a primitive, class name, or model. For example: 'array',
+ * 'Guzzle\\Foo\\Baz', or 'MyModelName' (to reference a model by ID).
+ *
+ * @param string $responseClass Type of response
+ *
+ * @return self
+ */
+ public function setResponseClass($responseClass)
+ {
+ $this->responseClass = $responseClass;
+ $this->inferResponseType();
+
+ return $this;
+ }
+
+ public function getResponseType()
+ {
+ return $this->responseType;
+ }
+
+ /**
+ * Set qualifying information about the responseClass. One of 'primitive', 'class', 'model', or 'documentation'
+ *
+ * @param string $responseType Response type information
+ *
+ * @return self
+ * @throws InvalidArgumentException
+ */
+ public function setResponseType($responseType)
+ {
+ static $types = array(
+ self::TYPE_PRIMITIVE => true,
+ self::TYPE_CLASS => true,
+ self::TYPE_MODEL => true,
+ self::TYPE_DOCUMENTATION => true
+ );
+ if (!isset($types[$responseType])) {
+ throw new InvalidArgumentException('responseType must be one of ' . implode(', ', array_keys($types)));
+ }
+
+ $this->responseType = $responseType;
+
+ return $this;
+ }
+
+ public function getResponseNotes()
+ {
+ return $this->responseNotes;
+ }
+
+ /**
+ * Set notes about the response of the operation
+ *
+ * @param string $notes Response notes
+ *
+ * @return self
+ */
+ public function setResponseNotes($notes)
+ {
+ $this->responseNotes = $notes;
+
+ return $this;
+ }
+
+ public function getDeprecated()
+ {
+ return $this->deprecated;
+ }
+
+ /**
+ * Set whether or not the command is deprecated
+ *
+ * @param bool $isDeprecated Set to true to mark as deprecated
+ *
+ * @return self
+ */
+ public function setDeprecated($isDeprecated)
+ {
+ $this->deprecated = $isDeprecated;
+
+ return $this;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ /**
+ * Set the URI template of the command
+ *
+ * @param string $uri URI template to set
+ *
+ * @return self
+ */
+ public function setUri($uri)
+ {
+ $this->uri = $uri;
+
+ return $this;
+ }
+
+ public function getErrorResponses()
+ {
+ return $this->errorResponses;
+ }
+
+ /**
+ * Add an error to the command
+ *
+ * @param string $code HTTP response code
+ * @param string $reason HTTP response reason phrase or information about the error
+ * @param string $class Exception class associated with the error
+ *
+ * @return self
+ */
+ public function addErrorResponse($code, $reason, $class)
+ {
+ $this->errorResponses[] = array('code' => $code, 'reason' => $reason, 'class' => $class);
+
+ return $this;
+ }
+
+ /**
+ * Set all of the error responses of the operation
+ *
+ * @param array $errorResponses Hash of error name to a hash containing a code, reason, class
+ *
+ * @return self
+ */
+ public function setErrorResponses(array $errorResponses)
+ {
+ $this->errorResponses = $errorResponses;
+
+ return $this;
+ }
+
+ public function getData($name)
+ {
+ return isset($this->data[$name]) ? $this->data[$name] : null;
+ }
+
+ /**
+ * Set a particular data point on the operation
+ *
+ * @param string $name Name of the data value
+ * @param mixed $value Value to set
+ *
+ * @return self
+ */
+ public function setData($name, $value)
+ {
+ $this->data[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get the additionalParameters of the operation
+ *
+ * @return Parameter|null
+ */
+ public function getAdditionalParameters()
+ {
+ return $this->additionalParameters;
+ }
+
+ /**
+ * Set the additionalParameters of the operation
+ *
+ * @param Parameter|null $parameter Parameter to set
+ *
+ * @return self
+ */
+ public function setAdditionalParameters($parameter)
+ {
+ if ($this->additionalParameters = $parameter) {
+ $this->additionalParameters->setParent($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Infer the response type from the responseClass value
+ */
+ protected function inferResponseType()
+ {
+ static $primitives = array('array' => 1, 'boolean' => 1, 'string' => 1, 'integer' => 1, '' => 1);
+ if (isset($primitives[$this->responseClass])) {
+ $this->responseType = self::TYPE_PRIMITIVE;
+ } elseif ($this->description && $this->description->hasModel($this->responseClass)) {
+ $this->responseType = self::TYPE_MODEL;
+ } else {
+ $this->responseType = self::TYPE_CLASS;
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php
new file mode 100644
index 0000000..4de41bd
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * Interface defining data objects that hold the information of an API operation
+ */
+interface OperationInterface extends ToArrayInterface
+{
+ const TYPE_PRIMITIVE = 'primitive';
+ const TYPE_CLASS = 'class';
+ const TYPE_DOCUMENTATION = 'documentation';
+ const TYPE_MODEL = 'model';
+
+ /**
+ * Get the service description that the operation belongs to
+ *
+ * @return ServiceDescriptionInterface|null
+ */
+ public function getServiceDescription();
+
+ /**
+ * Set the service description that the operation belongs to
+ *
+ * @param ServiceDescriptionInterface $description Service description
+ *
+ * @return self
+ */
+ public function setServiceDescription(ServiceDescriptionInterface $description);
+
+ /**
+ * Get the params of the operation
+ *
+ * @return array
+ */
+ public function getParams();
+
+ /**
+ * Returns an array of parameter names
+ *
+ * @return array
+ */
+ public function getParamNames();
+
+ /**
+ * Check if the operation has a specific parameter by name
+ *
+ * @param string $name Name of the param
+ *
+ * @return bool
+ */
+ public function hasParam($name);
+
+ /**
+ * Get a single parameter of the operation
+ *
+ * @param string $param Parameter to retrieve by name
+ *
+ * @return Parameter|null
+ */
+ public function getParam($param);
+
+ /**
+ * Get the HTTP method of the operation
+ *
+ * @return string|null
+ */
+ public function getHttpMethod();
+
+ /**
+ * Get the concrete operation class that implements this operation
+ *
+ * @return string
+ */
+ public function getClass();
+
+ /**
+ * Get the name of the operation
+ *
+ * @return string|null
+ */
+ public function getName();
+
+ /**
+ * Get a short summary of what the operation does
+ *
+ * @return string|null
+ */
+ public function getSummary();
+
+ /**
+ * Get a longer text field to explain the behavior of the operation
+ *
+ * @return string|null
+ */
+ public function getNotes();
+
+ /**
+ * Get the documentation URL of the operation
+ *
+ * @return string|null
+ */
+ public function getDocumentationUrl();
+
+ /**
+ * Get what is returned from the method. Can be a primitive, class name, or model. For example, the responseClass
+ * could be 'array', which would inherently use a responseType of 'primitive'. Using a class name would set a
+ * responseType of 'class'. Specifying a model by ID will use a responseType of 'model'.
+ *
+ * @return string|null
+ */
+ public function getResponseClass();
+
+ /**
+ * Get information about how the response is unmarshalled: One of 'primitive', 'class', 'model', or 'documentation'
+ *
+ * @return string
+ */
+ public function getResponseType();
+
+ /**
+ * Get notes about the response of the operation
+ *
+ * @return string|null
+ */
+ public function getResponseNotes();
+
+ /**
+ * Get whether or not the operation is deprecated
+ *
+ * @return bool
+ */
+ public function getDeprecated();
+
+ /**
+ * Get the URI that will be merged into the generated request
+ *
+ * @return string
+ */
+ public function getUri();
+
+ /**
+ * Get the errors that could be encountered when executing the operation
+ *
+ * @return array
+ */
+ public function getErrorResponses();
+
+ /**
+ * Get extra data from the operation
+ *
+ * @param string $name Name of the data point to retrieve
+ *
+ * @return mixed|null
+ */
+ public function getData($name);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Parameter.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Parameter.php
new file mode 100644
index 0000000..9ed3c30
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/Parameter.php
@@ -0,0 +1,925 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * API parameter object used with service descriptions
+ */
+class Parameter
+{
+ protected $name;
+ protected $description;
+ protected $serviceDescription;
+ protected $type;
+ protected $required;
+ protected $enum;
+ protected $pattern;
+ protected $minimum;
+ protected $maximum;
+ protected $minLength;
+ protected $maxLength;
+ protected $minItems;
+ protected $maxItems;
+ protected $default;
+ protected $static;
+ protected $instanceOf;
+ protected $filters;
+ protected $location;
+ protected $sentAs;
+ protected $data;
+ protected $properties = array();
+ protected $additionalProperties;
+ protected $items;
+ protected $parent;
+ protected $ref;
+ protected $format;
+ protected $propertiesCache = null;
+
+ /**
+ * Create a new Parameter using an associative array of data. The array can contain the following information:
+ * - name: (string) Unique name of the parameter
+ * - type: (string|array) Type of variable (string, number, integer, boolean, object, array, numeric,
+ * null, any). Types are using for validation and determining the structure of a parameter. You
+ * can use a union type by providing an array of simple types. If one of the union types matches
+ * the provided value, then the value is valid.
+ * - instanceOf: (string) When the type is an object, you can specify the class that the object must implement
+ * - required: (bool) Whether or not the parameter is required
+ * - default: (mixed) Default value to use if no value is supplied
+ * - static: (bool) Set to true to specify that the parameter value cannot be changed from the default
+ * - description: (string) Documentation of the parameter
+ * - location: (string) The location of a request used to apply a parameter. Custom locations can be registered
+ * with a command, but the defaults are uri, query, header, body, json, xml, postField, postFile.
+ * - sentAs: (string) Specifies how the data being modeled is sent over the wire. For example, you may wish
+ * to include certain headers in a response model that have a normalized casing of FooBar, but the
+ * actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar.
+ * - filters: (array) Array of static method names to to run a parameter value through. Each value in the
+ * array must be a string containing the full class path to a static method or an array of complex
+ * filter information. You can specify static methods of classes using the full namespace class
+ * name followed by '::' (e.g. Foo\Bar::baz()). Some filters require arguments in order to properly
+ * filter a value. For complex filters, use a hash containing a 'method' key pointing to a static
+ * method, and an 'args' key containing an array of positional arguments to pass to the method.
+ * Arguments can contain keywords that are replaced when filtering a value: '@value' is replaced
+ * with the value being validated, '@api' is replaced with the Parameter object.
+ * - properties: When the type is an object, you can specify nested parameters
+ * - additionalProperties: (array) This attribute defines a schema for all properties that are not explicitly
+ * defined in an object type definition. If specified, the value MUST be a schema or a boolean. If
+ * false is provided, no additional properties are allowed beyond the properties defined in the
+ * schema. The default value is an empty schema which allows any value for additional properties.
+ * - items: This attribute defines the allowed items in an instance array, and MUST be a schema or an array
+ * of schemas. The default value is an empty schema which allows any value for items in the
+ * instance array.
+ * When this attribute value is a schema and the instance value is an array, then all the items
+ * in the array MUST be valid according to the schema.
+ * - pattern: When the type is a string, you can specify the regex pattern that a value must match
+ * - enum: When the type is a string, you can specify a list of acceptable values
+ * - minItems: (int) Minimum number of items allowed in an array
+ * - maxItems: (int) Maximum number of items allowed in an array
+ * - minLength: (int) Minimum length of a string
+ * - maxLength: (int) Maximum length of a string
+ * - minimum: (int) Minimum value of an integer
+ * - maximum: (int) Maximum value of an integer
+ * - data: (array) Any additional custom data to use when serializing, validating, etc
+ * - format: (string) Format used to coax a value into the correct format when serializing or unserializing.
+ * You may specify either an array of filters OR a format, but not both.
+ * Supported values: date-time, date, time, timestamp, date-time-http
+ * - $ref: (string) String referencing a service description model. The parameter is replaced by the
+ * schema contained in the model.
+ *
+ * @param array $data Array of data as seen in service descriptions
+ * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(array $data = array(), ServiceDescriptionInterface $description = null)
+ {
+ if ($description) {
+ if (isset($data['$ref'])) {
+ if ($model = $description->getModel($data['$ref'])) {
+ $data = $model->toArray() + $data;
+ }
+ } elseif (isset($data['extends'])) {
+ // If this parameter extends from another parameter then start with the actual data
+ // union in the parent's data (e.g. actual supersedes parent)
+ if ($extends = $description->getModel($data['extends'])) {
+ $data += $extends->toArray();
+ }
+ }
+ }
+
+ // Pull configuration data into the parameter
+ foreach ($data as $key => $value) {
+ $this->{$key} = $value;
+ }
+
+ $this->serviceDescription = $description;
+ $this->required = (bool) $this->required;
+ $this->data = (array) $this->data;
+
+ if ($this->filters) {
+ $this->setFilters((array) $this->filters);
+ }
+
+ if ($this->type == 'object' && $this->additionalProperties === null) {
+ $this->additionalProperties = true;
+ }
+ }
+
+ /**
+ * Convert the object to an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ static $checks = array('required', 'description', 'static', 'type', 'format', 'instanceOf', 'location', 'sentAs',
+ 'pattern', 'minimum', 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum',
+ 'filters');
+
+ $result = array();
+
+ // Anything that is in the `Items` attribute of an array *must* include it's name if available
+ if ($this->parent instanceof self && $this->parent->getType() == 'array' && isset($this->name)) {
+ $result['name'] = $this->name;
+ }
+
+ foreach ($checks as $c) {
+ if ($value = $this->{$c}) {
+ $result[$c] = $value;
+ }
+ }
+
+ if ($this->default !== null) {
+ $result['default'] = $this->default;
+ }
+
+ if ($this->items !== null) {
+ $result['items'] = $this->getItems()->toArray();
+ }
+
+ if ($this->additionalProperties !== null) {
+ $result['additionalProperties'] = $this->getAdditionalProperties();
+ if ($result['additionalProperties'] instanceof self) {
+ $result['additionalProperties'] = $result['additionalProperties']->toArray();
+ }
+ }
+
+ if ($this->type == 'object' && $this->properties) {
+ $result['properties'] = array();
+ foreach ($this->getProperties() as $name => $property) {
+ $result['properties'][$name] = $property->toArray();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the default or static value of the command based on a value
+ *
+ * @param string $value Value that is currently set
+ *
+ * @return mixed Returns the value, a static value if one is present, or a default value
+ */
+ public function getValue($value)
+ {
+ if ($this->static || ($this->default !== null && $value === null)) {
+ return $this->default;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Run a value through the filters OR format attribute associated with the parameter
+ *
+ * @param mixed $value Value to filter
+ *
+ * @return mixed Returns the filtered value
+ */
+ public function filter($value)
+ {
+ // Formats are applied exclusively and supersed filters
+ if ($this->format) {
+ return SchemaFormatter::format($this->format, $value);
+ }
+
+ // Convert Boolean values
+ if ($this->type == 'boolean' && !is_bool($value)) {
+ $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
+ }
+
+ // Apply filters to the value
+ if ($this->filters) {
+ foreach ($this->filters as $filter) {
+ if (is_array($filter)) {
+ // Convert complex filters that hold value place holders
+ foreach ($filter['args'] as &$data) {
+ if ($data == '@value') {
+ $data = $value;
+ } elseif ($data == '@api') {
+ $data = $this;
+ }
+ }
+ $value = call_user_func_array($filter['method'], $filter['args']);
+ } else {
+ $value = call_user_func($filter, $value);
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get the name of the parameter
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the key of the parameter, where sentAs will supersede name if it is set
+ *
+ * @return string
+ */
+ public function getWireName()
+ {
+ return $this->sentAs ?: $this->name;
+ }
+
+ /**
+ * Set the name of the parameter
+ *
+ * @param string $name Name to set
+ *
+ * @return self
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get the type(s) of the parameter
+ *
+ * @return string|array
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set the type(s) of the parameter
+ *
+ * @param string|array $type Type of parameter or array of simple types used in a union
+ *
+ * @return self
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get if the parameter is required
+ *
+ * @return bool
+ */
+ public function getRequired()
+ {
+ return $this->required;
+ }
+
+ /**
+ * Set if the parameter is required
+ *
+ * @param bool $isRequired Whether or not the parameter is required
+ *
+ * @return self
+ */
+ public function setRequired($isRequired)
+ {
+ $this->required = (bool) $isRequired;
+
+ return $this;
+ }
+
+ /**
+ * Get the default value of the parameter
+ *
+ * @return string|null
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * Set the default value of the parameter
+ *
+ * @param string|null $default Default value to set
+ *
+ * @return self
+ */
+ public function setDefault($default)
+ {
+ $this->default = $default;
+
+ return $this;
+ }
+
+ /**
+ * Get the description of the parameter
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set the description of the parameter
+ *
+ * @param string $description Description
+ *
+ * @return self
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Get the minimum acceptable value for an integer
+ *
+ * @return int|null
+ */
+ public function getMinimum()
+ {
+ return $this->minimum;
+ }
+
+ /**
+ * Set the minimum acceptable value for an integer
+ *
+ * @param int|null $min Minimum
+ *
+ * @return self
+ */
+ public function setMinimum($min)
+ {
+ $this->minimum = $min;
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum acceptable value for an integer
+ *
+ * @return int|null
+ */
+ public function getMaximum()
+ {
+ return $this->maximum;
+ }
+
+ /**
+ * Set the maximum acceptable value for an integer
+ *
+ * @param int $max Maximum
+ *
+ * @return self
+ */
+ public function setMaximum($max)
+ {
+ $this->maximum = $max;
+
+ return $this;
+ }
+
+ /**
+ * Get the minimum allowed length of a string value
+ *
+ * @return int
+ */
+ public function getMinLength()
+ {
+ return $this->minLength;
+ }
+
+ /**
+ * Set the minimum allowed length of a string value
+ *
+ * @param int|null $min Minimum
+ *
+ * @return self
+ */
+ public function setMinLength($min)
+ {
+ $this->minLength = $min;
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum allowed length of a string value
+ *
+ * @return int|null
+ */
+ public function getMaxLength()
+ {
+ return $this->maxLength;
+ }
+
+ /**
+ * Set the maximum allowed length of a string value
+ *
+ * @param int $max Maximum length
+ *
+ * @return self
+ */
+ public function setMaxLength($max)
+ {
+ $this->maxLength = $max;
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum allowed number of items in an array value
+ *
+ * @return int|null
+ */
+ public function getMaxItems()
+ {
+ return $this->maxItems;
+ }
+
+ /**
+ * Set the maximum allowed number of items in an array value
+ *
+ * @param int $max Maximum
+ *
+ * @return self
+ */
+ public function setMaxItems($max)
+ {
+ $this->maxItems = $max;
+
+ return $this;
+ }
+
+ /**
+ * Get the minimum allowed number of items in an array value
+ *
+ * @return int
+ */
+ public function getMinItems()
+ {
+ return $this->minItems;
+ }
+
+ /**
+ * Set the minimum allowed number of items in an array value
+ *
+ * @param int|null $min Minimum
+ *
+ * @return self
+ */
+ public function setMinItems($min)
+ {
+ $this->minItems = $min;
+
+ return $this;
+ }
+
+ /**
+ * Get the location of the parameter
+ *
+ * @return string|null
+ */
+ public function getLocation()
+ {
+ return $this->location;
+ }
+
+ /**
+ * Set the location of the parameter
+ *
+ * @param string|null $location Location of the parameter
+ *
+ * @return self
+ */
+ public function setLocation($location)
+ {
+ $this->location = $location;
+
+ return $this;
+ }
+
+ /**
+ * Get the sentAs attribute of the parameter that used with locations to sentAs an attribute when it is being
+ * applied to a location.
+ *
+ * @return string|null
+ */
+ public function getSentAs()
+ {
+ return $this->sentAs;
+ }
+
+ /**
+ * Set the sentAs attribute
+ *
+ * @param string|null $name Name of the value as it is sent over the wire
+ *
+ * @return self
+ */
+ public function setSentAs($name)
+ {
+ $this->sentAs = $name;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a known property from the parameter by name or a data property by name. When not specific name value
+ * is specified, all data properties will be returned.
+ *
+ * @param string|null $name Specify a particular property name to retrieve
+ *
+ * @return array|mixed|null
+ */
+ public function getData($name = null)
+ {
+ if (!$name) {
+ return $this->data;
+ }
+
+ if (isset($this->data[$name])) {
+ return $this->data[$name];
+ } elseif (isset($this->{$name})) {
+ return $this->{$name};
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the extra data properties of the parameter or set a specific extra property
+ *
+ * @param string|array|null $nameOrData The name of a specific extra to set or an array of extras to set
+ * @param mixed|null $data When setting a specific extra property, specify the data to set for it
+ *
+ * @return self
+ */
+ public function setData($nameOrData, $data = null)
+ {
+ if (is_array($nameOrData)) {
+ $this->data = $nameOrData;
+ } else {
+ $this->data[$nameOrData] = $data;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get whether or not the default value can be changed
+ *
+ * @return mixed|null
+ */
+ public function getStatic()
+ {
+ return $this->static;
+ }
+
+ /**
+ * Set to true if the default value cannot be changed
+ *
+ * @param bool $static True or false
+ *
+ * @return self
+ */
+ public function setStatic($static)
+ {
+ $this->static = (bool) $static;
+
+ return $this;
+ }
+
+ /**
+ * Get an array of filters used by the parameter
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->filters ?: array();
+ }
+
+ /**
+ * Set the array of filters used by the parameter
+ *
+ * @param array $filters Array of functions to use as filters
+ *
+ * @return self
+ */
+ public function setFilters(array $filters)
+ {
+ $this->filters = array();
+ foreach ($filters as $filter) {
+ $this->addFilter($filter);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a filter to the parameter
+ *
+ * @param string|array $filter Method to filter the value through
+ *
+ * @return self
+ * @throws InvalidArgumentException
+ */
+ public function addFilter($filter)
+ {
+ if (is_array($filter)) {
+ if (!isset($filter['method'])) {
+ throw new InvalidArgumentException('A [method] value must be specified for each complex filter');
+ }
+ }
+
+ if (!$this->filters) {
+ $this->filters = array($filter);
+ } else {
+ $this->filters[] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the parent object (an {@see OperationInterface} or {@see Parameter}
+ *
+ * @return OperationInterface|Parameter|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set the parent object of the parameter
+ *
+ * @param OperationInterface|Parameter|null $parent Parent container of the parameter
+ *
+ * @return self
+ */
+ public function setParent($parent)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Get the properties of the parameter
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ if (!$this->propertiesCache) {
+ $this->propertiesCache = array();
+ foreach (array_keys($this->properties) as $name) {
+ $this->propertiesCache[$name] = $this->getProperty($name);
+ }
+ }
+
+ return $this->propertiesCache;
+ }
+
+ /**
+ * Get a specific property from the parameter
+ *
+ * @param string $name Name of the property to retrieve
+ *
+ * @return null|Parameter
+ */
+ public function getProperty($name)
+ {
+ if (!isset($this->properties[$name])) {
+ return null;
+ }
+
+ if (!($this->properties[$name] instanceof self)) {
+ $this->properties[$name]['name'] = $name;
+ $this->properties[$name] = new static($this->properties[$name], $this->serviceDescription);
+ $this->properties[$name]->setParent($this);
+ }
+
+ return $this->properties[$name];
+ }
+
+ /**
+ * Remove a property from the parameter
+ *
+ * @param string $name Name of the property to remove
+ *
+ * @return self
+ */
+ public function removeProperty($name)
+ {
+ unset($this->properties[$name]);
+ $this->propertiesCache = null;
+
+ return $this;
+ }
+
+ /**
+ * Add a property to the parameter
+ *
+ * @param Parameter $property Properties to set
+ *
+ * @return self
+ */
+ public function addProperty(Parameter $property)
+ {
+ $this->properties[$property->getName()] = $property;
+ $property->setParent($this);
+ $this->propertiesCache = null;
+
+ return $this;
+ }
+
+ /**
+ * Get the additionalProperties value of the parameter
+ *
+ * @return bool|Parameter|null
+ */
+ public function getAdditionalProperties()
+ {
+ if (is_array($this->additionalProperties)) {
+ $this->additionalProperties = new static($this->additionalProperties, $this->serviceDescription);
+ $this->additionalProperties->setParent($this);
+ }
+
+ return $this->additionalProperties;
+ }
+
+ /**
+ * Set the additionalProperties value of the parameter
+ *
+ * @param bool|Parameter|null $additional Boolean to allow any, an Parameter to specify a schema, or false to disallow
+ *
+ * @return self
+ */
+ public function setAdditionalProperties($additional)
+ {
+ $this->additionalProperties = $additional;
+
+ return $this;
+ }
+
+ /**
+ * Set the items data of the parameter
+ *
+ * @param Parameter|null $items Items to set
+ *
+ * @return self
+ */
+ public function setItems(Parameter $items = null)
+ {
+ if ($this->items = $items) {
+ $this->items->setParent($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the item data of the parameter
+ *
+ * @return Parameter|null
+ */
+ public function getItems()
+ {
+ if (is_array($this->items)) {
+ $this->items = new static($this->items, $this->serviceDescription);
+ $this->items->setParent($this);
+ }
+
+ return $this->items;
+ }
+
+ /**
+ * Get the class that the parameter must implement
+ *
+ * @return null|string
+ */
+ public function getInstanceOf()
+ {
+ return $this->instanceOf;
+ }
+
+ /**
+ * Set the class that the parameter must be an instance of
+ *
+ * @param string|null $instanceOf Class or interface name
+ *
+ * @return self
+ */
+ public function setInstanceOf($instanceOf)
+ {
+ $this->instanceOf = $instanceOf;
+
+ return $this;
+ }
+
+ /**
+ * Get the enum of strings that are valid for the parameter
+ *
+ * @return array|null
+ */
+ public function getEnum()
+ {
+ return $this->enum;
+ }
+
+ /**
+ * Set the enum of strings that are valid for the parameter
+ *
+ * @param array|null $enum Array of strings or null
+ *
+ * @return self
+ */
+ public function setEnum(array $enum = null)
+ {
+ $this->enum = $enum;
+
+ return $this;
+ }
+
+ /**
+ * Get the regex pattern that must match a value when the value is a string
+ *
+ * @return string
+ */
+ public function getPattern()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * Set the regex pattern that must match a value when the value is a string
+ *
+ * @param string $pattern Regex pattern
+ *
+ * @return self
+ */
+ public function setPattern($pattern)
+ {
+ $this->pattern = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Get the format attribute of the schema
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Set the format attribute of the schema
+ *
+ * @param string $format Format to set (e.g. date, date-time, timestamp, time, date-time-http)
+ *
+ * @return self
+ */
+ public function setFormat($format)
+ {
+ $this->format = $format;
+
+ return $this;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php
new file mode 100644
index 0000000..7f47fc9
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * JSON Schema formatter class
+ */
+class SchemaFormatter
+{
+ /** @var \DateTimeZone */
+ protected static $utcTimeZone;
+
+ /**
+ * Format a value by a registered format name
+ *
+ * @param string $format Registered format used to format the value
+ * @param mixed $value Value being formatted
+ *
+ * @return mixed
+ */
+ public static function format($format, $value)
+ {
+ switch ($format) {
+ case 'date-time':
+ return self::formatDateTime($value);
+ case 'date-time-http':
+ return self::formatDateTimeHttp($value);
+ case 'date':
+ return self::formatDate($value);
+ case 'time':
+ return self::formatTime($value);
+ case 'timestamp':
+ return self::formatTimestamp($value);
+ case 'boolean-string':
+ return self::formatBooleanAsString($value);
+ default:
+ return $value;
+ }
+ }
+
+ /**
+ * Create a ISO 8601 (YYYY-MM-DDThh:mm:ssZ) formatted date time value in UTC time
+ *
+ * @param string|integer|\DateTime $value Date time value
+ *
+ * @return string
+ */
+ public static function formatDateTime($value)
+ {
+ return self::dateFormatter($value, 'Y-m-d\TH:i:s\Z');
+ }
+
+ /**
+ * Create an HTTP date (RFC 1123 / RFC 822) formatted UTC date-time string
+ *
+ * @param string|integer|\DateTime $value Date time value
+ *
+ * @return string
+ */
+ public static function formatDateTimeHttp($value)
+ {
+ return self::dateFormatter($value, 'D, d M Y H:i:s \G\M\T');
+ }
+
+ /**
+ * Create a YYYY-MM-DD formatted string
+ *
+ * @param string|integer|\DateTime $value Date time value
+ *
+ * @return string
+ */
+ public static function formatDate($value)
+ {
+ return self::dateFormatter($value, 'Y-m-d');
+ }
+
+ /**
+ * Create a hh:mm:ss formatted string
+ *
+ * @param string|integer|\DateTime $value Date time value
+ *
+ * @return string
+ */
+ public static function formatTime($value)
+ {
+ return self::dateFormatter($value, 'H:i:s');
+ }
+
+ /**
+ * Formats a boolean value as a string
+ *
+ * @param string|integer|bool $value Value to convert to a boolean 'true' / 'false' value
+ *
+ * @return string
+ */
+ public static function formatBooleanAsString($value)
+ {
+ return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false';
+ }
+
+ /**
+ * Return a UNIX timestamp in the UTC timezone
+ *
+ * @param string|integer|\DateTime $value Time value
+ *
+ * @return int
+ */
+ public static function formatTimestamp($value)
+ {
+ return (int) self::dateFormatter($value, 'U');
+ }
+
+ /**
+ * Get a UTC DateTimeZone object
+ *
+ * @return \DateTimeZone
+ */
+ protected static function getUtcTimeZone()
+ {
+ // @codeCoverageIgnoreStart
+ if (!self::$utcTimeZone) {
+ self::$utcTimeZone = new \DateTimeZone('UTC');
+ }
+ // @codeCoverageIgnoreEnd
+
+ return self::$utcTimeZone;
+ }
+
+ /**
+ * Perform the actual DateTime formatting
+ *
+ * @param int|string|\DateTime $dateTime Date time value
+ * @param string $format Format of the result
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ protected static function dateFormatter($dateTime, $format)
+ {
+ if (is_numeric($dateTime)) {
+ return gmdate($format, (int) $dateTime);
+ }
+
+ if (is_string($dateTime)) {
+ $dateTime = new \DateTime($dateTime);
+ }
+
+ if ($dateTime instanceof \DateTime) {
+ return $dateTime->setTimezone(self::getUtcTimeZone())->format($format);
+ }
+
+ throw new InvalidArgumentException('Date/Time values must be either a string, integer, or DateTime object');
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php
new file mode 100644
index 0000000..b045422
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php
@@ -0,0 +1,291 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * Default parameter validator
+ */
+class SchemaValidator implements ValidatorInterface
+{
+ /** @var self Cache instance of the object */
+ protected static $instance;
+
+ /** @var bool Whether or not integers are converted to strings when an integer is received for a string input */
+ protected $castIntegerToStringType;
+
+ /** @var array Errors encountered while validating */
+ protected $errors;
+
+ /**
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function getInstance()
+ {
+ if (!self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @param bool $castIntegerToStringType Set to true to convert integers into strings when a required type is a
+ * string and the input value is an integer. Defaults to true.
+ */
+ public function __construct($castIntegerToStringType = true)
+ {
+ $this->castIntegerToStringType = $castIntegerToStringType;
+ }
+
+ public function validate(Parameter $param, &$value)
+ {
+ $this->errors = array();
+ $this->recursiveProcess($param, $value);
+
+ if (empty($this->errors)) {
+ return true;
+ } else {
+ sort($this->errors);
+ return false;
+ }
+ }
+
+ /**
+ * Get the errors encountered while validating
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors ?: array();
+ }
+
+ /**
+ * Recursively validate a parameter
+ *
+ * @param Parameter $param API parameter being validated
+ * @param mixed $value Value to validate and validate. The value may change during this validate.
+ * @param string $path Current validation path (used for error reporting)
+ * @param int $depth Current depth in the validation validate
+ *
+ * @return bool Returns true if valid, or false if invalid
+ */
+ protected function recursiveProcess(Parameter $param, &$value, $path = '', $depth = 0)
+ {
+ // Update the value by adding default or static values
+ $value = $param->getValue($value);
+
+ $required = $param->getRequired();
+ // if the value is null and the parameter is not required or is static, then skip any further recursion
+ if ((null === $value && !$required) || $param->getStatic()) {
+ return true;
+ }
+
+ $type = $param->getType();
+ // Attempt to limit the number of times is_array is called by tracking if the value is an array
+ $valueIsArray = is_array($value);
+ // If a name is set then update the path so that validation messages are more helpful
+ if ($name = $param->getName()) {
+ $path .= "[{$name}]";
+ }
+
+ if ($type == 'object') {
+
+ // Objects are either associative arrays, ToArrayInterface, or some other object
+ if ($param->getInstanceOf()) {
+ $instance = $param->getInstanceOf();
+ if (!($value instanceof $instance)) {
+ $this->errors[] = "{$path} must be an instance of {$instance}";
+ return false;
+ }
+ }
+
+ // Determine whether or not this "value" has properties and should be traversed
+ $traverse = $temporaryValue = false;
+
+ // Convert the value to an array
+ if (!$valueIsArray && $value instanceof ToArrayInterface) {
+ $value = $value->toArray();
+ }
+
+ if ($valueIsArray) {
+ // Ensure that the array is associative and not numerically indexed
+ if (isset($value[0])) {
+ $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array.";
+ return false;
+ }
+ $traverse = true;
+ } elseif ($value === null) {
+ // Attempt to let the contents be built up by default values if possible
+ $value = array();
+ $temporaryValue = $valueIsArray = $traverse = true;
+ }
+
+ if ($traverse) {
+
+ if ($properties = $param->getProperties()) {
+ // if properties were found, the validate each property of the value
+ foreach ($properties as $property) {
+ $name = $property->getName();
+ if (isset($value[$name])) {
+ $this->recursiveProcess($property, $value[$name], $path, $depth + 1);
+ } else {
+ $current = null;
+ $this->recursiveProcess($property, $current, $path, $depth + 1);
+ // Only set the value if it was populated with something
+ if (null !== $current) {
+ $value[$name] = $current;
+ }
+ }
+ }
+ }
+
+ $additional = $param->getAdditionalProperties();
+ if ($additional !== true) {
+ // If additional properties were found, then validate each against the additionalProperties attr.
+ $keys = array_keys($value);
+ // Determine the keys that were specified that were not listed in the properties of the schema
+ $diff = array_diff($keys, array_keys($properties));
+ if (!empty($diff)) {
+ // Determine which keys are not in the properties
+ if ($additional instanceOf Parameter) {
+ foreach ($diff as $key) {
+ $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth);
+ }
+ } else {
+ // if additionalProperties is set to false and there are additionalProperties in the values, then fail
+ foreach ($diff as $prop) {
+ $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop);
+ }
+ }
+ }
+ }
+
+ // A temporary value will be used to traverse elements that have no corresponding input value.
+ // This allows nested required parameters with default values to bubble up into the input.
+ // Here we check if we used a temp value and nothing bubbled up, then we need to remote the value.
+ if ($temporaryValue && empty($value)) {
+ $value = null;
+ $valueIsArray = false;
+ }
+ }
+
+ } elseif ($type == 'array' && $valueIsArray && $param->getItems()) {
+ foreach ($value as $i => &$item) {
+ // Validate each item in an array against the items attribute of the schema
+ $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1);
+ }
+ }
+
+ // If the value is required and the type is not null, then there is an error if the value is not set
+ if ($required && $value === null && $type != 'null') {
+ $message = "{$path} is " . ($param->getType() ? ('a required ' . implode(' or ', (array) $param->getType())) : 'required');
+ if ($param->getDescription()) {
+ $message .= ': ' . $param->getDescription();
+ }
+ $this->errors[] = $message;
+ return false;
+ }
+
+ // Validate that the type is correct. If the type is string but an integer was passed, the class can be
+ // instructed to cast the integer to a string to pass validation. This is the default behavior.
+ if ($type && (!$type = $this->determineType($type, $value))) {
+ if ($this->castIntegerToStringType && $param->getType() == 'string' && is_integer($value)) {
+ $value = (string) $value;
+ } else {
+ $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType());
+ }
+ }
+
+ // Perform type specific validation for strings, arrays, and integers
+ if ($type == 'string') {
+
+ // Strings can have enums which are a list of predefined values
+ if (($enum = $param->getEnum()) && !in_array($value, $enum)) {
+ $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) {
+ return '"' . addslashes($s) . '"';
+ }, $enum));
+ }
+ // Strings can have a regex pattern that the value must match
+ if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) {
+ $this->errors[] = "{$path} must match the following regular expression: {$pattern}";
+ }
+
+ $strLen = null;
+ if ($min = $param->getMinLength()) {
+ $strLen = strlen($value);
+ if ($strLen < $min) {
+ $this->errors[] = "{$path} length must be greater than or equal to {$min}";
+ }
+ }
+ if ($max = $param->getMaxLength()) {
+ if (($strLen ?: strlen($value)) > $max) {
+ $this->errors[] = "{$path} length must be less than or equal to {$max}";
+ }
+ }
+
+ } elseif ($type == 'array') {
+
+ $size = null;
+ if ($min = $param->getMinItems()) {
+ $size = count($value);
+ if ($size < $min) {
+ $this->errors[] = "{$path} must contain {$min} or more elements";
+ }
+ }
+ if ($max = $param->getMaxItems()) {
+ if (($size ?: count($value)) > $max) {
+ $this->errors[] = "{$path} must contain {$max} or fewer elements";
+ }
+ }
+
+ } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') {
+ if (($min = $param->getMinimum()) && $value < $min) {
+ $this->errors[] = "{$path} must be greater than or equal to {$min}";
+ }
+ if (($max = $param->getMaximum()) && $value > $max) {
+ $this->errors[] = "{$path} must be less than or equal to {$max}";
+ }
+ }
+
+ return empty($this->errors);
+ }
+
+ /**
+ * From the allowable types, determine the type that the variable matches
+ *
+ * @param string $type Parameter type
+ * @param mixed $value Value to determine the type
+ *
+ * @return string|bool Returns the matching type on
+ */
+ protected function determineType($type, $value)
+ {
+ foreach ((array) $type as $t) {
+ if ($t == 'string' && (is_string($value) || (is_object($value) && method_exists($value, '__toString')))) {
+ return 'string';
+ } elseif ($t == 'object' && (is_array($value) || is_object($value))) {
+ return 'object';
+ } elseif ($t == 'array' && is_array($value)) {
+ return 'array';
+ } elseif ($t == 'integer' && is_integer($value)) {
+ return 'integer';
+ } elseif ($t == 'boolean' && is_bool($value)) {
+ return 'boolean';
+ } elseif ($t == 'number' && is_numeric($value)) {
+ return 'number';
+ } elseif ($t == 'numeric' && is_numeric($value)) {
+ return 'numeric';
+ } elseif ($t == 'null' && !$value) {
+ return 'null';
+ } elseif ($t == 'any') {
+ return 'any';
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php
new file mode 100644
index 0000000..286e65e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php
@@ -0,0 +1,271 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * A ServiceDescription stores service information based on a service document
+ */
+class ServiceDescription implements ServiceDescriptionInterface, ToArrayInterface
+{
+ /** @var array Array of {@see OperationInterface} objects */
+ protected $operations = array();
+
+ /** @var array Array of API models */
+ protected $models = array();
+
+ /** @var string Name of the API */
+ protected $name;
+
+ /** @var string API version */
+ protected $apiVersion;
+
+ /** @var string Summary of the API */
+ protected $description;
+
+ /** @var array Any extra API data */
+ protected $extraData = array();
+
+ /** @var ServiceDescriptionLoader Factory used in factory method */
+ protected static $descriptionLoader;
+
+ /** @var string baseUrl/basePath */
+ protected $baseUrl;
+
+ /**
+ * {@inheritdoc}
+ * @param string|array $config File to build or array of operation information
+ * @param array $options Service description factory options
+ *
+ * @return self
+ */
+ public static function factory($config, array $options = array())
+ {
+ // @codeCoverageIgnoreStart
+ if (!self::$descriptionLoader) {
+ self::$descriptionLoader = new ServiceDescriptionLoader();
+ }
+ // @codeCoverageIgnoreEnd
+
+ return self::$descriptionLoader->load($config, $options);
+ }
+
+ /**
+ * @param array $config Array of configuration data
+ */
+ public function __construct(array $config = array())
+ {
+ $this->fromArray($config);
+ }
+
+ public function serialize()
+ {
+ return json_encode($this->toArray());
+ }
+
+ public function unserialize($json)
+ {
+ $this->operations = array();
+ $this->fromArray(json_decode($json, true));
+ }
+
+ public function toArray()
+ {
+ $result = array(
+ 'name' => $this->name,
+ 'apiVersion' => $this->apiVersion,
+ 'baseUrl' => $this->baseUrl,
+ 'description' => $this->description
+ ) + $this->extraData;
+ $result['operations'] = array();
+ foreach ($this->getOperations() as $name => $operation) {
+ $result['operations'][$operation->getName() ?: $name] = $operation->toArray();
+ }
+ if (!empty($this->models)) {
+ $result['models'] = array();
+ foreach ($this->models as $id => $model) {
+ $result['models'][$id] = $model instanceof Parameter ? $model->toArray(): $model;
+ }
+ }
+
+ return array_filter($result);
+ }
+
+ public function getBaseUrl()
+ {
+ return $this->baseUrl;
+ }
+
+ /**
+ * Set the baseUrl of the description
+ *
+ * @param string $baseUrl Base URL of each operation
+ *
+ * @return self
+ */
+ public function setBaseUrl($baseUrl)
+ {
+ $this->baseUrl = $baseUrl;
+
+ return $this;
+ }
+
+ public function getOperations()
+ {
+ foreach (array_keys($this->operations) as $name) {
+ $this->getOperation($name);
+ }
+
+ return $this->operations;
+ }
+
+ public function hasOperation($name)
+ {
+ return isset($this->operations[$name]);
+ }
+
+ public function getOperation($name)
+ {
+ // Lazily retrieve and build operations
+ if (!isset($this->operations[$name])) {
+ return null;
+ }
+
+ if (!($this->operations[$name] instanceof Operation)) {
+ $this->operations[$name] = new Operation($this->operations[$name], $this);
+ }
+
+ return $this->operations[$name];
+ }
+
+ /**
+ * Add a operation to the service description
+ *
+ * @param OperationInterface $operation Operation to add
+ *
+ * @return self
+ */
+ public function addOperation(OperationInterface $operation)
+ {
+ $this->operations[$operation->getName()] = $operation->setServiceDescription($this);
+
+ return $this;
+ }
+
+ public function getModel($id)
+ {
+ if (!isset($this->models[$id])) {
+ return null;
+ }
+
+ if (!($this->models[$id] instanceof Parameter)) {
+ $this->models[$id] = new Parameter($this->models[$id] + array('name' => $id), $this);
+ }
+
+ return $this->models[$id];
+ }
+
+ public function getModels()
+ {
+ // Ensure all models are converted into parameter objects
+ foreach (array_keys($this->models) as $id) {
+ $this->getModel($id);
+ }
+
+ return $this->models;
+ }
+
+ public function hasModel($id)
+ {
+ return isset($this->models[$id]);
+ }
+
+ /**
+ * Add a model to the service description
+ *
+ * @param Parameter $model Model to add
+ *
+ * @return self
+ */
+ public function addModel(Parameter $model)
+ {
+ $this->models[$model->getName()] = $model;
+
+ return $this;
+ }
+
+ public function getApiVersion()
+ {
+ return $this->apiVersion;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ public function getData($key)
+ {
+ return isset($this->extraData[$key]) ? $this->extraData[$key] : null;
+ }
+
+ public function setData($key, $value)
+ {
+ $this->extraData[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Initialize the state from an array
+ *
+ * @param array $config Configuration data
+ * @throws InvalidArgumentException
+ */
+ protected function fromArray(array $config)
+ {
+ // Keep a list of default keys used in service descriptions that is later used to determine extra data keys
+ static $defaultKeys = array('name', 'models', 'apiVersion', 'baseUrl', 'description');
+ // Pull in the default configuration values
+ foreach ($defaultKeys as $key) {
+ if (isset($config[$key])) {
+ $this->{$key} = $config[$key];
+ }
+ }
+
+ // Account for the Swagger name for Guzzle's baseUrl
+ if (isset($config['basePath'])) {
+ $this->baseUrl = $config['basePath'];
+ }
+
+ // Ensure that the models and operations properties are always arrays
+ $this->models = (array) $this->models;
+ $this->operations = (array) $this->operations;
+
+ // We want to add operations differently than adding the other properties
+ $defaultKeys[] = 'operations';
+
+ // Create operations for each operation
+ if (isset($config['operations'])) {
+ foreach ($config['operations'] as $name => $operation) {
+ if (!($operation instanceof Operation) && !is_array($operation)) {
+ throw new InvalidArgumentException('Invalid operation in service description: '
+ . gettype($operation));
+ }
+ $this->operations[$name] = $operation;
+ }
+ }
+
+ // Get all of the additional properties of the service description and store them in a data array
+ foreach (array_diff(array_keys($config), $defaultKeys) as $key) {
+ $this->extraData[$key] = $config[$key];
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php
new file mode 100644
index 0000000..5983e58
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+/**
+ * A ServiceDescription stores service information based on a service document
+ */
+interface ServiceDescriptionInterface extends \Serializable
+{
+ /**
+ * Get the basePath/baseUrl of the description
+ *
+ * @return string
+ */
+ public function getBaseUrl();
+
+ /**
+ * Get the API operations of the service
+ *
+ * @return array Returns an array of {@see OperationInterface} objects
+ */
+ public function getOperations();
+
+ /**
+ * Check if the service has an operation by name
+ *
+ * @param string $name Name of the operation to check
+ *
+ * @return bool
+ */
+ public function hasOperation($name);
+
+ /**
+ * Get an API operation by name
+ *
+ * @param string $name Name of the command
+ *
+ * @return OperationInterface|null
+ */
+ public function getOperation($name);
+
+ /**
+ * Get a specific model from the description
+ *
+ * @param string $id ID of the model
+ *
+ * @return Parameter|null
+ */
+ public function getModel($id);
+
+ /**
+ * Get all service description models
+ *
+ * @return array
+ */
+ public function getModels();
+
+ /**
+ * Check if the description has a specific model by name
+ *
+ * @param string $id ID of the model
+ *
+ * @return bool
+ */
+ public function hasModel($id);
+
+ /**
+ * Get the API version of the service
+ *
+ * @return string
+ */
+ public function getApiVersion();
+
+ /**
+ * Get the name of the API
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Get a summary of the purpose of the API
+ *
+ * @return string
+ */
+ public function getDescription();
+
+ /**
+ * Get arbitrary data from the service description that is not part of the Guzzle spec
+ *
+ * @param string $key Data key to retrieve
+ *
+ * @return null|mixed
+ */
+ public function getData($key);
+
+ /**
+ * Set arbitrary data on the service description
+ *
+ * @param string $key Data key to set
+ * @param mixed $value Value to set
+ *
+ * @return self
+ */
+ public function setData($key, $value);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionLoader.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionLoader.php
new file mode 100644
index 0000000..90fe7f4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionLoader.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+use Guzzle\Service\AbstractConfigLoader;
+use Guzzle\Service\Exception\DescriptionBuilderException;
+
+/**
+ * Loader for service descriptions
+ */
+class ServiceDescriptionLoader extends AbstractConfigLoader
+{
+ protected function build($config, array $options)
+ {
+ $operations = array();
+ if (!empty($config['operations'])) {
+ foreach ($config['operations'] as $name => $op) {
+ $name = $op['name'] = isset($op['name']) ? $op['name'] : $name;
+ // Extend other operations
+ if (!empty($op['extends'])) {
+ $this->resolveExtension($name, $op, $operations);
+ }
+ $op['parameters'] = isset($op['parameters']) ? $op['parameters'] : array();
+ $operations[$name] = $op;
+ }
+ }
+
+ return new ServiceDescription(array(
+ 'apiVersion' => isset($config['apiVersion']) ? $config['apiVersion'] : null,
+ 'baseUrl' => isset($config['baseUrl']) ? $config['baseUrl'] : null,
+ 'description' => isset($config['description']) ? $config['description'] : null,
+ 'operations' => $operations,
+ 'models' => isset($config['models']) ? $config['models'] : null
+ ) + $config);
+ }
+
+ /**
+ * @param string $name Name of the operation
+ * @param array $op Operation value array
+ * @param array $operations Currently loaded operations
+ * @throws DescriptionBuilderException when extending a non-existent operation
+ */
+ protected function resolveExtension($name, array &$op, array &$operations)
+ {
+ $resolved = array();
+ $original = empty($op['parameters']) ? false: $op['parameters'];
+ $hasClass = !empty($op['class']);
+ foreach ((array) $op['extends'] as $extendedCommand) {
+ if (empty($operations[$extendedCommand])) {
+ throw new DescriptionBuilderException("{$name} extends missing operation {$extendedCommand}");
+ }
+ $toArray = $operations[$extendedCommand];
+ $resolved = empty($resolved)
+ ? $toArray['parameters']
+ : array_merge($resolved, $toArray['parameters']);
+
+ $op = $op + $toArray;
+ if (!$hasClass && isset($toArray['class'])) {
+ $op['class'] = $toArray['class'];
+ }
+ }
+ $op['parameters'] = $original ? array_merge($resolved, $original) : $resolved;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php
new file mode 100644
index 0000000..94ca77d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Guzzle\Service\Description;
+
+/**
+ * Validator responsible for preparing and validating parameters against the parameter's schema
+ */
+interface ValidatorInterface
+{
+ /**
+ * Validate a value against the acceptable types, regular expressions, minimum, maximums, instanceOf, enums, etc
+ * Add default and static values to the passed in variable. If the validation completes successfully, the input
+ * must be run correctly through the matching schema's filters attribute.
+ *
+ * @param Parameter $param Schema that is being validated against the value
+ * @param mixed $value Value to validate and process. The value may change during this process.
+ *
+ * @return bool Returns true if the input data is valid for the schema
+ */
+ public function validate(Parameter $param, &$value);
+
+ /**
+ * Get validation errors encountered while validating
+ *
+ * @return array
+ */
+ public function getErrors();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandException.php
new file mode 100644
index 0000000..0f016fb
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class CommandException extends RuntimeException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandTransferException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandTransferException.php
new file mode 100644
index 0000000..eabe93d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/CommandTransferException.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Exception thrown when transferring commands in parallel
+ */
+class CommandTransferException extends MultiTransferException
+{
+ protected $successfulCommands = array();
+ protected $failedCommands = array();
+
+ /**
+ * Creates a new CommandTransferException from a MultiTransferException
+ *
+ * @param MultiTransferException $e Exception to base a new exception on
+ *
+ * @return self
+ */
+ public static function fromMultiTransferException(MultiTransferException $e)
+ {
+ $ce = new self($e->getMessage(), $e->getCode(), $e->getPrevious());
+ $ce->setSuccessfulRequests($e->getSuccessfulRequests());
+
+ $alreadyAddedExceptions = array();
+ foreach ($e->getFailedRequests() as $request) {
+ if ($re = $e->getExceptionForFailedRequest($request)) {
+ $alreadyAddedExceptions[] = $re;
+ $ce->addFailedRequestWithException($request, $re);
+ } else {
+ $ce->addFailedRequest($request);
+ }
+ }
+
+ // Add any exceptions that did not map to a request
+ if (count($alreadyAddedExceptions) < count($e)) {
+ foreach ($e as $ex) {
+ if (!in_array($ex, $alreadyAddedExceptions)) {
+ $ce->add($ex);
+ }
+ }
+ }
+
+ return $ce;
+ }
+
+ /**
+ * Get all of the commands in the transfer
+ *
+ * @return array
+ */
+ public function getAllCommands()
+ {
+ return array_merge($this->successfulCommands, $this->failedCommands);
+ }
+
+ /**
+ * Add to the array of successful commands
+ *
+ * @param CommandInterface $command Successful command
+ *
+ * @return self
+ */
+ public function addSuccessfulCommand(CommandInterface $command)
+ {
+ $this->successfulCommands[] = $command;
+
+ return $this;
+ }
+
+ /**
+ * Add to the array of failed commands
+ *
+ * @param CommandInterface $command Failed command
+ *
+ * @return self
+ */
+ public function addFailedCommand(CommandInterface $command)
+ {
+ $this->failedCommands[] = $command;
+
+ return $this;
+ }
+
+ /**
+ * Get an array of successful commands
+ *
+ * @return array
+ */
+ public function getSuccessfulCommands()
+ {
+ return $this->successfulCommands;
+ }
+
+ /**
+ * Get an array of failed commands
+ *
+ * @return array
+ */
+ public function getFailedCommands()
+ {
+ return $this->failedCommands;
+ }
+
+ /**
+ * Get the Exception that caused the given $command to fail
+ *
+ * @param CommandInterface $command Failed command
+ *
+ * @return \Exception|null
+ */
+ public function getExceptionForFailedCommand(CommandInterface $command)
+ {
+ return $this->getExceptionForFailedRequest($command->getRequest());
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php
new file mode 100644
index 0000000..1407e56
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class DescriptionBuilderException extends RuntimeException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/InconsistentClientTransferException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/InconsistentClientTransferException.php
new file mode 100644
index 0000000..71cbc01
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/InconsistentClientTransferException.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+/**
+ * Command transfer exception when commands do not all use the same client
+ */
+class InconsistentClientTransferException extends RuntimeException
+{
+ /**
+ * @var array Commands with an invalid client
+ */
+ private $invalidCommands = array();
+
+ /**
+ * @param array $commands Invalid commands
+ */
+ public function __construct(array $commands)
+ {
+ $this->invalidCommands = $commands;
+ parent::__construct(
+ 'Encountered commands in a batch transfer that use inconsistent clients. The batching ' .
+ 'strategy you use with a command transfer must divide command batches by client.'
+ );
+ }
+
+ /**
+ * Get the invalid commands
+ *
+ * @return array
+ */
+ public function getCommands()
+ {
+ return $this->invalidCommands;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php
new file mode 100644
index 0000000..d59ff21
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class ResponseClassException extends RuntimeException
+{
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceBuilderException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceBuilderException.php
new file mode 100644
index 0000000..e857e5f
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceBuilderException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class ServiceBuilderException extends RuntimeException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceNotFoundException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceNotFoundException.php
new file mode 100644
index 0000000..59a0d55
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ServiceNotFoundException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+class ServiceNotFoundException extends ServiceBuilderException {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ValidationException.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ValidationException.php
new file mode 100644
index 0000000..9033bce
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ValidationException.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Service\Exception;
+
+use Guzzle\Common\Exception\RuntimeException;
+
+class ValidationException extends RuntimeException
+{
+ protected $errors = array();
+
+ /**
+ * Set the validation error messages
+ *
+ * @param array $errors Array of validation errors
+ */
+ public function setErrors(array $errors)
+ {
+ $this->errors = $errors;
+ }
+
+ /**
+ * Get any validation errors
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php
new file mode 100644
index 0000000..21140e7
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Abstract resource iterator factory implementation
+ */
+abstract class AbstractResourceIteratorFactory implements ResourceIteratorFactoryInterface
+{
+ public function build(CommandInterface $command, array $options = array())
+ {
+ if (!$this->canBuild($command)) {
+ throw new InvalidArgumentException('Iterator was not found for ' . $command->getName());
+ }
+
+ $className = $this->getClassName($command);
+
+ return new $className($command, $options);
+ }
+
+ public function canBuild(CommandInterface $command)
+ {
+ return (bool) $this->getClassName($command);
+ }
+
+ /**
+ * Get the name of the class to instantiate for the command
+ *
+ * @param CommandInterface $command Command that is associated with the iterator
+ *
+ * @return string
+ */
+ abstract protected function getClassName(CommandInterface $command);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php
new file mode 100644
index 0000000..2efc133
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Factory that utilizes multiple factories for creating iterators
+ */
+class CompositeResourceIteratorFactory implements ResourceIteratorFactoryInterface
+{
+ /** @var array Array of factories */
+ protected $factories;
+
+ /** @param array $factories Array of factories used to instantiate iterators */
+ public function __construct(array $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ public function build(CommandInterface $command, array $options = array())
+ {
+ if (!($factory = $this->getFactory($command))) {
+ throw new InvalidArgumentException('Iterator was not found for ' . $command->getName());
+ }
+
+ return $factory->build($command, $options);
+ }
+
+ public function canBuild(CommandInterface $command)
+ {
+ return $this->getFactory($command) !== false;
+ }
+
+ /**
+ * Add a factory to the composite factory
+ *
+ * @param ResourceIteratorFactoryInterface $factory Factory to add
+ *
+ * @return self
+ */
+ public function addFactory(ResourceIteratorFactoryInterface $factory)
+ {
+ $this->factories[] = $factory;
+
+ return $this;
+ }
+
+ /**
+ * Get the factory that matches the command object
+ *
+ * @param CommandInterface $command Command retrieving the iterator for
+ *
+ * @return ResourceIteratorFactoryInterface|bool
+ */
+ protected function getFactory(CommandInterface $command)
+ {
+ foreach ($this->factories as $factory) {
+ if ($factory->canBuild($command)) {
+ return $factory;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php
new file mode 100644
index 0000000..c71ca9d
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Resource iterator factory used when explicitly mapping strings to iterator classes
+ */
+class MapResourceIteratorFactory extends AbstractResourceIteratorFactory
+{
+ /** @var array Associative array mapping iterator names to class names */
+ protected $map;
+
+ /** @param array $map Associative array mapping iterator names to class names */
+ public function __construct(array $map)
+ {
+ $this->map = $map;
+ }
+
+ public function getClassName(CommandInterface $command)
+ {
+ $className = $command->getName();
+
+ if (isset($this->map[$className])) {
+ return $this->map[$className];
+ } elseif (isset($this->map['*'])) {
+ // If a wildcard was added, then always use that
+ return $this->map['*'];
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php
new file mode 100644
index 0000000..2322434
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\Collection;
+use Guzzle\Service\Description\Parameter;
+
+/**
+ * Default model created when commands create service description model responses
+ */
+class Model extends Collection
+{
+ /** @var Parameter Structure of the model */
+ protected $structure;
+
+ /**
+ * @param array $data Data contained by the model
+ * @param Parameter $structure The structure of the model
+ */
+ public function __construct(array $data = array(), Parameter $structure = null)
+ {
+ $this->data = $data;
+ $this->structure = $structure;
+ }
+
+ /**
+ * Get the structure of the model
+ *
+ * @return Parameter
+ */
+ public function getStructure()
+ {
+ return $this->structure ?: new Parameter();
+ }
+
+ /**
+ * Provides debug information about the model object
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $output = 'Debug output of ';
+ if ($this->structure) {
+ $output .= $this->structure->getName() . ' ';
+ }
+ $output .= 'model';
+ $output = str_repeat('=', strlen($output)) . "\n" . $output . "\n" . str_repeat('=', strlen($output)) . "\n\n";
+ $output .= "Model data\n-----------\n\n";
+ $output .= "This data can be retrieved from the model object using the get() method of the model "
+ . "(e.g. \$model->get(\$key)) or accessing the model like an associative array (e.g. \$model['key']).\n\n";
+ $lines = array_slice(explode("\n", trim(print_r($this->toArray(), true))), 2, -1);
+ $output .= implode("\n", $lines);
+
+ if ($this->structure) {
+ $output .= "\n\nModel structure\n---------------\n\n";
+ $output .= "The following JSON document defines how the model was parsed from an HTTP response into the "
+ . "associative array structure you see above.\n\n";
+ $output .= ' ' . json_encode($this->structure->toArray()) . "\n\n";
+ }
+
+ return $output . "\n";
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php
new file mode 100644
index 0000000..e141524
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Service\Command\CommandInterface;
+
+abstract class ResourceIterator extends AbstractHasDispatcher implements ResourceIteratorInterface
+{
+ /** @var CommandInterface Command used to send requests */
+ protected $command;
+
+ /** @var CommandInterface First sent command */
+ protected $originalCommand;
+
+ /** @var array Currently loaded resources */
+ protected $resources;
+
+ /** @var int Total number of resources that have been retrieved */
+ protected $retrievedCount = 0;
+
+ /** @var int Total number of resources that have been iterated */
+ protected $iteratedCount = 0;
+
+ /** @var string NextToken/Marker for a subsequent request */
+ protected $nextToken = false;
+
+ /** @var int Maximum number of resources to fetch per request */
+ protected $pageSize;
+
+ /** @var int Maximum number of resources to retrieve in total */
+ protected $limit;
+
+ /** @var int Number of requests sent */
+ protected $requestCount = 0;
+
+ /** @var array Initial data passed to the constructor */
+ protected $data = array();
+
+ /** @var bool Whether or not the current value is known to be invalid */
+ protected $invalid;
+
+ public static function getAllEvents()
+ {
+ return array(
+ // About to issue another command to get more results
+ 'resource_iterator.before_send',
+ // Issued another command to get more results
+ 'resource_iterator.after_send'
+ );
+ }
+
+ /**
+ * @param CommandInterface $command Initial command used for iteration
+ * @param array $data Associative array of additional parameters. You may specify any number of custom
+ * options for an iterator. Among these options, you may also specify the following values:
+ * - limit: Attempt to limit the maximum number of resources to this amount
+ * - page_size: Attempt to retrieve this number of resources per request
+ */
+ public function __construct(CommandInterface $command, array $data = array())
+ {
+ // Clone the command to keep track of the originating command for rewind
+ $this->originalCommand = $command;
+
+ // Parse options from the array of options
+ $this->data = $data;
+ $this->limit = array_key_exists('limit', $data) ? $data['limit'] : 0;
+ $this->pageSize = array_key_exists('page_size', $data) ? $data['page_size'] : false;
+ }
+
+ /**
+ * Get all of the resources as an array (Warning: this could issue a large number of requests)
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return iterator_to_array($this, false);
+ }
+
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ $this->resetState();
+
+ return $this;
+ }
+
+ public function setPageSize($pageSize)
+ {
+ $this->pageSize = $pageSize;
+ $this->resetState();
+
+ return $this;
+ }
+
+ /**
+ * Get an option from the iterator
+ *
+ * @param string $key Key of the option to retrieve
+ *
+ * @return mixed|null Returns NULL if not set or the value if set
+ */
+ public function get($key)
+ {
+ return array_key_exists($key, $this->data) ? $this->data[$key] : null;
+ }
+
+ /**
+ * Set an option on the iterator
+ *
+ * @param string $key Key of the option to set
+ * @param mixed $value Value to set for the option
+ *
+ * @return ResourceIterator
+ */
+ public function set($key, $value)
+ {
+ $this->data[$key] = $value;
+
+ return $this;
+ }
+
+ public function current()
+ {
+ return $this->resources ? current($this->resources) : false;
+ }
+
+ public function key()
+ {
+ return max(0, $this->iteratedCount - 1);
+ }
+
+ public function count()
+ {
+ return $this->retrievedCount;
+ }
+
+ /**
+ * Get the total number of requests sent
+ *
+ * @return int
+ */
+ public function getRequestCount()
+ {
+ return $this->requestCount;
+ }
+
+ /**
+ * Rewind the Iterator to the first element and send the original command
+ */
+ public function rewind()
+ {
+ // Use the original command
+ $this->command = clone $this->originalCommand;
+ $this->resetState();
+ $this->next();
+ }
+
+ public function valid()
+ {
+ return !$this->invalid && (!$this->resources || $this->current() || $this->nextToken)
+ && (!$this->limit || $this->iteratedCount < $this->limit + 1);
+ }
+
+ public function next()
+ {
+ $this->iteratedCount++;
+
+ // Check if a new set of resources needs to be retrieved
+ $sendRequest = false;
+ if (!$this->resources) {
+ $sendRequest = true;
+ } else {
+ // iterate over the internal array
+ $current = next($this->resources);
+ $sendRequest = $current === false && $this->nextToken && (!$this->limit || $this->iteratedCount < $this->limit + 1);
+ }
+
+ if ($sendRequest) {
+
+ $this->dispatch('resource_iterator.before_send', array(
+ 'iterator' => $this,
+ 'resources' => $this->resources
+ ));
+
+ // Get a new command object from the original command
+ $this->command = clone $this->originalCommand;
+ // Send a request and retrieve the newly loaded resources
+ $this->resources = $this->sendRequest();
+ $this->requestCount++;
+
+ // If no resources were found, then the last request was not needed
+ // and iteration must stop
+ if (empty($this->resources)) {
+ $this->invalid = true;
+ } else {
+ // Add to the number of retrieved resources
+ $this->retrievedCount += count($this->resources);
+ // Ensure that we rewind to the beginning of the array
+ reset($this->resources);
+ }
+
+ $this->dispatch('resource_iterator.after_send', array(
+ 'iterator' => $this,
+ 'resources' => $this->resources
+ ));
+ }
+ }
+
+ /**
+ * Retrieve the NextToken that can be used in other iterators.
+ *
+ * @return string Returns a NextToken
+ */
+ public function getNextToken()
+ {
+ return $this->nextToken;
+ }
+
+ /**
+ * Returns the value that should be specified for the page size for a request that will maintain any hard limits,
+ * but still honor the specified pageSize if the number of items retrieved + pageSize < hard limit
+ *
+ * @return int Returns the page size of the next request.
+ */
+ protected function calculatePageSize()
+ {
+ if ($this->limit && $this->iteratedCount + $this->pageSize > $this->limit) {
+ return 1 + ($this->limit - $this->iteratedCount);
+ }
+
+ return (int) $this->pageSize;
+ }
+
+ /**
+ * Reset the internal state of the iterator without triggering a rewind()
+ */
+ protected function resetState()
+ {
+ $this->iteratedCount = 0;
+ $this->retrievedCount = 0;
+ $this->nextToken = false;
+ $this->resources = null;
+ $this->invalid = false;
+ }
+
+ /**
+ * Send a request to retrieve the next page of results. Hook for subclasses to implement.
+ *
+ * @return array Returns the newly loaded resources
+ */
+ abstract protected function sendRequest();
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php
new file mode 100644
index 0000000..6aa3615
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\AbstractHasDispatcher;
+use Guzzle\Batch\BatchBuilder;
+use Guzzle\Batch\BatchSizeDivisor;
+use Guzzle\Batch\BatchClosureTransfer;
+use Guzzle\Common\Version;
+
+/**
+ * Apply a callback to the contents of a {@see ResourceIteratorInterface}
+ * @deprecated Will be removed in a future version and is no longer maintained. Use the Batch\ abstractions instead.
+ * @codeCoverageIgnore
+ */
+class ResourceIteratorApplyBatched extends AbstractHasDispatcher
+{
+ /** @var callable|array */
+ protected $callback;
+
+ /** @var ResourceIteratorInterface */
+ protected $iterator;
+
+ /** @var integer Total number of sent batches */
+ protected $batches = 0;
+
+ /** @var int Total number of iterated resources */
+ protected $iterated = 0;
+
+ public static function getAllEvents()
+ {
+ return array(
+ // About to send a batch of requests to the callback
+ 'iterator_batch.before_batch',
+ // Finished sending a batch of requests to the callback
+ 'iterator_batch.after_batch',
+ // Created the batch object
+ 'iterator_batch.created_batch'
+ );
+ }
+
+ /**
+ * @param ResourceIteratorInterface $iterator Resource iterator to apply a callback to
+ * @param array|callable $callback Callback method accepting the resource iterator
+ * and an array of the iterator's current resources
+ */
+ public function __construct(ResourceIteratorInterface $iterator, $callback)
+ {
+ $this->iterator = $iterator;
+ $this->callback = $callback;
+ Version::warn(__CLASS__ . ' is deprecated');
+ }
+
+ /**
+ * Apply the callback to the contents of the resource iterator
+ *
+ * @param int $perBatch The number of records to group per batch transfer
+ *
+ * @return int Returns the number of iterated resources
+ */
+ public function apply($perBatch = 50)
+ {
+ $this->iterated = $this->batches = $batches = 0;
+ $that = $this;
+ $it = $this->iterator;
+ $callback = $this->callback;
+
+ $batch = BatchBuilder::factory()
+ ->createBatchesWith(new BatchSizeDivisor($perBatch))
+ ->transferWith(new BatchClosureTransfer(function (array $batch) use ($that, $callback, &$batches, $it) {
+ $batches++;
+ $that->dispatch('iterator_batch.before_batch', array('iterator' => $it, 'batch' => $batch));
+ call_user_func_array($callback, array($it, $batch));
+ $that->dispatch('iterator_batch.after_batch', array('iterator' => $it, 'batch' => $batch));
+ }))
+ ->autoFlushAt($perBatch)
+ ->build();
+
+ $this->dispatch('iterator_batch.created_batch', array('batch' => $batch));
+
+ foreach ($this->iterator as $resource) {
+ $this->iterated++;
+ $batch->add($resource);
+ }
+
+ $batch->flush();
+ $this->batches = $batches;
+
+ return $this->iterated;
+ }
+
+ /**
+ * Get the total number of batches sent
+ *
+ * @return int
+ */
+ public function getBatchCount()
+ {
+ return $this->batches;
+ }
+
+ /**
+ * Get the total number of iterated resources
+ *
+ * @return int
+ */
+ public function getIteratedCount()
+ {
+ return $this->iterated;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php
new file mode 100644
index 0000000..2fd9980
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Inflection\InflectorInterface;
+use Guzzle\Inflection\Inflector;
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Factory for creating {@see ResourceIteratorInterface} objects using a convention of storing iterator classes under a
+ * root namespace using the name of a {@see CommandInterface} object as a convention for determining the name of an
+ * iterator class. The command name is converted to CamelCase and Iterator is appended (e.g. abc_foo => AbcFoo).
+ */
+class ResourceIteratorClassFactory extends AbstractResourceIteratorFactory
+{
+ /** @var array List of namespaces used to look for classes */
+ protected $namespaces;
+
+ /** @var InflectorInterface Inflector used to determine class names */
+ protected $inflector;
+
+ /**
+ * @param string|array $namespaces List of namespaces for iterator objects
+ * @param InflectorInterface $inflector Inflector used to resolve class names
+ */
+ public function __construct($namespaces = array(), InflectorInterface $inflector = null)
+ {
+ $this->namespaces = (array) $namespaces;
+ $this->inflector = $inflector ?: Inflector::getDefault();
+ }
+
+ /**
+ * Registers a namespace to check for Iterators
+ *
+ * @param string $namespace Namespace which contains Iterator classes
+ *
+ * @return self
+ */
+ public function registerNamespace($namespace)
+ {
+ array_unshift($this->namespaces, $namespace);
+
+ return $this;
+ }
+
+ protected function getClassName(CommandInterface $command)
+ {
+ $iteratorName = $this->inflector->camel($command->getName()) . 'Iterator';
+
+ // Determine the name of the class to load
+ foreach ($this->namespaces as $namespace) {
+ $potentialClassName = $namespace . '\\' . $iteratorName;
+ if (class_exists($potentialClassName)) {
+ return $potentialClassName;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php
new file mode 100644
index 0000000..8b4e8db
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Service\Command\CommandInterface;
+
+/**
+ * Factory for creating {@see ResourceIteratorInterface} objects
+ */
+interface ResourceIteratorFactoryInterface
+{
+ /**
+ * Create a resource iterator
+ *
+ * @param CommandInterface $command Command to create an iterator for
+ * @param array $options Iterator options that are exposed as data.
+ *
+ * @return ResourceIteratorInterface
+ */
+ public function build(CommandInterface $command, array $options = array());
+
+ /**
+ * Check if the factory can create an iterator
+ *
+ * @param CommandInterface $command Command to create an iterator for
+ *
+ * @return bool
+ */
+ public function canBuild(CommandInterface $command);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorInterface.php
new file mode 100644
index 0000000..dbaafde
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorInterface.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Guzzle\Service\Resource;
+
+use Guzzle\Common\HasDispatcherInterface;
+use Guzzle\Common\ToArrayInterface;
+
+/**
+ * Iterates over a paginated resource using subsequent requests in order to retrieve the entire matching result set
+ */
+interface ResourceIteratorInterface extends ToArrayInterface, HasDispatcherInterface, \Iterator, \Countable
+{
+ /**
+ * Retrieve the NextToken that can be used in other iterators.
+ *
+ * @return string Returns a NextToken
+ */
+ public function getNextToken();
+
+ /**
+ * Attempt to limit the total number of resources returned by the iterator.
+ *
+ * You may still receive more items than you specify. Set to 0 to specify no limit.
+ *
+ * @param int $limit Limit amount
+ *
+ * @return ResourceIteratorInterface
+ */
+ public function setLimit($limit);
+
+ /**
+ * Attempt to limit the total number of resources retrieved per request by the iterator.
+ *
+ * The iterator may return more than you specify in the page size argument depending on the service and underlying
+ * command implementation. Set to 0 to specify no page size limitation.
+ *
+ * @param int $pageSize Limit amount
+ *
+ * @return ResourceIteratorInterface
+ */
+ public function setPageSize($pageSize);
+
+ /**
+ * Get a data option from the iterator
+ *
+ * @param string $key Key of the option to retrieve
+ *
+ * @return mixed|null Returns NULL if not set or the value if set
+ */
+ public function get($key);
+
+ /**
+ * Set a data option on the iterator
+ *
+ * @param string $key Key of the option to set
+ * @param mixed $value Value to set for the option
+ *
+ * @return ResourceIteratorInterface
+ */
+ public function set($key, $value);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Service/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Service/composer.json
new file mode 100644
index 0000000..cb7ace6
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Service/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "guzzle/service",
+ "description": "Guzzle service component for abstracting RESTful web services",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["web service", "webservice", "REST", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/cache": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Service": "" }
+ },
+ "target-dir": "Guzzle/Service",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php b/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php
new file mode 100644
index 0000000..d115fd8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php
@@ -0,0 +1,284 @@
+<?php
+
+namespace Guzzle\Stream;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Http\Message\EntityEnclosingRequestInterface;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Url;
+
+/**
+ * Factory used to create fopen streams using PHP's http and https stream wrappers
+ *
+ * Note: PHP's http stream wrapper only supports streaming downloads. It does not support streaming uploads.
+ */
+class PhpStreamRequestFactory implements StreamRequestFactoryInterface
+{
+ /** @var resource Stream context options */
+ protected $context;
+
+ /** @var array Stream context */
+ protected $contextOptions;
+
+ /** @var Url Stream URL */
+ protected $url;
+
+ /** @var array Last response headers received by the HTTP request */
+ protected $lastResponseHeaders;
+
+ /**
+ * {@inheritdoc}
+ *
+ * The $params array can contain the following custom keys specific to the PhpStreamRequestFactory:
+ * - stream_class: The name of a class to create instead of a Guzzle\Stream\Stream object
+ */
+ public function fromRequest(RequestInterface $request, $context = array(), array $params = array())
+ {
+ if (is_resource($context)) {
+ $this->contextOptions = stream_context_get_options($context);
+ $this->context = $context;
+ } elseif (is_array($context) || !$context) {
+ $this->contextOptions = $context;
+ $this->createContext($params);
+ } elseif ($context) {
+ throw new InvalidArgumentException('$context must be an array or resource');
+ }
+
+ // Dispatch the before send event
+ $request->dispatch('request.before_send', array(
+ 'request' => $request,
+ 'context' => $this->context,
+ 'context_options' => $this->contextOptions
+ ));
+
+ $this->setUrl($request);
+ $this->addDefaultContextOptions($request);
+ $this->addSslOptions($request);
+ $this->addBodyOptions($request);
+ $this->addProxyOptions($request);
+
+ // Create the file handle but silence errors
+ return $this->createStream($params)
+ ->setCustomData('request', $request)
+ ->setCustomData('response_headers', $this->getLastResponseHeaders());
+ }
+
+ /**
+ * Set an option on the context and the internal options array
+ *
+ * @param string $wrapper Stream wrapper name of http
+ * @param string $name Context name
+ * @param mixed $value Context value
+ * @param bool $overwrite Set to true to overwrite an existing value
+ */
+ protected function setContextValue($wrapper, $name, $value, $overwrite = false)
+ {
+ if (!isset($this->contextOptions[$wrapper])) {
+ $this->contextOptions[$wrapper] = array($name => $value);
+ } elseif (!$overwrite && isset($this->contextOptions[$wrapper][$name])) {
+ return;
+ }
+ $this->contextOptions[$wrapper][$name] = $value;
+ stream_context_set_option($this->context, $wrapper, $name, $value);
+ }
+
+ /**
+ * Create a stream context
+ *
+ * @param array $params Parameter array
+ */
+ protected function createContext(array $params)
+ {
+ $options = $this->contextOptions;
+ $this->context = $this->createResource(function () use ($params, $options) {
+ return stream_context_create($options, $params);
+ });
+ }
+
+ /**
+ * Get the last response headers received by the HTTP request
+ *
+ * @return array
+ */
+ public function getLastResponseHeaders()
+ {
+ return $this->lastResponseHeaders;
+ }
+
+ /**
+ * Adds the default context options to the stream context options
+ *
+ * @param RequestInterface $request Request
+ */
+ protected function addDefaultContextOptions(RequestInterface $request)
+ {
+ $this->setContextValue('http', 'method', $request->getMethod());
+ $headers = $request->getHeaderLines();
+
+ // "Connection: close" is required to get streams to work in HTTP 1.1
+ if (!$request->hasHeader('Connection')) {
+ $headers[] = 'Connection: close';
+ }
+
+ $this->setContextValue('http', 'header', $headers);
+ $this->setContextValue('http', 'protocol_version', $request->getProtocolVersion());
+ $this->setContextValue('http', 'ignore_errors', true);
+ }
+
+ /**
+ * Set the URL to use with the factory
+ *
+ * @param RequestInterface $request Request that owns the URL
+ */
+ protected function setUrl(RequestInterface $request)
+ {
+ $this->url = $request->getUrl(true);
+
+ // Check for basic Auth username
+ if ($request->getUsername()) {
+ $this->url->setUsername($request->getUsername());
+ }
+
+ // Check for basic Auth password
+ if ($request->getPassword()) {
+ $this->url->setPassword($request->getPassword());
+ }
+ }
+
+ /**
+ * Add SSL options to the stream context
+ *
+ * @param RequestInterface $request Request
+ */
+ protected function addSslOptions(RequestInterface $request)
+ {
+ if ($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)) {
+ $this->setContextValue('ssl', 'verify_peer', true, true);
+ if ($cafile = $request->getCurlOptions()->get(CURLOPT_CAINFO)) {
+ $this->setContextValue('ssl', 'cafile', $cafile, true);
+ }
+ } else {
+ $this->setContextValue('ssl', 'verify_peer', false, true);
+ }
+ }
+
+ /**
+ * Add body (content) specific options to the context options
+ *
+ * @param RequestInterface $request
+ */
+ protected function addBodyOptions(RequestInterface $request)
+ {
+ // Add the content for the request if needed
+ if (!($request instanceof EntityEnclosingRequestInterface)) {
+ return;
+ }
+
+ if (count($request->getPostFields())) {
+ $this->setContextValue('http', 'content', (string) $request->getPostFields(), true);
+ } elseif ($request->getBody()) {
+ $this->setContextValue('http', 'content', (string) $request->getBody(), true);
+ }
+
+ // Always ensure a content-length header is sent
+ if (isset($this->contextOptions['http']['content'])) {
+ $headers = isset($this->contextOptions['http']['header']) ? $this->contextOptions['http']['header'] : array();
+ $headers[] = 'Content-Length: ' . strlen($this->contextOptions['http']['content']);
+ $this->setContextValue('http', 'header', $headers, true);
+ }
+ }
+
+ /**
+ * Add proxy parameters to the context if needed
+ *
+ * @param RequestInterface $request Request
+ */
+ protected function addProxyOptions(RequestInterface $request)
+ {
+ if ($proxy = $request->getCurlOptions()->get(CURLOPT_PROXY)) {
+ $this->setContextValue('http', 'proxy', $proxy);
+ }
+ }
+
+ /**
+ * Create the stream for the request with the context options
+ *
+ * @param array $params Parameters of the stream
+ *
+ * @return StreamInterface
+ */
+ protected function createStream(array $params)
+ {
+ $http_response_header = null;
+ $url = $this->url;
+ $context = $this->context;
+ $fp = $this->createResource(function () use ($context, $url, &$http_response_header) {
+ return fopen((string) $url, 'r', false, $context);
+ });
+
+ // Determine the class to instantiate
+ $className = isset($params['stream_class']) ? $params['stream_class'] : __NAMESPACE__ . '\\Stream';
+
+ /** @var $stream StreamInterface */
+ $stream = new $className($fp);
+
+ // Track the response headers of the request
+ if (isset($http_response_header)) {
+ $this->lastResponseHeaders = $http_response_header;
+ $this->processResponseHeaders($stream);
+ }
+
+ return $stream;
+ }
+
+ /**
+ * Process response headers
+ *
+ * @param StreamInterface $stream
+ */
+ protected function processResponseHeaders(StreamInterface $stream)
+ {
+ // Set the size on the stream if it was returned in the response
+ foreach ($this->lastResponseHeaders as $header) {
+ if ((stripos($header, 'Content-Length:')) === 0) {
+ $stream->setSize(trim(substr($header, 15)));
+ }
+ }
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Closure to invoke that must return a valid resource
+ *
+ * @return resource
+ * @throws RuntimeException on error
+ */
+ protected function createResource($callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = array(
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ );
+ return true;
+ });
+ $resource = call_user_func($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 RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php b/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php
new file mode 100644
index 0000000..12bed26
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php
@@ -0,0 +1,289 @@
+<?php
+
+namespace Guzzle\Stream;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+
+/**
+ * PHP stream implementation
+ */
+class Stream implements StreamInterface
+{
+ const STREAM_TYPE = 'stream_type';
+ const WRAPPER_TYPE = 'wrapper_type';
+ const IS_LOCAL = 'is_local';
+ const IS_READABLE = 'is_readable';
+ const IS_WRITABLE = 'is_writable';
+ const SEEKABLE = 'seekable';
+
+ /** @var resource Stream resource */
+ protected $stream;
+
+ /** @var int Size of the stream contents in bytes */
+ protected $size;
+
+ /** @var array Stream cached data */
+ protected $cache = array();
+
+ /** @var array Custom stream data */
+ protected $customData = array();
+
+ /** @var array Hash table of readable and writeable stream types for fast lookups */
+ protected static $readWriteHash = array(
+ 'read' => array(
+ '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' => array(
+ '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
+ )
+ );
+
+ /**
+ * @param resource $stream Stream resource to wrap
+ * @param int $size Size of the stream in bytes. Only pass if the size cannot be obtained from the stream.
+ *
+ * @throws InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $size = null)
+ {
+ $this->setStream($stream, $size);
+ }
+
+ /**
+ * Closes the stream when the helper is destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ if (!$this->isReadable() || (!$this->isSeekable() && $this->isConsumed())) {
+ return '';
+ }
+
+ $originalPos = $this->ftell();
+ $body = stream_get_contents($this->stream, -1, 0);
+ $this->seek($originalPos);
+
+ return $body;
+ }
+
+ public function close()
+ {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->cache[self::IS_READABLE] = false;
+ $this->cache[self::IS_WRITABLE] = false;
+ }
+
+ /**
+ * 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 bool|string Returns false on failure or a hash string on success
+ */
+ public static function getHash(StreamInterface $stream, $algo, $rawOutput = false)
+ {
+ $pos = $stream->ftell();
+ if (!$stream->seek(0)) {
+ return false;
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->feof()) {
+ hash_update($ctx, $stream->read(8192));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+ }
+
+ public function getMetaData($key = null)
+ {
+ $meta = stream_get_meta_data($this->stream);
+
+ return !$key ? $meta : (array_key_exists($key, $meta) ? $meta[$key] : null);
+ }
+
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ public function setStream($stream, $size = null)
+ {
+ if (!is_resource($stream)) {
+ throw new InvalidArgumentException('Stream must be a resource');
+ }
+
+ $this->size = $size;
+ $this->stream = $stream;
+ $this->rebuildCache();
+
+ return $this;
+ }
+
+ public function detachStream()
+ {
+ $this->stream = null;
+
+ return $this;
+ }
+
+ public function getWrapper()
+ {
+ return $this->cache[self::WRAPPER_TYPE];
+ }
+
+ public function getWrapperData()
+ {
+ return $this->getMetaData('wrapper_data') ?: array();
+ }
+
+ public function getStreamType()
+ {
+ return $this->cache[self::STREAM_TYPE];
+ }
+
+ public function getUri()
+ {
+ return $this->cache['uri'];
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ // If the stream is a file based stream and local, then use fstat
+ clearstatcache(true, $this->cache['uri']);
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ } elseif ($this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE]) {
+ // Only get the size based on the content if the the stream is readable and seekable
+ $pos = $this->ftell();
+ $this->size = strlen((string) $this);
+ $this->seek($pos);
+ return $this->size;
+ }
+
+ return false;
+ }
+
+ public function isReadable()
+ {
+ return $this->cache[self::IS_READABLE];
+ }
+
+ public function isRepeatable()
+ {
+ return $this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE];
+ }
+
+ public function isWritable()
+ {
+ return $this->cache[self::IS_WRITABLE];
+ }
+
+ public function isConsumed()
+ {
+ return feof($this->stream);
+ }
+
+ public function feof()
+ {
+ return $this->isConsumed();
+ }
+
+ public function isLocal()
+ {
+ return $this->cache[self::IS_LOCAL];
+ }
+
+ public function isSeekable()
+ {
+ return $this->cache[self::SEEKABLE];
+ }
+
+ public function setSize($size)
+ {
+ $this->size = $size;
+
+ return $this;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return $this->cache[self::SEEKABLE] ? fseek($this->stream, $offset, $whence) === 0 : false;
+ }
+
+ public function read($length)
+ {
+ return fread($this->stream, $length);
+ }
+
+ public function write($string)
+ {
+ // We can't know the size after writing anything
+ $this->size = null;
+
+ return fwrite($this->stream, $string);
+ }
+
+ public function ftell()
+ {
+ return ftell($this->stream);
+ }
+
+ public function rewind()
+ {
+ return $this->seek(0);
+ }
+
+ public function readLine($maxLength = null)
+ {
+ if (!$this->cache[self::IS_READABLE]) {
+ return false;
+ } else {
+ return $maxLength ? fgets($this->getStream(), $maxLength) : fgets($this->getStream());
+ }
+ }
+
+ public function setCustomData($key, $value)
+ {
+ $this->customData[$key] = $value;
+
+ return $this;
+ }
+
+ public function getCustomData($key)
+ {
+ return isset($this->customData[$key]) ? $this->customData[$key] : null;
+ }
+
+ /**
+ * Reprocess stream metadata
+ */
+ protected function rebuildCache()
+ {
+ $this->cache = stream_get_meta_data($this->stream);
+ $this->cache[self::IS_LOCAL] = stream_is_local($this->stream);
+ $this->cache[self::IS_READABLE] = isset(self::$readWriteHash['read'][$this->cache['mode']]);
+ $this->cache[self::IS_WRITABLE] = isset(self::$readWriteHash['write'][$this->cache['mode']]);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php
new file mode 100644
index 0000000..6d7dc37
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php
@@ -0,0 +1,218 @@
+<?php
+
+namespace Guzzle\Stream;
+
+/**
+ * OO interface to PHP streams
+ */
+interface StreamInterface
+{
+ /**
+ * Convert the stream to a string if the stream is readable and the stream is seekable.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Close the underlying stream
+ */
+ public function close();
+
+ /**
+ * Get stream metadata
+ *
+ * @param string $key Specific metadata to retrieve
+ *
+ * @return array|mixed|null
+ */
+ public function getMetaData($key = null);
+
+ /**
+ * Get the stream resource
+ *
+ * @return resource
+ */
+ public function getStream();
+
+ /**
+ * Set the stream that is wrapped by the object
+ *
+ * @param resource $stream Stream resource to wrap
+ * @param int $size Size of the stream in bytes. Only pass if the size cannot be obtained from the stream.
+ *
+ * @return self
+ */
+ public function setStream($stream, $size = null);
+
+ /**
+ * Detach the current stream resource
+ *
+ * @return self
+ */
+ public function detachStream();
+
+ /**
+ * Get the stream wrapper type
+ *
+ * @return string
+ */
+ public function getWrapper();
+
+ /**
+ * Wrapper specific data attached to this stream.
+ *
+ * @return array
+ */
+ public function getWrapperData();
+
+ /**
+ * Get a label describing the underlying implementation of the stream
+ *
+ * @return string
+ */
+ public function getStreamType();
+
+ /**
+ * Get the URI/filename associated with this stream
+ *
+ * @return string
+ */
+ public function getUri();
+
+ /**
+ * Get the size of the stream if able
+ *
+ * @return int|bool
+ */
+ public function getSize();
+
+ /**
+ * Check if the stream is readable
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Check if the stream is repeatable
+ *
+ * @return bool
+ */
+ public function isRepeatable();
+
+ /**
+ * Check if the stream is writable
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Check if the stream has been consumed
+ *
+ * @return bool
+ */
+ public function isConsumed();
+
+ /**
+ * Alias of isConsumed
+ *
+ * @return bool
+ */
+ public function feof();
+
+ /**
+ * Check if the stream is a local stream vs a remote stream
+ *
+ * @return bool
+ */
+ public function isLocal();
+
+ /**
+ * Check if the string is repeatable
+ *
+ * @return bool
+ */
+ public function isSeekable();
+
+ /**
+ * Specify the size of the stream in bytes
+ *
+ * @param int $size Size of the stream contents in bytes
+ *
+ * @return self
+ */
+ public function setSize($size);
+
+ /**
+ * Seek to a position in the stream
+ *
+ * @param int $offset Stream offset
+ * @param int $whence Where the offset is applied
+ *
+ * @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);
+
+ /**
+ * Read data from the stream
+ *
+ * @param int $length Up to length number of bytes read.
+ *
+ * @return string|bool Returns the data read from the stream or FALSE on failure or EOF
+ */
+ public function read($length);
+
+ /**
+ * 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 or FALSE on failure.
+ */
+ public function write($string);
+
+ /**
+ * 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 ftell();
+
+ /**
+ * Rewind to the beginning of the stream
+ *
+ * @return bool Returns true on success or false on failure
+ */
+ public function rewind();
+
+ /**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+ public function readLine($maxLength = null);
+
+ /**
+ * Set custom data on the stream
+ *
+ * @param string $key Key to set
+ * @param mixed $value Value to set
+ *
+ * @return self
+ */
+ public function setCustomData($key, $value);
+
+ /**
+ * Get custom data from the stream
+ *
+ * @param string $key Key to retrieve
+ *
+ * @return null|mixed
+ */
+ public function getCustomData($key);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamRequestFactoryInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamRequestFactoryInterface.php
new file mode 100644
index 0000000..d00e622
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamRequestFactoryInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Guzzle\Stream;
+
+use Guzzle\Http\Message\RequestInterface;
+
+/**
+ * Interface used for creating streams from requests
+ */
+interface StreamRequestFactoryInterface
+{
+ /**
+ * Create a stream based on a request object
+ *
+ * @param RequestInterface $request Base the stream on a request
+ * @param array|resource $context A stream_context_options resource or array of parameters used to create a
+ * stream context.
+ * @param array $params Optional array of parameters specific to the factory
+ *
+ * @return StreamInterface Returns a stream object
+ * @throws \Guzzle\Common\Exception\RuntimeException if the stream cannot be opened or an error occurs
+ */
+ public function fromRequest(RequestInterface $request, $context = array(), array $params = array());
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Stream/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Stream/composer.json
new file mode 100644
index 0000000..9c19d2b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Stream/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "guzzle/stream",
+ "description": "Guzzle stream wrapper component",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["stream", "component", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/common": "self.version"
+ },
+ "suggest": {
+ "guzzle/http": "To convert Guzzle request objects to PHP streams"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Stream": "" }
+ },
+ "target-dir": "Guzzle/Stream",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php
new file mode 100644
index 0000000..951738d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\Batch;
+
+/**
+ * @covers Guzzle\Batch\AbstractBatchDecorator
+ */
+class AbstractBatchDecoratorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testProxiesToWrappedObject()
+ {
+ $batch = new Batch(
+ $this->getMock('Guzzle\Batch\BatchTransferInterface'),
+ $this->getMock('Guzzle\Batch\BatchDivisorInterface')
+ );
+
+ $decoratorA = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator')
+ ->setConstructorArgs(array($batch))
+ ->getMockForAbstractClass();
+
+ $decoratorB = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator')
+ ->setConstructorArgs(array($decoratorA))
+ ->getMockForAbstractClass();
+
+ $decoratorA->add('foo');
+ $this->assertFalse($decoratorB->isEmpty());
+ $this->assertFalse($batch->isEmpty());
+ $this->assertEquals(array($decoratorB, $decoratorA), $decoratorB->getDecorators());
+ $this->assertEquals(array(), $decoratorB->flush());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php
new file mode 100644
index 0000000..4da09d3
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchBuilder;
+
+/**
+ * @covers Guzzle\Batch\BatchBuilder
+ */
+class BatchBuilderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private function getMockTransfer()
+ {
+ return $this->getMock('Guzzle\Batch\BatchTransferInterface');
+ }
+
+ private function getMockDivisor()
+ {
+ return $this->getMock('Guzzle\Batch\BatchDivisorInterface');
+ }
+
+ private function getMockBatchBuilder()
+ {
+ return BatchBuilder::factory()
+ ->transferWith($this->getMockTransfer())
+ ->createBatchesWith($this->getMockDivisor());
+ }
+
+ public function testFactoryCreatesInstance()
+ {
+ $builder = BatchBuilder::factory();
+ $this->assertInstanceOf('Guzzle\Batch\BatchBuilder', $builder);
+ }
+
+ public function testAddsAutoFlush()
+ {
+ $batch = $this->getMockBatchBuilder()->autoFlushAt(10)->build();
+ $this->assertInstanceOf('Guzzle\Batch\FlushingBatch', $batch);
+ }
+
+ public function testAddsExceptionBuffering()
+ {
+ $batch = $this->getMockBatchBuilder()->bufferExceptions()->build();
+ $this->assertInstanceOf('Guzzle\Batch\ExceptionBufferingBatch', $batch);
+ }
+
+ public function testAddHistory()
+ {
+ $batch = $this->getMockBatchBuilder()->keepHistory()->build();
+ $this->assertInstanceOf('Guzzle\Batch\HistoryBatch', $batch);
+ }
+
+ public function testAddsNotify()
+ {
+ $batch = $this->getMockBatchBuilder()->notify(function() {})->build();
+ $this->assertInstanceOf('Guzzle\Batch\NotifyingBatch', $batch);
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\RuntimeException
+ */
+ public function testTransferStrategyMustBeSet()
+ {
+ $batch = BatchBuilder::factory()->createBatchesWith($this->getMockDivisor())->build();
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\RuntimeException
+ */
+ public function testDivisorStrategyMustBeSet()
+ {
+ $batch = BatchBuilder::factory()->transferWith($this->getMockTransfer())->build();
+ }
+
+ public function testTransfersRequests()
+ {
+ $batch = BatchBuilder::factory()->transferRequests(10)->build();
+ $this->assertInstanceOf('Guzzle\Batch\BatchRequestTransfer', $this->readAttribute($batch, 'transferStrategy'));
+ }
+
+ public function testTransfersCommands()
+ {
+ $batch = BatchBuilder::factory()->transferCommands(10)->build();
+ $this->assertInstanceOf('Guzzle\Batch\BatchCommandTransfer', $this->readAttribute($batch, 'transferStrategy'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php
new file mode 100644
index 0000000..753db7d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchClosureDivisor;
+
+/**
+ * @covers Guzzle\Batch\BatchClosureDivisor
+ */
+class BatchClosureDivisorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresCallableIsCallable()
+ {
+ $d = new BatchClosureDivisor(new \stdClass());
+ }
+
+ public function testDividesBatch()
+ {
+ $queue = new \SplQueue();
+ $queue[] = 'foo';
+ $queue[] = 'baz';
+
+ $d = new BatchClosureDivisor(function (\SplQueue $queue, $context) {
+ return array(
+ array('foo'),
+ array('baz')
+ );
+ }, 'Bar!');
+
+ $batches = $d->createBatches($queue);
+ $this->assertEquals(array(array('foo'), array('baz')), $batches);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php
new file mode 100644
index 0000000..6ba7ae0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchClosureTransfer;
+
+/**
+ * @covers Guzzle\Batch\BatchClosureTransfer
+ */
+class BatchClosureTransferTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var \Guzzle\Batch\BatchClosureTransfer The transfer fixture */
+ protected $transferStrategy;
+
+ /** @var array|null An array for keeping track of items passed into the transfer closure */
+ protected $itemsTransferred;
+
+ protected function setUp()
+ {
+ $this->itemsTransferred = null;
+ $itemsTransferred =& $this->itemsTransferred;
+
+ $this->transferStrategy = new BatchClosureTransfer(function (array $batch) use (&$itemsTransferred) {
+ $itemsTransferred = $batch;
+ return;
+ });
+ }
+
+ public function testTransfersBatch()
+ {
+ $batchedItems = array('foo', 'bar', 'baz');
+ $this->transferStrategy->transfer($batchedItems);
+
+ $this->assertEquals($batchedItems, $this->itemsTransferred);
+ }
+
+ public function testTransferBailsOnEmptyBatch()
+ {
+ $batchedItems = array();
+ $this->transferStrategy->transfer($batchedItems);
+
+ $this->assertNull($this->itemsTransferred);
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresCallableIsCallable()
+ {
+ $foo = new BatchClosureTransfer('uh oh!');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php
new file mode 100644
index 0000000..a04efab
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchCommandTransfer;
+use Guzzle\Service\Client;
+use Guzzle\Tests\Service\Mock\Command\MockCommand as Mc;
+
+/**
+ * @covers Guzzle\Batch\BatchCommandTransfer
+ */
+class BatchCommandTransferTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCreatesBatchesBasedOnClient()
+ {
+ $client1 = new Client('http://www.example.com');
+ $client2 = new Client('http://www.example.com');
+
+ $commands = array(new Mc(), new Mc(), new Mc(), new Mc(), new Mc());
+
+ $queue = new \SplQueue();
+ foreach ($commands as $i => $command) {
+ if ($i % 2) {
+ $command->setClient($client1);
+ } else {
+ $command->setClient($client2);
+ }
+ $queue[] = $command;
+ }
+
+ $batch = new BatchCommandTransfer(2);
+ $this->assertEquals(array(
+ array($commands[0], $commands[2]),
+ array($commands[4]),
+ array($commands[1], $commands[3])
+ ), $batch->createBatches($queue));
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresAllItemsAreCommands()
+ {
+ $queue = new \SplQueue();
+ $queue[] = 'foo';
+ $batch = new BatchCommandTransfer(2);
+ $batch->createBatches($queue);
+ }
+
+ public function testTransfersBatches()
+ {
+ $client = $this->getMockBuilder('Guzzle\Service\Client')
+ ->setMethods(array('send'))
+ ->getMock();
+ $client->expects($this->once())
+ ->method('send');
+ $command = new Mc();
+ $command->setClient($client);
+ $batch = new BatchCommandTransfer(2);
+ $batch->transfer(array($command));
+ }
+
+ public function testDoesNotTransfersEmptyBatches()
+ {
+ $batch = new BatchCommandTransfer(2);
+ $batch->transfer(array());
+ }
+
+ /**
+ * @expectedException Guzzle\Service\Exception\InconsistentClientTransferException
+ */
+ public function testEnsuresAllCommandsUseTheSameClient()
+ {
+ $batch = new BatchCommandTransfer(2);
+ $client1 = new Client('http://www.example.com');
+ $client2 = new Client('http://www.example.com');
+ $command1 = new Mc();
+ $command1->setClient($client1);
+ $command2 = new Mc();
+ $command2->setClient($client2);
+ $batch->transfer(array($command1, $command2));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php
new file mode 100644
index 0000000..dec7bd5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchRequestTransfer;
+use Guzzle\Http\Client;
+use Guzzle\Http\Curl\CurlMulti;
+
+/**
+ * @covers Guzzle\Batch\BatchRequestTransfer
+ */
+class BatchRequestTransferTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCreatesBatchesBasedOnCurlMultiHandles()
+ {
+ $client1 = new Client('http://www.example.com');
+ $client1->setCurlMulti(new CurlMulti());
+
+ $client2 = new Client('http://www.example.com');
+ $client2->setCurlMulti(new CurlMulti());
+
+ $request1 = $client1->get();
+ $request2 = $client2->get();
+ $request3 = $client1->get();
+ $request4 = $client2->get();
+ $request5 = $client1->get();
+
+ $queue = new \SplQueue();
+ $queue[] = $request1;
+ $queue[] = $request2;
+ $queue[] = $request3;
+ $queue[] = $request4;
+ $queue[] = $request5;
+
+ $batch = new BatchRequestTransfer(2);
+ $this->assertEquals(array(
+ array($request1, $request3),
+ array($request3),
+ array($request2, $request4)
+ ), $batch->createBatches($queue));
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresAllItemsAreRequests()
+ {
+ $queue = new \SplQueue();
+ $queue[] = 'foo';
+ $batch = new BatchRequestTransfer(2);
+ $batch->createBatches($queue);
+ }
+
+ public function testTransfersBatches()
+ {
+ $client = new Client('http://127.0.0.1:123');
+ $request = $client->get();
+ // For some reason... PHP unit clones the request, which emits a request.clone event. This causes the
+ // 'sorted' property of the event dispatcher to contain an array in the cloned request that is not present in
+ // the original.
+ $request->dispatch('request.clone');
+
+ $multi = $this->getMock('Guzzle\Http\Curl\CurlMultiInterface');
+ $client->setCurlMulti($multi);
+ $multi->expects($this->once())
+ ->method('add')
+ ->with($request);
+ $multi->expects($this->once())
+ ->method('send');
+
+ $batch = new BatchRequestTransfer(2);
+ $batch->transfer(array($request));
+ }
+
+ public function testDoesNotTransfersEmptyBatches()
+ {
+ $batch = new BatchRequestTransfer(2);
+ $batch->transfer(array());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php
new file mode 100644
index 0000000..5542228
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\BatchSizeDivisor;
+
+/**
+ * @covers Guzzle\Batch\BatchSizeDivisor
+ */
+class BatchSizeDivisorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDividesBatch()
+ {
+ $queue = new \SplQueue();
+ $queue[] = 'foo';
+ $queue[] = 'baz';
+ $queue[] = 'bar';
+ $d = new BatchSizeDivisor(3);
+ $this->assertEquals(3, $d->getSize());
+ $d->setSize(2);
+ $batches = $d->createBatches($queue);
+ $this->assertEquals(array(array('foo', 'baz'), array('bar')), $batches);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php
new file mode 100644
index 0000000..296f57a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\Batch;
+use Guzzle\Batch\Exception\BatchTransferException;
+
+/**
+ * @covers Guzzle\Batch\Batch
+ */
+class BatchTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private function getMockTransfer()
+ {
+ return $this->getMock('Guzzle\Batch\BatchTransferInterface');
+ }
+
+ private function getMockDivisor()
+ {
+ return $this->getMock('Guzzle\Batch\BatchDivisorInterface');
+ }
+
+ public function testAddsItemsToQueue()
+ {
+ $batch = new Batch($this->getMockTransfer(), $this->getMockDivisor());
+ $this->assertSame($batch, $batch->add('foo'));
+ $this->assertEquals(1, count($batch));
+ }
+
+ public function testFlushReturnsItems()
+ {
+ $transfer = $this->getMockTransfer();
+ $transfer->expects($this->exactly(2))
+ ->method('transfer');
+
+ $divisor = $this->getMockDivisor();
+ $divisor->expects($this->once())
+ ->method('createBatches')
+ ->will($this->returnValue(array(array('foo', 'baz'), array('bar'))));
+
+ $batch = new Batch($transfer, $divisor);
+
+ $batch->add('foo')->add('baz')->add('bar');
+ $items = $batch->flush();
+
+ $this->assertEquals(array('foo', 'baz', 'bar'), $items);
+ }
+
+ public function testThrowsExceptionContainingTheFailedBatch()
+ {
+ $called = 0;
+ $originalException = new \Exception('Foo!');
+
+ $transfer = $this->getMockTransfer();
+ $transfer->expects($this->exactly(2))
+ ->method('transfer')
+ ->will($this->returnCallback(function () use (&$called, $originalException) {
+ if (++$called == 2) {
+ throw $originalException;
+ }
+ }));
+
+ $divisor = $this->getMockDivisor();
+ $batch = new Batch($transfer, $divisor);
+
+ // PHPunit clones objects before passing them to a callback.
+ // Horrible hack to get around this!
+ $queue = $this->readAttribute($batch, 'queue');
+
+ $divisor->expects($this->once())
+ ->method('createBatches')
+ ->will($this->returnCallback(function ($batch) use ($queue) {
+ foreach ($queue as $item) {
+ $items[] = $item;
+ }
+ return array_chunk($items, 2);
+ }));
+
+ $batch->add('foo')->add('baz')->add('bar')->add('bee')->add('boo');
+ $this->assertFalse($batch->isEmpty());
+
+ try {
+ $items = $batch->flush();
+ $this->fail('Expected exception');
+ } catch (BatchTransferException $e) {
+ $this->assertEquals($originalException, $e->getPrevious());
+ $this->assertEquals(array('bar', 'bee'), array_values($e->getBatch()));
+ $this->assertEquals(1, count($batch));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php
new file mode 100644
index 0000000..fd810b1
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\ExceptionBufferingBatch;
+use Guzzle\Batch\Batch;
+use Guzzle\Batch\BatchSizeDivisor;
+
+/**
+ * @covers Guzzle\Batch\ExceptionBufferingBatch
+ */
+class ExceptionBufferingBatchTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testFlushesEntireBatchWhileBufferingErroredBatches()
+ {
+ $t = $this->getMockBuilder('Guzzle\Batch\BatchTransferInterface')
+ ->setMethods(array('transfer'))
+ ->getMock();
+
+ $d = new BatchSizeDivisor(1);
+ $batch = new Batch($t, $d);
+
+ $called = 0;
+ $t->expects($this->exactly(3))
+ ->method('transfer')
+ ->will($this->returnCallback(function ($batch) use (&$called) {
+ if (++$called === 2) {
+ throw new \Exception('Foo');
+ }
+ }));
+
+ $decorator = new ExceptionBufferingBatch($batch);
+ $decorator->add('foo')->add('baz')->add('bar');
+ $result = $decorator->flush();
+
+ $e = $decorator->getExceptions();
+ $this->assertEquals(1, count($e));
+ $this->assertEquals(array('baz'), $e[0]->getBatch());
+
+ $decorator->clearExceptions();
+ $this->assertEquals(0, count($decorator->getExceptions()));
+
+ $this->assertEquals(array('foo', 'bar'), $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php
new file mode 100644
index 0000000..9b37a48
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\FlushingBatch;
+use Guzzle\Batch\Batch;
+
+/**
+ * @covers Guzzle\Batch\FlushingBatch
+ */
+class FlushingBatchTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testFlushesWhenSizeMeetsThreshold()
+ {
+ $t = $this->getMock('Guzzle\Batch\BatchTransferInterface', array('transfer'));
+ $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface', array('createBatches'));
+
+ $batch = new Batch($t, $d);
+ $queue = $this->readAttribute($batch, 'queue');
+
+ $d->expects($this->exactly(2))
+ ->method('createBatches')
+ ->will($this->returnCallback(function () use ($queue) {
+ $items = array();
+ foreach ($queue as $item) {
+ $items[] = $item;
+ }
+ return array($items);
+ }));
+
+ $t->expects($this->exactly(2))
+ ->method('transfer');
+
+ $flush = new FlushingBatch($batch, 3);
+ $this->assertEquals(3, $flush->getThreshold());
+ $flush->setThreshold(2);
+ $flush->add('foo')->add('baz')->add('bar')->add('bee')->add('boo');
+ $this->assertEquals(1, count($flush));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php
new file mode 100644
index 0000000..60d6f95
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\HistoryBatch;
+use Guzzle\Batch\Batch;
+
+/**
+ * @covers Guzzle\Batch\HistoryBatch
+ */
+class HistoryBatchTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testMaintainsHistoryOfItemsAddedToBatch()
+ {
+ $batch = new Batch(
+ $this->getMock('Guzzle\Batch\BatchTransferInterface'),
+ $this->getMock('Guzzle\Batch\BatchDivisorInterface')
+ );
+
+ $history = new HistoryBatch($batch);
+ $history->add('foo')->add('baz');
+ $this->assertEquals(array('foo', 'baz'), $history->getHistory());
+ $history->clearHistory();
+ $this->assertEquals(array(), $history->getHistory());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php
new file mode 100644
index 0000000..69a8900
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Guzzle\Tests\Batch;
+
+use Guzzle\Batch\NotifyingBatch;
+use Guzzle\Batch\Batch;
+
+/**
+ * @covers Guzzle\Batch\NotifyingBatch
+ */
+class NotifyingBatchTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testNotifiesAfterFlush()
+ {
+ $batch = $this->getMock('Guzzle\Batch\Batch', array('flush'), array(
+ $this->getMock('Guzzle\Batch\BatchTransferInterface'),
+ $this->getMock('Guzzle\Batch\BatchDivisorInterface')
+ ));
+
+ $batch->expects($this->once())
+ ->method('flush')
+ ->will($this->returnValue(array('foo', 'baz')));
+
+ $data = array();
+ $decorator = new NotifyingBatch($batch, function ($batch) use (&$data) {
+ $data[] = $batch;
+ });
+
+ $decorator->add('foo')->add('baz');
+ $decorator->flush();
+ $this->assertEquals(array(array('foo', 'baz')), $data);
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresCallableIsValid()
+ {
+ $batch = new Batch(
+ $this->getMock('Guzzle\Batch\BatchTransferInterface'),
+ $this->getMock('Guzzle\Batch\BatchDivisorInterface')
+ );
+ $decorator = new NotifyingBatch($batch, 'foo');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php
new file mode 100644
index 0000000..c4140a9
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Guzzle\Tests\Cache;
+
+use Guzzle\Cache\CacheAdapterFactory;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Doctrine\Common\Cache\ArrayCache;
+use Zend\Cache\StorageFactory;
+
+/**
+ * @covers Guzzle\Cache\CacheAdapterFactory
+ */
+class CacheAdapterFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var ArrayCache */
+ private $cache;
+
+ /** @var DoctrineCacheAdapter */
+ private $adapter;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setup()
+ {
+ parent::setUp();
+ $this->cache = new ArrayCache();
+ $this->adapter = new DoctrineCacheAdapter($this->cache);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresConfigIsObject()
+ {
+ CacheAdapterFactory::fromCache(array());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresKnownType()
+ {
+ CacheAdapterFactory::fromCache(new \stdClass());
+ }
+
+ public function cacheProvider()
+ {
+ return array(
+ array(new DoctrineCacheAdapter(new ArrayCache()), 'Guzzle\Cache\DoctrineCacheAdapter'),
+ array(new ArrayCache(), 'Guzzle\Cache\DoctrineCacheAdapter'),
+ array(StorageFactory::factory(array('adapter' => 'memory')), 'Guzzle\Cache\Zf2CacheAdapter'),
+ );
+ }
+
+ /**
+ * @dataProvider cacheProvider
+ */
+ public function testCreatesNullCacheAdapterByDefault($cache, $type)
+ {
+ $adapter = CacheAdapterFactory::fromCache($cache);
+ $this->assertInstanceOf($type, $adapter);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php
new file mode 100644
index 0000000..3e30ddd
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Guzzle\Tests\Cache;
+
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Doctrine\Common\Cache\ArrayCache;
+
+/**
+ * @covers Guzzle\Cache\DoctrineCacheAdapter
+ * @covers Guzzle\Cache\AbstractCacheAdapter
+ */
+class CacheAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var ArrayCache */
+ private $cache;
+
+ /** @var DoctrineCacheAdapter */
+ private $adapter;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->cache = new ArrayCache();
+ $this->adapter = new DoctrineCacheAdapter($this->cache);
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->adapter = null;
+ $this->cache = null;
+ parent::tearDown();
+ }
+
+ public function testGetCacheObject()
+ {
+ $this->assertEquals($this->cache, $this->adapter->getCacheObject());
+ }
+
+ public function testSave()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ }
+
+ public function testFetch()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ $this->assertEquals('data', $this->adapter->fetch('test'));
+ }
+
+ public function testContains()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ $this->assertTrue($this->adapter->contains('test'));
+ }
+
+ public function testDelete()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ $this->assertTrue($this->adapter->delete('test'));
+ $this->assertFalse($this->adapter->contains('test'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php
new file mode 100644
index 0000000..12de65b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Guzzle\Tests\Cache;
+
+use Guzzle\Cache\ClosureCacheAdapter;
+
+/**
+ * @covers Guzzle\Cache\ClosureCacheAdapter
+ */
+class ClosureCacheAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var ClosureCacheAdapter */
+ private $adapter;
+
+ /** Array of callables to use for testing */
+ private $callables;
+
+ /** Cache data for testing */
+ public $data = array();
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $that = $this;
+ $this->callables = array(
+ 'contains' => function($id, $options = array()) use ($that) {
+ return array_key_exists($id, $that->data);
+ },
+ 'delete' => function($id, $options = array()) use ($that) {
+ unset($that->data[$id]);
+ return true;
+ },
+ 'fetch' => function($id, $options = array()) use ($that) {
+ return array_key_exists($id, $that->data) ? $that->data[$id] : null;
+ },
+ 'save' => function($id, $data, $lifeTime, $options = array()) use ($that) {
+ $that->data[$id] = $data;
+ return true;
+ }
+ );
+
+ $this->adapter = new ClosureCacheAdapter($this->callables);
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->cache = null;
+ $this->callables = null;
+ parent::tearDown();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testEnsuresCallablesArePresent()
+ {
+ $callables = $this->callables;
+ unset($callables['delete']);
+ $cache = new ClosureCacheAdapter($callables);
+ }
+
+ public function testAllCallablesMustBePresent()
+ {
+ $cache = new ClosureCacheAdapter($this->callables);
+ }
+
+ public function testCachesDataUsingCallables()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ $this->assertEquals('data', $this->adapter->fetch('test'));
+ }
+
+ public function testChecksIfCacheContainsKeys()
+ {
+ $this->adapter->save('test', 'data', 1000);
+ $this->assertTrue($this->adapter->contains('test'));
+ $this->assertFalse($this->adapter->contains('foo'));
+ }
+
+ public function testDeletesFromCacheByKey()
+ {
+ $this->adapter->save('test', 'data', 1000);
+ $this->assertTrue($this->adapter->contains('test'));
+ $this->adapter->delete('test');
+ $this->assertFalse($this->adapter->contains('test'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php
new file mode 100644
index 0000000..e05df3f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Guzzle\Tests\Common\Cache;
+
+use Guzzle\Cache\NullCacheAdapter;
+
+/**
+ * @covers Guzzle\Cache\NullCacheAdapter
+ */
+class NullCacheAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testNullCacheAdapter()
+ {
+ $c = new NullCacheAdapter();
+ $this->assertEquals(false, $c->contains('foo'));
+ $this->assertEquals(true, $c->delete('foo'));
+ $this->assertEquals(false, $c->fetch('foo'));
+ $this->assertEquals(true, $c->save('foo', 'bar'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php
new file mode 100644
index 0000000..9077c12
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Guzzle\Tests\Cache;
+
+use Guzzle\Cache\Zf2CacheAdapter;
+use Zend\Cache\StorageFactory;
+
+/**
+ * @covers Guzzle\Cache\Zf2CacheAdapter
+ */
+class Zf2CacheAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private $cache;
+ private $adapter;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->cache = StorageFactory::factory(array(
+ 'adapter' => 'memory'
+ ));
+ $this->adapter = new Zf2CacheAdapter($this->cache);
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->adapter = null;
+ $this->cache = null;
+ parent::tearDown();
+ }
+
+ public function testCachesDataUsingCallables()
+ {
+ $this->assertTrue($this->adapter->save('test', 'data', 1000));
+ $this->assertEquals('data', $this->adapter->fetch('test'));
+ }
+
+ public function testChecksIfCacheContainsKeys()
+ {
+ $this->adapter->save('test', 'data', 1000);
+ $this->assertTrue($this->adapter->contains('test'));
+ $this->assertFalse($this->adapter->contains('foo'));
+ }
+
+ public function testDeletesFromCacheByKey()
+ {
+ $this->adapter->save('test', 'data', 1000);
+ $this->assertTrue($this->adapter->contains('test'));
+ $this->adapter->delete('test');
+ $this->assertFalse($this->adapter->contains('test'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php
new file mode 100644
index 0000000..19d12e6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Guzzle\Tests\Common;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\AbstractHasDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * @covers Guzzle\Common\AbstractHasDispatcher
+ */
+class AbstractHasAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDoesNotRequireRegisteredEvents()
+ {
+ $this->assertEquals(array(), AbstractHasDispatcher::getAllEvents());
+ }
+
+ public function testAllowsDispatcherToBeInjected()
+ {
+ $d = new EventDispatcher();
+ $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher');
+ $this->assertSame($mock, $mock->setEventDispatcher($d));
+ $this->assertSame($d, $mock->getEventDispatcher());
+ }
+
+ public function testCreatesDefaultEventDispatcherIfNeeded()
+ {
+ $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher');
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventDispatcher', $mock->getEventDispatcher());
+ }
+
+ public function testHelperDispatchesEvents()
+ {
+ $data = array();
+ $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher');
+ $mock->getEventDispatcher()->addListener('test', function(Event $e) use (&$data) {
+ $data = $e->getIterator()->getArrayCopy();
+ });
+ $mock->dispatch('test', array(
+ 'param' => 'abc'
+ ));
+ $this->assertEquals(array(
+ 'param' => 'abc',
+ ), $data);
+ }
+
+ public function testHelperAttachesSubscribers()
+ {
+ $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher');
+ $subscriber = $this->getMockForAbstractClass('Symfony\Component\EventDispatcher\EventSubscriberInterface');
+
+ $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher')
+ ->setMethods(array('addSubscriber'))
+ ->getMock();
+
+ $dispatcher->expects($this->once())
+ ->method('addSubscriber');
+
+ $mock->setEventDispatcher($dispatcher);
+ $mock->addSubscriber($subscriber);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php
new file mode 100644
index 0000000..0648a02
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php
@@ -0,0 +1,529 @@
+<?php
+
+namespace Guzzle\Tests\Common;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\QueryString;
+
+/**
+ * @covers Guzzle\Common\Collection
+ */
+class CollectionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Collection */
+ protected $coll;
+
+ protected function setUp()
+ {
+ $this->coll = new Collection();
+ }
+
+ public function testConstructorCanBeCalledWithNoParams()
+ {
+ $this->coll = new Collection();
+ $p = $this->coll->getAll();
+ $this->assertEmpty($p, '-> Collection must be empty when no data is passed');
+ }
+
+ public function testConstructorCanBeCalledWithParams()
+ {
+ $testData = array(
+ 'test' => 'value',
+ 'test_2' => 'value2'
+ );
+ $this->coll = new Collection($testData);
+ $this->assertEquals($this->coll->getAll(), $testData, '-> getAll() must return the data passed in the constructor');
+ $this->assertEquals($this->coll->getAll(), $this->coll->toArray());
+ }
+
+ public function testImplementsIteratorAggregate()
+ {
+ $this->coll->set('key', 'value');
+ $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator());
+ $this->assertEquals(1, count($this->coll));
+ $total = 0;
+ foreach ($this->coll as $key => $value) {
+ $this->assertEquals('key', $key);
+ $this->assertEquals('value', $value);
+ $total++;
+ }
+ $this->assertEquals(1, $total);
+ }
+
+ public function testCanAddValuesToExistingKeysByUsingArray()
+ {
+ $this->coll->add('test', 'value1');
+ $this->assertEquals($this->coll->getAll(), array('test' => 'value1'));
+ $this->coll->add('test', 'value2');
+ $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2')));
+ $this->coll->add('test', 'value3');
+ $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2', 'value3')));
+ }
+
+ public function testHandlesMergingInDisparateDataSources()
+ {
+ $params = array(
+ 'test' => 'value1',
+ 'test2' => 'value2',
+ 'test3' => array('value3', 'value4')
+ );
+ $this->coll->merge($params);
+ $this->assertEquals($this->coll->getAll(), $params);
+
+ // Pass the same object to itself
+ $this->assertEquals($this->coll->merge($this->coll), $this->coll);
+ }
+
+ public function testCanClearAllDataOrSpecificKeys()
+ {
+ $this->coll->merge(array(
+ 'test' => 'value1',
+ 'test2' => 'value2'
+ ));
+
+ // Clear a specific parameter by name
+ $this->coll->remove('test');
+
+ $this->assertEquals($this->coll->getAll(), array(
+ 'test2' => 'value2'
+ ));
+
+ // Clear all parameters
+ $this->coll->clear();
+
+ $this->assertEquals($this->coll->getAll(), array());
+ }
+
+ public function testGetsValuesByKey()
+ {
+ $this->assertNull($this->coll->get('test'));
+ $this->coll->add('test', 'value');
+ $this->assertEquals('value', $this->coll->get('test'));
+ $this->coll->set('test2', 'v2');
+ $this->coll->set('test3', 'v3');
+ $this->assertEquals(array(
+ 'test' => 'value',
+ 'test2' => 'v2'
+ ), $this->coll->getAll(array('test', 'test2')));
+ }
+
+ public function testProvidesKeys()
+ {
+ $this->assertEquals(array(), $this->coll->getKeys());
+ $this->coll->merge(array(
+ 'test1' => 'value1',
+ 'test2' => 'value2'
+ ));
+ $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys());
+ // Returns the cached array previously returned
+ $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys());
+ $this->coll->remove('test1');
+ $this->assertEquals(array('test2'), $this->coll->getKeys());
+ $this->coll->add('test3', 'value3');
+ $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys());
+ }
+
+ public function testChecksIfHasKey()
+ {
+ $this->assertFalse($this->coll->hasKey('test'));
+ $this->coll->add('test', 'value');
+ $this->assertEquals(true, $this->coll->hasKey('test'));
+ $this->coll->add('test2', 'value2');
+ $this->assertEquals(true, $this->coll->hasKey('test'));
+ $this->assertEquals(true, $this->coll->hasKey('test2'));
+ $this->assertFalse($this->coll->hasKey('testing'));
+ $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk'));
+ }
+
+ public function testChecksIfHasValue()
+ {
+ $this->assertFalse($this->coll->hasValue('value'));
+ $this->coll->add('test', 'value');
+ $this->assertEquals('test', $this->coll->hasValue('value'));
+ $this->coll->add('test2', 'value2');
+ $this->assertEquals('test', $this->coll->hasValue('value'));
+ $this->assertEquals('test2', $this->coll->hasValue('value2'));
+ $this->assertFalse($this->coll->hasValue('val'));
+ }
+
+ public function testCanGetAllValuesByArray()
+ {
+ $this->coll->add('foo', 'bar');
+ $this->coll->add('tEsT', 'value');
+ $this->coll->add('tesTing', 'v2');
+ $this->coll->add('key', 'v3');
+ $this->assertNull($this->coll->get('test'));
+ $this->assertEquals(array(
+ 'foo' => 'bar',
+ 'tEsT' => 'value',
+ 'tesTing' => 'v2'
+ ), $this->coll->getAll(array(
+ 'foo', 'tesTing', 'tEsT'
+ )));
+ }
+
+ public function testImplementsCount()
+ {
+ $data = new Collection();
+ $this->assertEquals(0, $data->count());
+ $data->add('key', 'value');
+ $this->assertEquals(1, count($data));
+ $data->add('key', 'value2');
+ $this->assertEquals(1, count($data));
+ $data->add('key_2', 'value3');
+ $this->assertEquals(2, count($data));
+ }
+
+ public function testAddParamsByMerging()
+ {
+ $params = array(
+ 'test' => 'value1',
+ 'test2' => 'value2',
+ 'test3' => array('value3', 'value4')
+ );
+
+ // Add some parameters
+ $this->coll->merge($params);
+
+ // Add more parameters by merging them in
+ $this->coll->merge(array(
+ 'test' => 'another',
+ 'different_key' => 'new value'
+ ));
+
+ $this->assertEquals(array(
+ 'test' => array('value1', 'another'),
+ 'test2' => 'value2',
+ 'test3' => array('value3', 'value4'),
+ 'different_key' => 'new value'
+ ), $this->coll->getAll());
+ }
+
+ public function testAllowsFunctionalFilter()
+ {
+ $this->coll->merge(array(
+ 'fruit' => 'apple',
+ 'number' => 'ten',
+ 'prepositions' => array('about', 'above', 'across', 'after'),
+ 'same_number' => 'ten'
+ ));
+
+ $filtered = $this->coll->filter(function($key, $value) {
+ return $value == 'ten';
+ });
+
+ $this->assertNotEquals($filtered, $this->coll);
+
+ $this->assertEquals(array(
+ 'number' => 'ten',
+ 'same_number' => 'ten'
+ ), $filtered->getAll());
+ }
+
+ public function testAllowsFunctionalMapping()
+ {
+ $this->coll->merge(array(
+ 'number_1' => 1,
+ 'number_2' => 2,
+ 'number_3' => 3
+ ));
+
+ $mapped = $this->coll->map(function($key, $value) {
+ return $value * $value;
+ });
+
+ $this->assertNotEquals($mapped, $this->coll);
+
+ $this->assertEquals(array(
+ 'number_1' => 1,
+ 'number_2' => 4,
+ 'number_3' => 9
+ ), $mapped->getAll());
+ }
+
+ public function testImplementsArrayAccess()
+ {
+ $this->coll->merge(array(
+ 'k1' => 'v1',
+ 'k2' => 'v2'
+ ));
+
+ $this->assertTrue($this->coll->offsetExists('k1'));
+ $this->assertFalse($this->coll->offsetExists('Krull'));
+
+ $this->coll->offsetSet('k3', 'v3');
+ $this->assertEquals('v3', $this->coll->offsetGet('k3'));
+ $this->assertEquals('v3', $this->coll->get('k3'));
+
+ $this->coll->offsetUnset('k1');
+ $this->assertFalse($this->coll->offsetExists('k1'));
+ }
+
+ public function testUsesStaticWhenCreatingNew()
+ {
+ $qs = new QueryString(array(
+ 'a' => 'b',
+ 'c' => 'd'
+ ));
+
+ $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->map(function($a, $b) {}));
+ $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->map(function($a, $b) {}, array(), false));
+
+ $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->filter(function($a, $b) {}));
+ $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->filter(function($a, $b) {}, false));
+ }
+
+ public function testCanReplaceAllData()
+ {
+ $this->assertSame($this->coll, $this->coll->replace(array(
+ 'a' => '123'
+ )));
+
+ $this->assertEquals(array(
+ 'a' => '123'
+ ), $this->coll->getAll());
+ }
+
+ public function dataProvider()
+ {
+ return array(
+ array('this_is_a_test', '{a}_is_a_{b}', array(
+ 'a' => 'this',
+ 'b' => 'test'
+ )),
+ array('this_is_a_test', '{abc}_is_a_{0}', array(
+ 'abc' => 'this',
+ 0 => 'test'
+ )),
+ array('this_is_a_test', '{abc}_is_a_{0}', array(
+ 'abc' => 'this',
+ 0 => 'test'
+ )),
+ array('this_is_a_test', 'this_is_a_test', array(
+ 'abc' => 'this'
+ )),
+ array('{abc}_is_{not_found}a_{0}', '{abc}_is_{not_found}a_{0}', array())
+ );
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testInjectsConfigData($output, $input, $config)
+ {
+ $collection = new Collection($config);
+ $this->assertEquals($output, $collection->inject($input));
+ }
+
+ public function testCanSearchByKey()
+ {
+ $collection = new Collection(array(
+ 'foo' => 'bar',
+ 'BaZ' => 'pho'
+ ));
+
+ $this->assertEquals('foo', $collection->keySearch('FOO'));
+ $this->assertEquals('BaZ', $collection->keySearch('baz'));
+ $this->assertEquals(false, $collection->keySearch('Bar'));
+ }
+
+ public function testPreparesFromConfig()
+ {
+ $c = Collection::fromConfig(array(
+ 'a' => '123',
+ 'base_url' => 'http://www.test.com/'
+ ), array(
+ 'a' => 'xyz',
+ 'b' => 'lol'
+ ), array('a'));
+
+ $this->assertInstanceOf('Guzzle\Common\Collection', $c);
+ $this->assertEquals(array(
+ 'a' => '123',
+ 'b' => 'lol',
+ 'base_url' => 'http://www.test.com/'
+ ), $c->getAll());
+
+ try {
+ $c = Collection::fromConfig(array(), array(), array('a'));
+ $this->fail('Exception not throw when missing config');
+ } catch (InvalidArgumentException $e) {
+ }
+ }
+
+ function falseyDataProvider()
+ {
+ return array(
+ array(false, false),
+ array(null, null),
+ array('', ''),
+ array(array(), array()),
+ array(0, 0),
+ );
+ }
+
+ /**
+ * @dataProvider falseyDataProvider
+ */
+ public function testReturnsCorrectData($a, $b)
+ {
+ $c = new Collection(array('value' => $a));
+ $this->assertSame($b, $c->get('value'));
+ }
+
+ public function testRetrievesNestedKeysUsingPath()
+ {
+ $data = array(
+ 'foo' => 'bar',
+ 'baz' => array(
+ 'mesa' => array(
+ 'jar' => 'jar'
+ )
+ )
+ );
+ $collection = new Collection($data);
+ $this->assertEquals('bar', $collection->getPath('foo'));
+ $this->assertEquals('jar', $collection->getPath('baz/mesa/jar'));
+ $this->assertNull($collection->getPath('wewewf'));
+ $this->assertNull($collection->getPath('baz/mesa/jar/jar'));
+ }
+
+ public function testFalseyKeysStillDescend()
+ {
+ $collection = new Collection(array(
+ '0' => array(
+ 'a' => 'jar'
+ ),
+ 1 => 'other'
+ ));
+ $this->assertEquals('jar', $collection->getPath('0/a'));
+ $this->assertEquals('other', $collection->getPath('1'));
+ }
+
+ public function getPathProvider()
+ {
+ $data = array(
+ 'foo' => 'bar',
+ 'baz' => array(
+ 'mesa' => array(
+ 'jar' => 'jar',
+ 'array' => array('a', 'b', 'c')
+ ),
+ 'bar' => array(
+ 'baz' => 'bam',
+ 'array' => array('d', 'e', 'f')
+ )
+ ),
+ 'bam' => array(
+ array('foo' => 1),
+ array('foo' => 2),
+ array('array' => array('h', 'i'))
+ )
+ );
+ $c = new Collection($data);
+
+ return array(
+ // Simple path selectors
+ array($c, 'foo', 'bar'),
+ array($c, 'baz', $data['baz']),
+ array($c, 'bam', $data['bam']),
+ array($c, 'baz/mesa', $data['baz']['mesa']),
+ array($c, 'baz/mesa/jar', 'jar'),
+ // Merge everything two levels under baz
+ array($c, 'baz/*', array(
+ 'jar' => 'jar',
+ 'array' => array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array']),
+ 'baz' => 'bam'
+ )),
+ // Does not barf on missing keys
+ array($c, 'fefwfw', null),
+ // Does not barf when a wildcard does not resolve correctly
+ array($c, '*/*/*/*/*/wefwfe', array()),
+ // Allows custom separator
+ array($c, '*|mesa', $data['baz']['mesa'], '|'),
+ // Merge all 'array' keys two levels under baz (the trailing * does not hurt the results)
+ array($c, 'baz/*/array/*', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])),
+ // Merge all 'array' keys two levels under baz
+ array($c, 'baz/*/array', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])),
+ array($c, 'baz/mesa/array', $data['baz']['mesa']['array']),
+ // Having a trailing * does not hurt the results
+ array($c, 'baz/mesa/array/*', $data['baz']['mesa']['array']),
+ // Merge of anything one level deep
+ array($c, '*', array_merge(array('bar'), $data['baz'], $data['bam'])),
+ // Funky merge of anything two levels deep
+ array($c, '*/*', array(
+ 'jar' => 'jar',
+ 'array' => array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'),
+ 'baz' => 'bam',
+ 'foo' => array(1, 2)
+ )),
+ // Funky merge of all 'array' keys that are two levels deep
+ array($c, '*/*/array', array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'))
+ );
+ }
+
+ /**
+ * @dataProvider getPathProvider
+ */
+ public function testGetPath(Collection $c, $path, $expected, $separator = '/')
+ {
+ $this->assertEquals($expected, $c->getPath($path, $separator));
+ }
+
+ public function testOverridesSettings()
+ {
+ $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
+ $c->overwriteWith(array('foo' => 10, 'bar' => 300));
+ $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll());
+ }
+
+ public function testOverwriteWithCollection()
+ {
+ $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
+ $b = new Collection(array('foo' => 10, 'bar' => 300));
+ $c->overwriteWith($b);
+ $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll());
+ }
+
+ public function testOverwriteWithTraversable()
+ {
+ $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
+ $b = new Collection(array('foo' => 10, 'bar' => 300));
+ $c->overwriteWith($b->getIterator());
+ $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll());
+ }
+
+ public function testCanSetNestedPathValueThatDoesNotExist()
+ {
+ $c = new Collection(array());
+ $c->setPath('foo/bar/baz/123', 'hi');
+ $this->assertEquals('hi', $c['foo']['bar']['baz']['123']);
+ }
+
+ public function testCanSetNestedPathValueThatExists()
+ {
+ $c = new Collection(array('foo' => array('bar' => 'test')));
+ $c->setPath('foo/bar', 'hi');
+ $this->assertEquals('hi', $c['foo']['bar']);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ */
+ public function testVerifiesNestedPathIsValidAtExactLevel()
+ {
+ $c = new Collection(array('foo' => 'bar'));
+ $c->setPath('foo/bar', 'hi');
+ $this->assertEquals('hi', $c['foo']['bar']);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ */
+ public function testVerifiesThatNestedPathIsValidAtAnyLevel()
+ {
+ $c = new Collection(array('foo' => 'bar'));
+ $c->setPath('foo/bar/baz', 'test');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php
new file mode 100644
index 0000000..5484e14
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Guzzle\Tests\Common;
+
+use Guzzle\Common\Event;
+
+/**
+ * @covers Guzzle\Common\Event
+ */
+class EventTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @return Event
+ */
+ private function getEvent()
+ {
+ return new Event(array(
+ 'test' => '123',
+ 'other' => '456',
+ 'event' => 'test.notify'
+ ));
+ }
+
+ public function testAllowsParameterInjection()
+ {
+ $event = new Event(array(
+ 'test' => '123'
+ ));
+ $this->assertEquals('123', $event['test']);
+ }
+
+ public function testImplementsArrayAccess()
+ {
+ $event = $this->getEvent();
+ $this->assertEquals('123', $event['test']);
+ $this->assertNull($event['foobar']);
+
+ $this->assertTrue($event->offsetExists('test'));
+ $this->assertFalse($event->offsetExists('foobar'));
+
+ unset($event['test']);
+ $this->assertFalse($event->offsetExists('test'));
+
+ $event['test'] = 'new';
+ $this->assertEquals('new', $event['test']);
+ }
+
+ public function testImplementsIteratorAggregate()
+ {
+ $event = $this->getEvent();
+ $this->assertInstanceOf('ArrayIterator', $event->getIterator());
+ }
+
+ public function testConvertsToArray()
+ {
+ $this->assertEquals(array(
+ 'test' => '123',
+ 'other' => '456',
+ 'event' => 'test.notify'
+ ), $this->getEvent()->toArray());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php
new file mode 100644
index 0000000..c72a2a6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Common\Exception;
+
+use Guzzle\Batch\Exception\BatchTransferException;
+
+class BatchTransferExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testContainsBatch()
+ {
+ $e = new \Exception('Baz!');
+ $t = $this->getMock('Guzzle\Batch\BatchTransferInterface');
+ $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface');
+ $transferException = new BatchTransferException(array('foo'), array(1, 2), $e, $t, $d);
+ $this->assertEquals(array('foo'), $transferException->getBatch());
+ $this->assertSame($t, $transferException->getTransferStrategy());
+ $this->assertSame($d, $transferException->getDivisorStrategy());
+ $this->assertSame($e, $transferException->getPrevious());
+ $this->assertEquals(array(1, 2), $transferException->getTransferredItems());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php
new file mode 100644
index 0000000..2aecf2a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Guzzle\Tests\Common\Exception;
+
+use Guzzle\Common\Exception\ExceptionCollection;
+
+class ExceptionCollectionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private function getExceptions()
+ {
+ return array(
+ new \Exception('Test'),
+ new \Exception('Testing')
+ );
+ }
+
+ public function testAggregatesExceptions()
+ {
+ $e = new ExceptionCollection();
+ $exceptions = $this->getExceptions();
+ $e->add($exceptions[0]);
+ $e->add($exceptions[1]);
+ $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $e->getMessage());
+ $this->assertContains(" Test\n\n #0 ./", $e->getMessage());
+ $this->assertSame($exceptions[0], $e->getFirst());
+ }
+
+ public function testCanSetExceptions()
+ {
+ $ex = new \Exception('foo');
+ $e = new ExceptionCollection();
+ $e->setExceptions(array($ex));
+ $this->assertSame($ex, $e->getFirst());
+ }
+
+ public function testActsAsArray()
+ {
+ $e = new ExceptionCollection();
+ $exceptions = $this->getExceptions();
+ $e->add($exceptions[0]);
+ $e->add($exceptions[1]);
+ $this->assertEquals(2, count($e));
+ $this->assertEquals($exceptions, $e->getIterator()->getArrayCopy());
+ }
+
+ public function testCanAddSelf()
+ {
+ $e1 = new ExceptionCollection();
+ $e1->add(new \Exception("Test"));
+ $e2 = new ExceptionCollection('Meta description!');
+ $e2->add(new \Exception("Test 2"));
+ $e3 = new ExceptionCollection();
+ $e3->add(new \Exception('Baz'));
+ $e2->add($e3);
+ $e1->add($e2);
+ $message = $e1->getMessage();
+ $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message);
+ $this->assertContains("\n Test\n\n #0 ", $message);
+ $this->assertContains("\n\n(Guzzle\\Common\\Exception\\ExceptionCollection) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message);
+ $this->assertContains("\n\n Meta description!\n\n", $message);
+ $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message);
+ $this->assertContains("\n Test 2\n\n #0 ", $message);
+ $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message);
+ $this->assertContains(" Baz\n\n #0", $message);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php
new file mode 100644
index 0000000..c3a81d1
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Tests\Common;
+
+use Guzzle\Common\Version;
+
+/**
+ * @covers Guzzle\Common\Version
+ */
+class VersionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Deprecated
+ */
+ public function testEmitsWarnings()
+ {
+ Version::$emitWarnings = true;
+ Version::warn('testing!');
+ }
+
+ public function testCanSilenceWarnings()
+ {
+ Version::$emitWarnings = false;
+ Version::warn('testing!');
+ Version::$emitWarnings = true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/GuzzleTestCase.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/GuzzleTestCase.php
new file mode 100644
index 0000000..d89c5f0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/GuzzleTestCase.php
@@ -0,0 +1,235 @@
+<?php
+
+namespace Guzzle\Tests;
+
+use Guzzle\Common\HasDispatcherInterface;
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Tests\Http\Message\HeaderComparison;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Http\Client;
+use Guzzle\Service\Builder\ServiceBuilderInterface;
+use Guzzle\Service\Builder\ServiceBuilder;
+use Guzzle\Tests\Mock\MockObserver;
+use Guzzle\Tests\Http\Server;
+use RuntimeException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Base testcase class for all Guzzle testcases.
+ */
+abstract class GuzzleTestCase extends \PHPUnit_Framework_TestCase
+{
+ protected static $mockBasePath;
+ public static $serviceBuilder;
+ public static $server;
+
+ private $requests = array();
+ public $mockObserver;
+
+ /**
+ * Get the global server object used throughout the unit tests of Guzzle
+ *
+ * @return Server
+ */
+ public static function getServer()
+ {
+ if (!self::$server) {
+ self::$server = new Server();
+ if (self::$server->isRunning()) {
+ self::$server->flush();
+ } else {
+ self::$server->start();
+ }
+ }
+
+ return self::$server;
+ }
+
+ /**
+ * Set the service builder to use for tests
+ *
+ * @param ServiceBuilderInterface $builder Service builder
+ */
+ public static function setServiceBuilder(ServiceBuilderInterface $builder)
+ {
+ self::$serviceBuilder = $builder;
+ }
+
+ /**
+ * Get a service builder object that can be used throughout the service tests
+ *
+ * @return ServiceBuilder
+ */
+ public static function getServiceBuilder()
+ {
+ if (!self::$serviceBuilder) {
+ throw new RuntimeException('No service builder has been set via setServiceBuilder()');
+ }
+
+ return self::$serviceBuilder;
+ }
+
+ /**
+ * Check if an event dispatcher has a subscriber
+ *
+ * @param HasDispatcherInterface $dispatcher
+ * @param EventSubscriberInterface $subscriber
+ *
+ * @return bool
+ */
+ protected function hasSubscriber(HasDispatcherInterface $dispatcher, EventSubscriberInterface $subscriber)
+ {
+ $class = get_class($subscriber);
+ $all = array_keys(call_user_func(array($class, 'getSubscribedEvents')));
+
+ foreach ($all as $i => $event) {
+ foreach ($dispatcher->getEventDispatcher()->getListeners($event) as $e) {
+ if ($e[0] === $subscriber) {
+ unset($all[$i]);
+ break;
+ }
+ }
+ }
+
+ return count($all) == 0;
+ }
+
+ /**
+ * Get a wildcard observer for an event dispatcher
+ *
+ * @param HasDispatcherInterface $hasDispatcher
+ *
+ * @return MockObserver
+ */
+ public function getWildcardObserver(HasDispatcherInterface $hasDispatcher)
+ {
+ $class = get_class($hasDispatcher);
+ $o = new MockObserver();
+ $events = call_user_func(array($class, 'getAllEvents'));
+ foreach ($events as $event) {
+ $hasDispatcher->getEventDispatcher()->addListener($event, array($o, 'update'));
+ }
+
+ return $o;
+ }
+
+ /**
+ * Set the mock response base path
+ *
+ * @param string $path Path to mock response folder
+ *
+ * @return GuzzleTestCase
+ */
+ public static function setMockBasePath($path)
+ {
+ self::$mockBasePath = $path;
+ }
+
+ /**
+ * Mark a request as being mocked
+ *
+ * @param RequestInterface $request
+ *
+ * @return self
+ */
+ public function addMockedRequest(RequestInterface $request)
+ {
+ $this->requests[] = $request;
+
+ return $this;
+ }
+
+ /**
+ * Get all of the mocked requests
+ *
+ * @return array
+ */
+ public function getMockedRequests()
+ {
+ return $this->requests;
+ }
+
+ /**
+ * Get a mock response for a client by mock file name
+ *
+ * @param string $path Relative path to the mock response file
+ *
+ * @return Response
+ */
+ public function getMockResponse($path)
+ {
+ return $path instanceof Response
+ ? $path
+ : MockPlugin::getMockFile(self::$mockBasePath . DIRECTORY_SEPARATOR . $path);
+ }
+
+ /**
+ * Set a mock response from a mock file on the next client request.
+ *
+ * This method assumes that mock response files are located under the
+ * Command/Mock/ directory of the Service being tested
+ * (e.g. Unfuddle/Command/Mock/). A mock response is added to the next
+ * request sent by the client.
+ *
+ * @param Client $client Client object to modify
+ * @param string $paths Path to files within the Mock folder of the service
+ *
+ * @return MockPlugin returns the created mock plugin
+ */
+ public function setMockResponse(Client $client, $paths)
+ {
+ $this->requests = array();
+ $that = $this;
+ $mock = new MockPlugin(null, true);
+ $client->getEventDispatcher()->removeSubscriber($mock);
+ $mock->getEventDispatcher()->addListener('mock.request', function(Event $event) use ($that) {
+ $that->addMockedRequest($event['request']);
+ });
+
+ if ($paths instanceof Response) {
+ // A single response instance has been specified, create an array with that instance
+ // as the only element for the following loop to work as expected
+ $paths = array($paths);
+ }
+
+ foreach ((array) $paths as $path) {
+ $mock->addResponse($this->getMockResponse($path));
+ }
+
+ $client->getEventDispatcher()->addSubscriber($mock);
+
+ return $mock;
+ }
+
+ /**
+ * Compare HTTP headers and use special markup to filter values
+ * A header prefixed with '!' means it must not exist
+ * A header prefixed with '_' means it must be ignored
+ * A header value of '*' means anything after the * will be ignored
+ *
+ * @param array $filteredHeaders Array of special headers
+ * @param array $actualHeaders Array of headers to check against
+ *
+ * @return array|bool Returns an array of the differences or FALSE if none
+ */
+ public function compareHeaders($filteredHeaders, $actualHeaders)
+ {
+ $comparison = new HeaderComparison();
+
+ return $comparison->compare($filteredHeaders, $actualHeaders);
+ }
+
+ /**
+ * Case insensitive assertContains
+ *
+ * @param string $needle Search string
+ * @param string $haystack Search this
+ * @param string $message Optional failure message
+ */
+ public function assertContainsIns($needle, $haystack, $message = null)
+ {
+ $this->assertContains(strtolower($needle), strtolower($haystack), $message);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php
new file mode 100644
index 0000000..20feaa8
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\EntityBody;
+
+/**
+ * @covers Guzzle\Http\AbstractEntityBodyDecorator
+ */
+class AbstractEntityBodyDecoratorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDecoratesEntityBody()
+ {
+ $e = EntityBody::factory();
+ $mock = $this->getMockForAbstractClass('Guzzle\Http\AbstractEntityBodyDecorator', array($e));
+
+ $this->assertSame($e->getStream(), $mock->getStream());
+ $this->assertSame($e->getContentLength(), $mock->getContentLength());
+ $this->assertSame($e->getSize(), $mock->getSize());
+ $this->assertSame($e->getContentMd5(), $mock->getContentMd5());
+ $this->assertSame($e->getContentType(), $mock->getContentType());
+ $this->assertSame($e->__toString(), $mock->__toString());
+ $this->assertSame($e->getUri(), $mock->getUri());
+ $this->assertSame($e->getStreamType(), $mock->getStreamType());
+ $this->assertSame($e->getWrapper(), $mock->getWrapper());
+ $this->assertSame($e->getWrapperData(), $mock->getWrapperData());
+ $this->assertSame($e->isReadable(), $mock->isReadable());
+ $this->assertSame($e->isWritable(), $mock->isWritable());
+ $this->assertSame($e->isConsumed(), $mock->isConsumed());
+ $this->assertSame($e->isLocal(), $mock->isLocal());
+ $this->assertSame($e->isSeekable(), $mock->isSeekable());
+ $this->assertSame($e->getContentEncoding(), $mock->getContentEncoding());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php
new file mode 100644
index 0000000..e6e6cdb
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php
@@ -0,0 +1,249 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\CachingEntityBody;
+
+/**
+ * @covers Guzzle\Http\CachingEntityBody
+ */
+class CachingEntityBodyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var CachingEntityBody */
+ protected $body;
+
+ /** @var EntityBody */
+ protected $decorated;
+
+ public function setUp()
+ {
+ $this->decorated = EntityBody::factory('testing');
+ $this->body = new CachingEntityBody($this->decorated);
+ }
+
+ public function testUsesRemoteSizeIfPossible()
+ {
+ $body = EntityBody::factory('test');
+ $caching = new CachingEntityBody($body);
+ $this->assertEquals(4, $caching->getSize());
+ $this->assertEquals(4, $caching->getContentLength());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage does not support custom stream rewind
+ */
+ public function testDoesNotAllowRewindFunction()
+ {
+ $this->body->setRewindFunction(true);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage Cannot seek to byte 10
+ */
+ public function testCannotSeekPastWhatHasBeenRead()
+ {
+ $this->body->seek(10);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage supports only SEEK_SET and SEEK_CUR
+ */
+ public function testCannotUseSeekEnd()
+ {
+ $this->body->seek(2, SEEK_END);
+ }
+
+ public function testChangingUnderlyingStreamUpdatesSizeAndStream()
+ {
+ $size = filesize(__FILE__);
+ $s = fopen(__FILE__, 'r');
+ $this->body->setStream($s, $size);
+ $this->assertEquals($size, $this->body->getSize());
+ $this->assertEquals($size, $this->decorated->getSize());
+ $this->assertSame($s, $this->body->getStream());
+ $this->assertSame($s, $this->decorated->getStream());
+ }
+
+ public function testRewindUsesSeek()
+ {
+ $a = EntityBody::factory('foo');
+ $d = $this->getMockBuilder('Guzzle\Http\CachingEntityBody')
+ ->setMethods(array('seek'))
+ ->setConstructorArgs(array($a))
+ ->getMock();
+ $d->expects($this->once())
+ ->method('seek')
+ ->with(0)
+ ->will($this->returnValue(true));
+ $d->rewind();
+ }
+
+ public function testCanSeekToReadBytes()
+ {
+ $this->assertEquals('te', $this->body->read(2));
+ $this->body->seek(0);
+ $this->assertEquals('test', $this->body->read(4));
+ $this->assertEquals(4, $this->body->ftell());
+ $this->body->seek(2);
+ $this->assertEquals(2, $this->body->ftell());
+ $this->body->seek(2, SEEK_CUR);
+ $this->assertEquals(4, $this->body->ftell());
+ $this->assertEquals('ing', $this->body->read(3));
+ }
+
+ public function testWritesToBufferStream()
+ {
+ $this->body->read(2);
+ $this->body->write('hi');
+ $this->body->rewind();
+ $this->assertEquals('tehiing', (string) $this->body);
+ }
+
+ public function testReadLinesFromBothStreams()
+ {
+ $this->body->seek($this->body->ftell());
+ $this->body->write("test\n123\nhello\n1234567890\n");
+ $this->body->rewind();
+ $this->assertEquals("test\n", $this->body->readLine(7));
+ $this->assertEquals("123\n", $this->body->readLine(7));
+ $this->assertEquals("hello\n", $this->body->readLine(7));
+ $this->assertEquals("123456", $this->body->readLine(7));
+ $this->assertEquals("7890\n", $this->body->readLine(7));
+ // We overwrote the decorated stream, so no more data
+ $this->assertEquals('', $this->body->readLine(7));
+ }
+
+ public function testSkipsOverwrittenBytes()
+ {
+ $decorated = EntityBody::factory(
+ implode("\n", array_map(function ($n) {
+ return str_pad($n, 4, '0', STR_PAD_LEFT);
+ }, range(0, 25)))
+ );
+
+ $body = new CachingEntityBody($decorated);
+
+ $this->assertEquals("0000\n", $body->readLine());
+ $this->assertEquals("0001\n", $body->readLine());
+ // Write over part of the body yet to be read, so skip some bytes
+ $this->assertEquals(5, $body->write("TEST\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+ // Read, which skips bytes, then reads
+ $this->assertEquals("0003\n", $body->readLine());
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("0004\n", $body->readLine());
+ $this->assertEquals("0005\n", $body->readLine());
+
+ // Overwrite part of the cached body (so don't skip any bytes)
+ $body->seek(5);
+ $this->assertEquals(5, $body->write("ABCD\n"));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("TEST\n", $body->readLine());
+ $this->assertEquals("0003\n", $body->readLine());
+ $this->assertEquals("0004\n", $body->readLine());
+ $this->assertEquals("0005\n", $body->readLine());
+ $this->assertEquals("0006\n", $body->readLine());
+ $this->assertEquals(5, $body->write("1234\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+
+ // Seek to 0 and ensure the overwritten bit is replaced
+ $body->rewind();
+ $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
+
+ // Ensure that casting it to a string does not include the bit that was overwritten
+ $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
+ }
+
+ public function testWrapsContentType()
+ {
+ $a = $this->getMockBuilder('Guzzle\Http\EntityBody')
+ ->setMethods(array('getContentType'))
+ ->setConstructorArgs(array(fopen(__FILE__, 'r')))
+ ->getMock();
+ $a->expects($this->once())
+ ->method('getContentType')
+ ->will($this->returnValue('foo'));
+ $d = new CachingEntityBody($a);
+ $this->assertEquals('foo', $d->getContentType());
+ }
+
+ public function testWrapsContentEncoding()
+ {
+ $a = $this->getMockBuilder('Guzzle\Http\EntityBody')
+ ->setMethods(array('getContentEncoding'))
+ ->setConstructorArgs(array(fopen(__FILE__, 'r')))
+ ->getMock();
+ $a->expects($this->once())
+ ->method('getContentEncoding')
+ ->will($this->returnValue('foo'));
+ $d = new CachingEntityBody($a);
+ $this->assertEquals('foo', $d->getContentEncoding());
+ }
+
+ public function testWrapsMetadata()
+ {
+ $a = $this->getMockBuilder('Guzzle\Http\EntityBody')
+ ->setMethods(array('getMetadata', 'getWrapper', 'getWrapperData', 'getStreamType', 'getUri'))
+ ->setConstructorArgs(array(fopen(__FILE__, 'r')))
+ ->getMock();
+
+ $a->expects($this->once())
+ ->method('getMetadata')
+ ->will($this->returnValue(array()));
+ // Called twice for getWrapper and getWrapperData
+ $a->expects($this->exactly(1))
+ ->method('getWrapper')
+ ->will($this->returnValue('wrapper'));
+ $a->expects($this->once())
+ ->method('getWrapperData')
+ ->will($this->returnValue(array()));
+ $a->expects($this->once())
+ ->method('getStreamType')
+ ->will($this->returnValue('baz'));
+ $a->expects($this->once())
+ ->method('getUri')
+ ->will($this->returnValue('path/to/foo'));
+
+ $d = new CachingEntityBody($a);
+ $this->assertEquals(array(), $d->getMetaData());
+ $this->assertEquals('wrapper', $d->getWrapper());
+ $this->assertEquals(array(), $d->getWrapperData());
+ $this->assertEquals('baz', $d->getStreamType());
+ $this->assertEquals('path/to/foo', $d->getUri());
+ }
+
+ public function testWrapsCustomData()
+ {
+ $a = $this->getMockBuilder('Guzzle\Http\EntityBody')
+ ->setMethods(array('getCustomData', 'setCustomData'))
+ ->setConstructorArgs(array(fopen(__FILE__, 'r')))
+ ->getMock();
+
+ $a->expects($this->exactly(1))
+ ->method('getCustomData')
+ ->with('foo')
+ ->will($this->returnValue('bar'));
+
+ $a->expects($this->exactly(1))
+ ->method('setCustomData')
+ ->with('foo', 'bar')
+ ->will($this->returnSelf());
+
+ $d = new CachingEntityBody($a);
+ $this->assertSame($d, $d->setCustomData('foo', 'bar'));
+ $this->assertEquals('bar', $d->getCustomData('foo'));
+ }
+
+ public function testClosesBothStreams()
+ {
+ $s = fopen('php://temp', 'r');
+ $a = EntityBody::factory($s);
+ $d = new CachingEntityBody($a);
+ $d->close();
+ $this->assertFalse(is_resource($s));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php
new file mode 100644
index 0000000..4a91a18
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php
@@ -0,0 +1,601 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Common\Collection;
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Parser\UriTemplate\UriTemplate;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Log\LogPlugin;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Http\Curl\CurlMulti;
+use Guzzle\Http\Client;
+use Guzzle\Common\Version;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Client
+ */
+class ClientTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @return LogPlugin
+ */
+ private function getLogPlugin()
+ {
+ return new LogPlugin(new ClosureLogAdapter(
+ function($message, $priority, $extras = null) {
+ echo $message . ' ' . $priority . ' ' . implode(' - ', (array) $extras) . "\n";
+ }
+ ));
+ }
+
+ public function testAcceptsConfig()
+ {
+ $client = new Client('http://www.google.com/');
+ $this->assertEquals('http://www.google.com/', $client->getBaseUrl());
+ $this->assertSame($client, $client->setConfig(array(
+ 'test' => '123'
+ )));
+ $this->assertEquals(array('test' => '123'), $client->getConfig()->getAll());
+ $this->assertEquals('123', $client->getConfig('test'));
+ $this->assertSame($client, $client->setBaseUrl('http://www.test.com/{test}'));
+ $this->assertEquals('http://www.test.com/123', $client->getBaseUrl());
+ $this->assertEquals('http://www.test.com/{test}', $client->getBaseUrl(false));
+
+ try {
+ $client->setConfig(false);
+ } catch (\InvalidArgumentException $e) {
+ }
+ }
+
+ public function testDescribesEvents()
+ {
+ $this->assertEquals(array('client.create_request'), Client::getAllEvents());
+ }
+
+ public function testConstructorCanAcceptConfig()
+ {
+ $client = new Client('http://www.test.com/', array(
+ 'data' => '123'
+ ));
+ $this->assertEquals('123', $client->getConfig('data'));
+ }
+
+ public function testCanUseCollectionAsConfig()
+ {
+ $client = new Client('http://www.google.com/');
+ $client->setConfig(new Collection(array(
+ 'api' => 'v1',
+ 'key' => 'value',
+ 'base_url' => 'http://www.google.com/'
+ )));
+ $this->assertEquals('v1', $client->getConfig('api'));
+ }
+
+ public function testExpandsUriTemplatesUsingConfig()
+ {
+ $client = new Client('http://www.google.com/');
+ $client->setConfig(array('api' => 'v1', 'key' => 'value', 'foo' => 'bar'));
+ $ref = new \ReflectionMethod($client, 'expandTemplate');
+ $ref->setAccessible(true);
+ $this->assertEquals('Testing...api/v1/key/value', $ref->invoke($client, 'Testing...api/{api}/key/{key}'));
+ }
+
+ public function testClientAttachersObserversToRequests()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+
+ $client = new Client($this->getServer()->getUrl());
+ $logPlugin = $this->getLogPlugin();
+ $client->getEventDispatcher()->addSubscriber($logPlugin);
+
+ // Get a request from the client and ensure the the observer was
+ // attached to the new request
+ $request = $client->createRequest();
+ $this->assertTrue($this->hasSubscriber($request, $logPlugin));
+ }
+
+ public function testClientReturnsValidBaseUrls()
+ {
+ $client = new Client('http://www.{foo}.{data}/', array(
+ 'data' => '123',
+ 'foo' => 'bar'
+ ));
+ $this->assertEquals('http://www.bar.123/', $client->getBaseUrl());
+ $client->setBaseUrl('http://www.google.com/');
+ $this->assertEquals('http://www.google.com/', $client->getBaseUrl());
+ }
+
+ public function testClientAddsCurlOptionsToRequests()
+ {
+ $client = new Client('http://www.test.com/', array(
+ 'api' => 'v1',
+ // Adds the option using the curl values
+ 'curl.options' => array(
+ 'CURLOPT_HTTPAUTH' => 'CURLAUTH_DIGEST',
+ 'abc' => 'foo',
+ 'blacklist' => 'abc',
+ 'debug' => true
+ )
+ ));
+
+ $request = $client->createRequest();
+ $options = $request->getCurlOptions();
+ $this->assertEquals(CURLAUTH_DIGEST, $options->get(CURLOPT_HTTPAUTH));
+ $this->assertEquals('foo', $options->get('abc'));
+ $this->assertEquals('abc', $options->get('blacklist'));
+ }
+
+ public function testClientAllowsFineGrainedSslControlButIsSecureByDefault()
+ {
+ $client = new Client('https://www.secure.com/');
+
+ // secure by default
+ $request = $client->createRequest();
+ $options = $request->getCurlOptions();
+ $this->assertTrue($options->get(CURLOPT_SSL_VERIFYPEER));
+
+ // set a capath if you prefer
+ $client = new Client('https://www.secure.com/');
+ $client->setSslVerification(__DIR__);
+ $request = $client->createRequest();
+ $options = $request->getCurlOptions();
+ $this->assertSame(__DIR__, $options->get(CURLOPT_CAPATH));
+ }
+
+ public function testConfigSettingsControlSslConfiguration()
+ {
+ // Use the default ca certs on the system
+ $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => 'system'));
+ $this->assertNull($client->getConfig('curl.options'));
+ // Can set the cacert value as well
+ $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => false));
+ $options = $client->getConfig('curl.options');
+ $this->assertArrayNotHasKey(CURLOPT_CAINFO, $options);
+ $this->assertSame(false, $options[CURLOPT_SSL_VERIFYPEER]);
+ $this->assertSame(0, $options[CURLOPT_SSL_VERIFYHOST]);
+ }
+
+ public function testClientAllowsUnsafeOperationIfRequested()
+ {
+ // be really unsafe if you insist
+ $client = new Client('https://www.secure.com/', array(
+ 'api' => 'v1'
+ ));
+
+ $client->setSslVerification(false);
+ $request = $client->createRequest();
+ $options = $request->getCurlOptions();
+ $this->assertFalse($options->get(CURLOPT_SSL_VERIFYPEER));
+ $this->assertNull($options->get(CURLOPT_CAINFO));
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ */
+ public function testThrowsExceptionForInvalidCertificate()
+ {
+ $client = new Client('https://www.secure.com/');
+ $client->setSslVerification('/path/to/missing/file');
+ }
+
+ public function testClientAllowsSettingSpecificSslCaInfo()
+ {
+ // set a file other than the provided cacert.pem
+ $client = new Client('https://www.secure.com/', array(
+ 'api' => 'v1'
+ ));
+
+ $client->setSslVerification(__FILE__);
+ $request = $client->createRequest();
+ $options = $request->getCurlOptions();
+ $this->assertSame(__FILE__, $options->get(CURLOPT_CAINFO));
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testClientPreventsInadvertentInsecureVerifyHostSetting()
+ {
+ // set a file other than the provided cacert.pem
+ $client = new Client('https://www.secure.com/', array(
+ 'api' => 'v1'
+ ));
+ $client->setSslVerification(__FILE__, true, true);
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testClientPreventsInvalidVerifyPeerSetting()
+ {
+ // set a file other than the provided cacert.pem
+ $client = new Client('https://www.secure.com/', array(
+ 'api' => 'v1'
+ ));
+ $client->setSslVerification(__FILE__, 'yes');
+ }
+
+ public function testClientAddsParamsToRequests()
+ {
+ Version::$emitWarnings = false;
+ $client = new Client('http://www.example.com', array(
+ 'api' => 'v1',
+ 'request.params' => array(
+ 'foo' => 'bar',
+ 'baz' => 'jar'
+ )
+ ));
+ $request = $client->createRequest();
+ $this->assertEquals('bar', $request->getParams()->get('foo'));
+ $this->assertEquals('jar', $request->getParams()->get('baz'));
+ Version::$emitWarnings = true;
+ }
+
+ public function urlProvider()
+ {
+ $u = $this->getServer()->getUrl() . 'base/';
+ $u2 = $this->getServer()->getUrl() . 'base?z=1';
+ return array(
+ array($u, '', $u),
+ array($u, 'relative/path/to/resource', $u . 'relative/path/to/resource'),
+ array($u, 'relative/path/to/resource?a=b&c=d', $u . 'relative/path/to/resource?a=b&c=d'),
+ array($u, '/absolute/path/to/resource', $this->getServer()->getUrl() . 'absolute/path/to/resource'),
+ array($u, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d'),
+ array($u2, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d&z=1'),
+ array($u2, 'relative/path/to/resource', $this->getServer()->getUrl() . 'base/relative/path/to/resource?z=1'),
+ array($u2, 'relative/path/to/resource?another=query', $this->getServer()->getUrl() . 'base/relative/path/to/resource?another=query&z=1')
+ );
+ }
+
+ /**
+ * @dataProvider urlProvider
+ */
+ public function testBuildsRelativeUrls($baseUrl, $url, $result)
+ {
+ $client = new Client($baseUrl);
+ $this->assertEquals($result, $client->get($url)->getUrl());
+ }
+
+ public function testAllowsConfigsToBeChangedAndInjectedInBaseUrl()
+ {
+ $client = new Client('http://{a}/{b}');
+ $this->assertEquals('http:///', $client->getBaseUrl());
+ $this->assertEquals('http://{a}/{b}', $client->getBaseUrl(false));
+ $client->setConfig(array(
+ 'a' => 'test.com',
+ 'b' => 'index.html'
+ ));
+ $this->assertEquals('http://test.com/index.html', $client->getBaseUrl());
+ }
+
+ public function testCreatesRequestsWithDefaultValues()
+ {
+ $client = new Client($this->getServer()->getUrl() . 'base');
+
+ // Create a GET request
+ $request = $client->createRequest();
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals($client->getBaseUrl(), $request->getUrl());
+
+ // Create a DELETE request
+ $request = $client->createRequest('DELETE');
+ $this->assertEquals('DELETE', $request->getMethod());
+ $this->assertEquals($client->getBaseUrl(), $request->getUrl());
+
+ // Create a HEAD request with custom headers
+ $request = $client->createRequest('HEAD', 'http://www.test.com/');
+ $this->assertEquals('HEAD', $request->getMethod());
+ $this->assertEquals('http://www.test.com/', $request->getUrl());
+
+ // Create a PUT request
+ $request = $client->createRequest('PUT');
+ $this->assertEquals('PUT', $request->getMethod());
+
+ // Create a PUT request with injected config
+ $client->getConfig()->set('a', 1)->set('b', 2);
+ $request = $client->createRequest('PUT', '/path/{a}?q={b}');
+ $this->assertEquals($request->getUrl(), $this->getServer()->getUrl() . 'path/1?q=2');
+ }
+
+ public function testClientHasHelperMethodsForCreatingRequests()
+ {
+ $url = $this->getServer()->getUrl();
+ $client = new Client($url . 'base');
+ $this->assertEquals('GET', $client->get()->getMethod());
+ $this->assertEquals('PUT', $client->put()->getMethod());
+ $this->assertEquals('POST', $client->post()->getMethod());
+ $this->assertEquals('HEAD', $client->head()->getMethod());
+ $this->assertEquals('DELETE', $client->delete()->getMethod());
+ $this->assertEquals('OPTIONS', $client->options()->getMethod());
+ $this->assertEquals('PATCH', $client->patch()->getMethod());
+ $this->assertEquals($url . 'base/abc', $client->get('abc')->getUrl());
+ $this->assertEquals($url . 'zxy', $client->put('/zxy')->getUrl());
+ $this->assertEquals($url . 'zxy?a=b', $client->post('/zxy?a=b')->getUrl());
+ $this->assertEquals($url . 'base?a=b', $client->head('?a=b')->getUrl());
+ $this->assertEquals($url . 'base?a=b', $client->delete('/base?a=b')->getUrl());
+ }
+
+ public function testClientInjectsConfigsIntoUrls()
+ {
+ $client = new Client('http://www.test.com/api/v1', array(
+ 'test' => '123'
+ ));
+ $request = $client->get('relative/{test}');
+ $this->assertEquals('http://www.test.com/api/v1/relative/123', $request->getUrl());
+ }
+
+ public function testAllowsEmptyBaseUrl()
+ {
+ $client = new Client();
+ $request = $client->get('http://www.google.com/');
+ $this->assertEquals('http://www.google.com/', $request->getUrl());
+ $request->setResponse(new Response(200), true);
+ $request->send();
+ }
+
+ public function testAllowsCustomCurlMultiObjects()
+ {
+ $mock = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('add', 'send'));
+ $mock->expects($this->once())
+ ->method('add')
+ ->will($this->returnSelf());
+ $mock->expects($this->once())
+ ->method('send')
+ ->will($this->returnSelf());
+
+ $client = new Client();
+ $client->setCurlMulti($mock);
+
+ $request = $client->get();
+ $request->setResponse(new Response(200), true);
+ $client->send($request);
+ }
+
+ public function testClientSendsMultipleRequests()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $mock = new MockPlugin();
+
+ $responses = array(
+ new Response(200),
+ new Response(201),
+ new Response(202)
+ );
+
+ $mock->addResponse($responses[0]);
+ $mock->addResponse($responses[1]);
+ $mock->addResponse($responses[2]);
+
+ $client->getEventDispatcher()->addSubscriber($mock);
+
+ $requests = array(
+ $client->get(),
+ $client->head(),
+ $client->put('/', null, 'test')
+ );
+
+ $this->assertEquals(array(
+ $responses[0],
+ $responses[1],
+ $responses[2]
+ ), $client->send($requests));
+ }
+
+ public function testClientSendsSingleRequest()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $mock = new MockPlugin();
+ $response = new Response(200);
+ $mock->addResponse($response);
+ $client->getEventDispatcher()->addSubscriber($mock);
+ $this->assertEquals($response, $client->send($client->get()));
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\BadResponseException
+ */
+ public function testClientThrowsExceptionForSingleRequest()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $mock = new MockPlugin();
+ $response = new Response(404);
+ $mock->addResponse($response);
+ $client->getEventDispatcher()->addSubscriber($mock);
+ $client->send($client->get());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\ExceptionCollection
+ */
+ public function testClientThrowsExceptionForMultipleRequests()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $mock = new MockPlugin();
+ $mock->addResponse(new Response(200));
+ $mock->addResponse(new Response(404));
+ $client->getEventDispatcher()->addSubscriber($mock);
+ $client->send(array($client->get(), $client->head()));
+ }
+
+ public function testQueryStringsAreNotDoubleEncoded()
+ {
+ $client = new Client('http://test.com', array(
+ 'path' => array('foo', 'bar'),
+ 'query' => 'hi there',
+ 'data' => array(
+ 'test' => 'a&b'
+ )
+ ));
+
+ $request = $client->get('{/path*}{?query,data*}');
+ $this->assertEquals('http://test.com/foo/bar?query=hi%20there&test=a%26b', $request->getUrl());
+ $this->assertEquals('hi there', $request->getQuery()->get('query'));
+ $this->assertEquals('a&b', $request->getQuery()->get('test'));
+ }
+
+ public function testQueryStringsAreNotDoubleEncodedUsingAbsolutePaths()
+ {
+ $client = new Client('http://test.com', array(
+ 'path' => array('foo', 'bar'),
+ 'query' => 'hi there',
+ ));
+ $request = $client->get('http://test.com{?query}');
+ $this->assertEquals('http://test.com?query=hi%20there', $request->getUrl());
+ $this->assertEquals('hi there', $request->getQuery()->get('query'));
+ }
+
+ public function testAllowsUriTemplateInjection()
+ {
+ $client = new Client('http://test.com');
+ $ref = new \ReflectionMethod($client, 'getUriTemplate');
+ $ref->setAccessible(true);
+ $a = $ref->invoke($client);
+ $this->assertSame($a, $ref->invoke($client));
+ $client->setUriTemplate(new UriTemplate());
+ $this->assertNotSame($a, $ref->invoke($client));
+ }
+
+ public function testAllowsCustomVariablesWhenExpandingTemplates()
+ {
+ $client = new Client('http://test.com', array('test' => 'hi'));
+ $ref = new \ReflectionMethod($client, 'expandTemplate');
+ $ref->setAccessible(true);
+ $uri = $ref->invoke($client, 'http://{test}{?query*}', array('query' => array('han' => 'solo')));
+ $this->assertEquals('http://hi?han=solo', $uri);
+ }
+
+ public function testUriArrayAllowsCustomTemplateVariables()
+ {
+ $client = new Client();
+ $vars = array(
+ 'var' => 'hi'
+ );
+ $this->assertEquals('/hi', (string) $client->createRequest('GET', array('/{var}', $vars))->getUrl());
+ $this->assertEquals('/hi', (string) $client->get(array('/{var}', $vars))->getUrl());
+ $this->assertEquals('/hi', (string) $client->put(array('/{var}', $vars))->getUrl());
+ $this->assertEquals('/hi', (string) $client->post(array('/{var}', $vars))->getUrl());
+ $this->assertEquals('/hi', (string) $client->head(array('/{var}', $vars))->getUrl());
+ $this->assertEquals('/hi', (string) $client->options(array('/{var}', $vars))->getUrl());
+ }
+
+ public function testAllowsDefaultHeaders()
+ {
+ Version::$emitWarnings = false;
+ $default = array('X-Test' => 'Hi!');
+ $other = array('X-Other' => 'Foo');
+
+ $client = new Client();
+ $client->setDefaultHeaders($default);
+ $this->assertEquals($default, $client->getDefaultHeaders()->getAll());
+ $client->setDefaultHeaders(new Collection($default));
+ $this->assertEquals($default, $client->getDefaultHeaders()->getAll());
+
+ $request = $client->createRequest('GET', null, $other);
+ $this->assertEquals('Hi!', $request->getHeader('X-Test'));
+ $this->assertEquals('Foo', $request->getHeader('X-Other'));
+
+ $request = $client->createRequest('GET', null, new Collection($other));
+ $this->assertEquals('Hi!', $request->getHeader('X-Test'));
+ $this->assertEquals('Foo', $request->getHeader('X-Other'));
+
+ $request = $client->createRequest('GET');
+ $this->assertEquals('Hi!', $request->getHeader('X-Test'));
+ Version::$emitWarnings = true;
+ }
+
+ public function testDontReuseCurlMulti()
+ {
+ $client1 = new Client();
+ $client2 = new Client();
+ $this->assertNotSame($client1->getCurlMulti(), $client2->getCurlMulti());
+ }
+
+ public function testGetDefaultUserAgent()
+ {
+ $client = new Client();
+ $agent = $this->readAttribute($client, 'userAgent');
+ $version = curl_version();
+ $testAgent = sprintf('Guzzle/%s curl/%s PHP/%s', Version::VERSION, $version['version'], PHP_VERSION);
+ $this->assertEquals($agent, $testAgent);
+
+ $client->setUserAgent('foo');
+ $this->assertEquals('foo', $this->readAttribute($client, 'userAgent'));
+ }
+
+ public function testOverwritesUserAgent()
+ {
+ $client = new Client();
+ $request = $client->createRequest('GET', 'http://www.foo.com', array('User-agent' => 'foo'));
+ $this->assertEquals('foo', (string) $request->getHeader('User-Agent'));
+ }
+
+ public function testUsesDefaultUserAgent()
+ {
+ $client = new Client();
+ $request = $client->createRequest('GET', 'http://www.foo.com');
+ $this->assertContains('Guzzle/', (string) $request->getHeader('User-Agent'));
+ }
+
+ public function testCanSetDefaultRequestOptions()
+ {
+ $client = new Client();
+ $client->getConfig()->set('request.options', array(
+ 'query' => array('test' => '123', 'other' => 'abc'),
+ 'headers' => array('Foo' => 'Bar', 'Baz' => 'Bam')
+ ));
+ $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test'));
+ // Explicit options on a request should overrule default options
+ $this->assertEquals('Test', (string) $request->getHeader('Foo'));
+ $this->assertEquals('hello', $request->getQuery()->get('test'));
+ // Default options should still be set
+ $this->assertEquals('abc', $request->getQuery()->get('other'));
+ $this->assertEquals('Bam', (string) $request->getHeader('Baz'));
+ }
+
+ public function testCanSetSetOptionsOnRequests()
+ {
+ $client = new Client();
+ $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test'), null, array(
+ 'cookies' => array('michael' => 'test')
+ ));
+ $this->assertEquals('test', $request->getCookie('michael'));
+ }
+
+ public function testHasDefaultOptionsHelperMethods()
+ {
+ $client = new Client();
+ // With path
+ $client->setDefaultOption('headers/foo', 'bar');
+ $this->assertEquals('bar', $client->getDefaultOption('headers/foo'));
+ // With simple key
+ $client->setDefaultOption('allow_redirects', false);
+ $this->assertFalse($client->getDefaultOption('allow_redirects'));
+
+ $this->assertEquals(array(
+ 'headers' => array('foo' => 'bar'),
+ 'allow_redirects' => false
+ ), $client->getConfig('request.options'));
+
+ $request = $client->get('/');
+ $this->assertEquals('bar', $request->getHeader('foo'));
+ }
+
+ public function testHeadCanUseOptions()
+ {
+ $client = new Client();
+ $head = $client->head('http://www.foo.com', array(), array('query' => array('foo' => 'bar')));
+ $this->assertEquals('bar', $head->getQuery()->get('foo'));
+ }
+
+ public function testCanSetRelativeUrlStartingWithHttp()
+ {
+ $client = new Client('http://www.foo.com');
+ $this->assertEquals(
+ 'http://www.foo.com/httpfoo',
+ $client->createRequest('GET', 'httpfoo')->getUrl()
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php
new file mode 100644
index 0000000..5bf28de
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php
@@ -0,0 +1,947 @@
+<?php
+
+namespace Guzzle\Tests\Http\Curl;
+
+use Guzzle\Common\Collection;
+use Guzzle\Common\Event;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\QueryString;
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Curl\CurlHandle;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Curl\CurlHandle
+ */
+class CurlHandleTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public $requestHandle;
+
+ protected function updateForHandle(RequestInterface $request)
+ {
+ $that = $this;
+ $request->getEventDispatcher()->addListener('request.sent', function (Event $e) use ($that) {
+ $that->requestHandle = $e['handle'];
+ });
+
+ return $request;
+ }
+
+ public function setUp()
+ {
+ $this->requestHandle = null;
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorExpectsCurlResource()
+ {
+ $h = new CurlHandle(false, array());
+ }
+
+ public function testConstructorExpectsProperOptions()
+ {
+ $h = curl_init($this->getServer()->getUrl());
+ try {
+ $ha = new CurlHandle($h, false);
+ $this->fail('Expected InvalidArgumentException');
+ } catch (\InvalidArgumentException $e) {
+ }
+
+ $ha = new CurlHandle($h, array(
+ CURLOPT_URL => $this->getServer()->getUrl()
+ ));
+ $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL));
+
+ $ha = new CurlHandle($h, new Collection(array(
+ CURLOPT_URL => $this->getServer()->getUrl()
+ )));
+ $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL));
+ }
+
+ public function testConstructorInitializesObject()
+ {
+ $handle = curl_init($this->getServer()->getUrl());
+ $h = new CurlHandle($handle, array(
+ CURLOPT_URL => $this->getServer()->getUrl()
+ ));
+ $this->assertSame($handle, $h->getHandle());
+ $this->assertInstanceOf('Guzzle\\Http\\Url', $h->getUrl());
+ $this->assertEquals($this->getServer()->getUrl(), (string) $h->getUrl());
+ $this->assertEquals($this->getServer()->getUrl(), $h->getOptions()->get(CURLOPT_URL));
+ }
+
+ public function testStoresStdErr()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://test.com');
+ $request->getCurlOptions()->set('debug', true);
+ $h = CurlHandle::factory($request);
+ $this->assertEquals($h->getStderr(true), $h->getOptions()->get(CURLOPT_STDERR));
+ $this->assertInternalType('resource', $h->getStderr(true));
+ $this->assertInternalType('string', $h->getStderr(false));
+ $r = $h->getStderr(true);
+ fwrite($r, 'test');
+ $this->assertEquals('test', $h->getStderr(false));
+ }
+
+ public function testStoresCurlErrorNumber()
+ {
+ $h = new CurlHandle(curl_init('http://test.com'), array(CURLOPT_URL => 'http://test.com'));
+ $this->assertEquals(CURLE_OK, $h->getErrorNo());
+ $h->setErrorNo(CURLE_OPERATION_TIMEOUTED);
+ $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $h->getErrorNo());
+ }
+
+ public function testAccountsForMissingStdErr()
+ {
+ $handle = curl_init('http://www.test.com/');
+ $h = new CurlHandle($handle, array(
+ CURLOPT_URL => 'http://www.test.com/'
+ ));
+ $this->assertNull($h->getStderr(false));
+ }
+
+ public function testDeterminesIfResourceIsAvailable()
+ {
+ $handle = curl_init($this->getServer()->getUrl());
+ $h = new CurlHandle($handle, array());
+ $this->assertTrue($h->isAvailable());
+
+ // Mess it up by closing the handle
+ curl_close($handle);
+ $this->assertFalse($h->isAvailable());
+
+ // Mess it up by unsetting the handle
+ $handle = null;
+ $this->assertFalse($h->isAvailable());
+ }
+
+ public function testWrapsErrorsAndInfo()
+ {
+ if (!defined('CURLOPT_TIMEOUT_MS')) {
+ $this->markTestSkipped('Update curl');
+ }
+
+ $settings = array(
+ CURLOPT_PORT => 123,
+ CURLOPT_CONNECTTIMEOUT_MS => 1,
+ CURLOPT_TIMEOUT_MS => 1
+ );
+
+ $handle = curl_init($this->getServer()->getUrl());
+ curl_setopt_array($handle, $settings);
+ $h = new CurlHandle($handle, $settings);
+ @curl_exec($handle);
+
+ $errors = array(
+ "couldn't connect to host",
+ 'timeout was reached',
+ 'connection time-out',
+ 'connect() timed out!',
+ 'failed connect to 127.0.0.1:123; connection refused',
+ 'failed to connect to 127.0.0.1 port 123: connection refused'
+ );
+ $this->assertTrue(in_array(strtolower($h->getError()), $errors), $h->getError() . ' was not the error');
+
+ $this->assertTrue($h->getErrorNo() > 0);
+
+ $this->assertEquals($this->getServer()->getUrl(), $h->getInfo(CURLINFO_EFFECTIVE_URL));
+ $this->assertInternalType('array', $h->getInfo());
+
+ curl_close($handle);
+ $this->assertEquals(null, $h->getInfo('url'));
+ }
+
+ public function testGetInfoWithoutDebugMode()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get($this->getServer()->getUrl());
+ $response = $request->send();
+
+ $info = $response->getInfo();
+ $this->assertFalse(empty($info));
+ $this->assertEquals($this->getServer()->getUrl(), $info['url']);
+ }
+
+ public function testWrapsCurlOptions()
+ {
+ $handle = curl_init($this->getServer()->getUrl());
+ $h = new CurlHandle($handle, array(
+ CURLOPT_AUTOREFERER => true,
+ CURLOPT_BUFFERSIZE => 1024
+ ));
+
+ $this->assertEquals(true, $h->getOptions()->get(CURLOPT_AUTOREFERER));
+ $this->assertEquals(1024, $h->getOptions()->get(CURLOPT_BUFFERSIZE));
+ }
+
+ /**
+ * Data provider for factory tests
+ *
+ * @return array
+ */
+ public function dataProvider()
+ {
+ $testFile = __DIR__ . '/../../../../../phpunit.xml.dist';
+
+ $postBody = new QueryString(array('file' => '@' . $testFile));
+ $qs = new QueryString(array(
+ 'x' => 'y',
+ 'z' => 'a'
+ ));
+
+ $client = new Client();
+ $userAgent = $client->getDefaultUserAgent();
+ $auth = base64_encode('michael:123');
+ $testFileSize = filesize($testFile);
+
+ $tests = array(
+ // Send a regular GET
+ array('GET', 'http://www.google.com/', null, null, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent),
+ )),
+ // Test that custom request methods can be used
+ array('TRACE', 'http://www.google.com/', null, null, array(
+ CURLOPT_CUSTOMREQUEST => 'TRACE'
+ )),
+ // Send a GET using a port
+ array('GET', 'http://127.0.0.1:8080', null, null, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_PORT => 8080,
+ CURLOPT_HTTPHEADER => array('Accept:', 'Host: 127.0.0.1:8080', 'User-Agent: ' . $userAgent),
+ )),
+ // Send a HEAD request
+ array('HEAD', 'http://www.google.com/', null, null, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent),
+ CURLOPT_NOBODY => 1
+ )),
+ // Send a GET using basic auth
+ array('GET', 'https://michael:123@127.0.0.1/index.html?q=2', null, null, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_HTTPHEADER => array(
+ 'Accept:',
+ 'Host: 127.0.0.1',
+ 'Authorization: Basic ' . $auth,
+ 'User-Agent: ' . $userAgent
+ ),
+ CURLOPT_PORT => 443
+ )),
+ // Send a GET request with custom headers
+ array('GET', 'http://127.0.0.1:8124/', array(
+ 'x-test-data' => 'Guzzle'
+ ), null, array(
+ CURLOPT_PORT => 8124,
+ CURLOPT_HTTPHEADER => array(
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'x-test-data: Guzzle',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'x-test-data' => 'Guzzle'
+ )),
+ // Send a POST using a query string
+ array('POST', 'http://127.0.0.1:8124/post.php', null, $qs, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_POSTFIELDS => 'x=y&z=a',
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '7',
+ '!Expect' => null,
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
+ '!Transfer-Encoding' => null
+ )),
+ // Send a PUT using raw data
+ array('PUT', 'http://127.0.0.1:8124/put.php', null, EntityBody::factory(fopen($testFile, 'r+')), array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_READFUNCTION => 'callback',
+ CURLOPT_INFILESIZE => filesize($testFile),
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ '!Expect' => null,
+ 'Content-Length' => $testFileSize,
+ '!Transfer-Encoding' => null
+ )),
+ // Send a POST request using an array of fields
+ array('POST', 'http://127.0.0.1:8124/post.php', null, array(
+ 'x' => 'y',
+ 'a' => 'b'
+ ), array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_POST => 1,
+ CURLOPT_POSTFIELDS => 'x=y&a=b',
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '7',
+ '!Expect' => null,
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
+ '!Transfer-Encoding' => null
+ )),
+ // Send a POST request with raw POST data and a custom content-type
+ array('POST', 'http://127.0.0.1:8124/post.php', array(
+ 'Content-Type' => 'application/json'
+ ), '{"hi":"there"}', array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_UPLOAD => true,
+ CURLOPT_INFILESIZE => 14,
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'Content-Type: application/json',
+ 'User-Agent: ' . $userAgent
+ ),
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Type' => 'application/json',
+ '!Expect' => null,
+ 'Content-Length' => '14',
+ '!Transfer-Encoding' => null
+ )),
+ // Send a POST request with raw POST data, a custom content-type, and use chunked encoding
+ array('POST', 'http://127.0.0.1:8124/post.php', array(
+ 'Content-Type' => 'application/json',
+ 'Transfer-Encoding' => 'chunked'
+ ), '{"hi":"there"}', array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_UPLOAD => true,
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'Transfer-Encoding: chunked',
+ 'Content-Type: application/json',
+ 'User-Agent: ' . $userAgent
+ ),
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Type' => 'application/json',
+ '!Expect' => null,
+ 'Transfer-Encoding' => 'chunked',
+ '!Content-Length' => ''
+ )),
+ // Send a POST request with no body
+ array('POST', 'http://127.0.0.1:8124/post.php', null, '', array(
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '0',
+ '!Transfer-Encoding' => null
+ )),
+ // Send a POST request with empty post fields
+ array('POST', 'http://127.0.0.1:8124/post.php', null, array(), array(
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '0',
+ '!Transfer-Encoding' => null
+ )),
+ // Send a PATCH request
+ array('PATCH', 'http://127.0.0.1:8124/patch.php', null, 'body', array(
+ CURLOPT_INFILESIZE => 4,
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'User-Agent: ' . $userAgent
+ )
+ )),
+ // Send a DELETE request with a body
+ array('DELETE', 'http://127.0.0.1:8124/delete.php', null, 'body', array(
+ CURLOPT_CUSTOMREQUEST => 'DELETE',
+ CURLOPT_INFILESIZE => 4,
+ CURLOPT_HTTPHEADER => array (
+ 'Expect:',
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '4',
+ '!Expect' => null,
+ '!Transfer-Encoding' => null
+ )),
+
+ /**
+ * Send a request with empty path and a fragment - the fragment must be
+ * stripped out before sending it to curl
+ *
+ * @issue 453
+ * @link https://github.com/guzzle/guzzle/issues/453
+ */
+ array('GET', 'http://www.google.com#head', null, null, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent),
+ )),
+ );
+
+ $postTest = array('POST', 'http://127.0.0.1:8124/post.php', null, $postBody, array(
+ CURLOPT_RETURNTRANSFER => 0,
+ CURLOPT_HEADER => 0,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_WRITEFUNCTION => 'callback',
+ CURLOPT_HEADERFUNCTION => 'callback',
+ CURLOPT_POST => 1,
+ CURLOPT_POSTFIELDS => array(
+ 'file' => '@' . $testFile . ';filename=phpunit.xml.dist;type=application/octet-stream'
+ ),
+ CURLOPT_HTTPHEADER => array (
+ 'Accept:',
+ 'Host: 127.0.0.1:8124',
+ 'Content-Type: multipart/form-data',
+ 'Expect: 100-Continue',
+ 'User-Agent: ' . $userAgent
+ )
+ ), array(
+ 'Host' => '*',
+ 'User-Agent' => '*',
+ 'Content-Length' => '*',
+ 'Expect' => '100-Continue',
+ 'Content-Type' => 'multipart/form-data; boundary=*',
+ '!Transfer-Encoding' => null
+ ));
+
+ if (version_compare(phpversion(), '5.5.0', '>=')) {
+ $postTest[4][CURLOPT_POSTFIELDS] = array(
+ 'file' => new \CurlFile($testFile, 'application/octet-stream', 'phpunit.xml.dist')
+ );
+ }
+
+ $tests[] = $postTest;
+
+ return $tests;
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testFactoryCreatesCurlBasedOnRequest($method, $url, $headers, $body, $options, $expectedHeaders = null)
+ {
+ $client = new Client();
+ $request = $client->createRequest($method, $url, $headers, $body);
+ $request->getCurlOptions()->set('debug', true);
+
+ $originalRequest = clone $request;
+ $curlTest = clone $request;
+ $handle = CurlHandle::factory($curlTest);
+
+ $this->assertInstanceOf('Guzzle\\Http\\Curl\\CurlHandle', $handle);
+ $o = $handle->getOptions()->getAll();
+
+ // Headers are case-insensitive
+ if (isset($o[CURLOPT_HTTPHEADER])) {
+ $o[CURLOPT_HTTPHEADER] = array_map('strtolower', $o[CURLOPT_HTTPHEADER]);
+ }
+ if (isset($options[CURLOPT_HTTPHEADER])) {
+ $options[CURLOPT_HTTPHEADER] = array_map('strtolower', $options[CURLOPT_HTTPHEADER]);
+ }
+
+ $check = 0;
+ foreach ($options as $key => $value) {
+ $check++;
+ $this->assertArrayHasKey($key, $o, '-> Check number ' . $check);
+ if ($key != CURLOPT_HTTPHEADER && $key != CURLOPT_POSTFIELDS && (is_array($o[$key])) || $o[$key] instanceof \Closure) {
+ $this->assertEquals('callback', $value, '-> Check number ' . $check);
+ } else {
+ $this->assertTrue($value == $o[$key], '-> Check number ' . $check . ' - ' . var_export($value, true) . ' != ' . var_export($o[$key], true));
+ }
+ }
+
+ // If we are testing the actual sent headers
+ if ($expectedHeaders) {
+
+ // Send the request to the test server
+ $client = new Client($this->getServer()->getUrl());
+ $request->setClient($client);
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request->send();
+
+ // Get the request that was sent and create a request that we expected
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals($method, $requests[0]->getMethod());
+
+ $test = $this->compareHeaders($expectedHeaders, $requests[0]->getHeaders());
+ $this->assertFalse($test, $test . "\nSent: \n" . $request . "\n\n" . $requests[0]);
+
+ // Ensure only one Content-Length header is sent
+ if ($request->getHeader('Content-Length')) {
+ $this->assertEquals((string) $request->getHeader('Content-Length'), (string) $requests[0]->getHeader('Content-Length'));
+ }
+ }
+ }
+
+ public function testFactoryUsesSpecifiedProtocol()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:8124/');
+ $request->setProtocolVersion('1.1');
+ $handle = CurlHandle::factory($request);
+ $options = $handle->getOptions();
+ $this->assertEquals(CURL_HTTP_VERSION_1_1, $options[CURLOPT_HTTP_VERSION]);
+ }
+
+ public function testUploadsPutData()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/');
+ $request->getCurlOptions()->set('debug', true);
+ $request->setBody(EntityBody::factory('test'), 'text/plain', false);
+ $request->getCurlOptions()->set('progress', true);
+
+ $o = $this->getWildcardObserver($request);
+ $request->send();
+
+ // Make sure that the events were dispatched
+ $this->assertTrue($o->has('curl.callback.progress'));
+
+ // Ensure that the request was received exactly as intended
+ $r = $this->getServer()->getReceivedRequests(true);
+ $this->assertFalse($r[0]->hasHeader('Transfer-Encoding'));
+ $this->assertEquals(4, (string) $r[0]->getHeader('Content-Length'));
+ $sent = strtolower($r[0]);
+ $this->assertContains('put / http/1.1', $sent);
+ $this->assertContains('host: 127.0.0.1', $sent);
+ $this->assertContains('user-agent:', $sent);
+ $this->assertContains('content-type: text/plain', $sent);
+ }
+
+ public function testUploadsPutDataUsingChunkedEncodingWhenLengthCannotBeDetermined()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/');
+ $request->setBody(EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')), 'text/plain');
+ $request->send();
+
+ $r = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('chunked', $r[1]->getHeader('Transfer-Encoding'));
+ $this->assertFalse($r[1]->hasHeader('Content-Length'));
+ }
+
+ public function testUploadsPutDataUsingChunkedEncodingWhenForced()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/', array('Transfer-Encoding' => 'chunked'), 'hi!');
+ $request->send();
+
+ $r = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('chunked', $r[0]->getHeader('Transfer-Encoding'));
+ $this->assertFalse($r[0]->hasHeader('Content-Length'));
+ $this->assertEquals('hi!', $r[0]->getBody(true));
+ }
+
+ public function testSendsPostRequestsWithFields()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+
+ $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl());
+ $request->getCurlOptions()->set('debug', true);
+ $request->setClient(new Client());
+ $request->addPostFields(array(
+ 'a' => 'b',
+ 'c' => 'ay! ~This is a test, isn\'t it?'
+ ));
+ $request->send();
+
+ // Make sure that the request was sent correctly
+ $r = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('a=b&c=ay%21%20~This%20is%20a%20test%2C%20isn%27t%20it%3F', (string) $r[0]->getBody());
+ $this->assertFalse($r[0]->hasHeader('Transfer-Encoding'));
+ $this->assertEquals(56, (string) $r[0]->getHeader('Content-Length'));
+ $sent = strtolower($r[0]);
+ $this->assertContains('post / http/1.1', $sent);
+ $this->assertContains('content-type: application/x-www-form-urlencoded; charset=utf-8', $sent);
+ }
+
+ public function testSendsPostRequestsWithFiles()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+
+ $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl());
+ $request->getCurlOptions()->set('debug', true);
+ $request->setClient(new Client());
+ $request->addPostFiles(array(
+ 'foo' => __FILE__,
+ ));
+ $request->addPostFields(array(
+ 'bar' => 'baz',
+ 'arr' => array('a' => 1, 'b' => 2),
+ ));
+ $this->updateForHandle($request);
+ $request->send();
+
+ // Ensure the CURLOPT_POSTFIELDS option was set properly
+ $options = $this->requestHandle->getOptions()->getAll();
+ if (version_compare(phpversion(), '5.5.0', '<')) {
+ $this->assertContains('@' . __FILE__ . ';filename=CurlHandleTest.php;type=text/x-', $options[CURLOPT_POSTFIELDS]['foo']);
+ } else{
+ $this->assertInstanceOf('CURLFile', $options[CURLOPT_POSTFIELDS]['foo']);
+ }
+ $this->assertEquals('baz', $options[CURLOPT_POSTFIELDS]['bar']);
+ $this->assertEquals('1', $options[CURLOPT_POSTFIELDS]['arr[a]']);
+ $this->assertEquals('2', $options[CURLOPT_POSTFIELDS]['arr[b]']);
+ // Ensure that a Content-Length header was sent by cURL
+ $this->assertTrue($request->hasHeader('Content-Length'));
+ }
+
+ public function testCurlConfigurationOptionsAreSet()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl());
+ $request->setClient(new Client('http://www.example.com'));
+ $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, 99);
+ $request->getCurlOptions()->set('curl.fake_opt', 99);
+ $request->getCurlOptions()->set(CURLOPT_PORT, 8181);
+ $handle = CurlHandle::factory($request);
+ $this->assertEquals(99, $handle->getOptions()->get(CURLOPT_CONNECTTIMEOUT));
+ $this->assertEquals(8181, $handle->getOptions()->get(CURLOPT_PORT));
+ $this->assertNull($handle->getOptions()->get('curl.fake_opt'));
+ $this->assertNull($handle->getOptions()->get('fake_opt'));
+ }
+
+ public function testEnsuresRequestsHaveResponsesWhenUpdatingFromTransfer()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl());
+ $handle = CurlHandle::factory($request);
+ $handle->updateRequestFromTransfer($request);
+ }
+
+ public function testCanSendBodyAsString()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/', null, 'foo');
+ $request->getCurlOptions()->set('body_as_string', true);
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(false);
+ $this->assertContains('PUT /', $requests[0]);
+ $this->assertContains("\nfoo", $requests[0]);
+ $this->assertContains('content-length: 3', $requests[0]);
+ $this->assertNotContains('content-type', $requests[0]);
+ }
+
+ public function testCanSendPostBodyAsString()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post('/', null, 'foo');
+ $request->getCurlOptions()->set('body_as_string', true);
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(false);
+ $this->assertContains('POST /', $requests[0]);
+ $this->assertContains("\nfoo", $requests[0]);
+ $this->assertContains('content-length: 3', $requests[0]);
+ $this->assertNotContains('content-type', $requests[0]);
+ }
+
+ public function testAllowsWireTransferInfoToBeEnabled()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl());
+ $request->getCurlOptions()->set('debug', true);
+ $handle = CurlHandle::factory($request);
+ $this->assertNotNull($handle->getOptions()->get(CURLOPT_STDERR));
+ $this->assertNotNull($handle->getOptions()->get(CURLOPT_VERBOSE));
+ }
+
+ public function testAddsCustomCurlOptions()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl());
+ $request->getCurlOptions()->set(CURLOPT_TIMEOUT, 200);
+ $handle = CurlHandle::factory($request);
+ $this->assertEquals(200, $handle->getOptions()->get(CURLOPT_TIMEOUT));
+ }
+
+ public function testSendsPostUploadsWithContentDispositionHeaders()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n");
+
+ $fileToUpload = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'TestData' . DIRECTORY_SEPARATOR . 'test_service.json';
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post();
+ $request->addPostFile('foo', $fileToUpload, 'application/json');
+ $request->addPostFile('foo', __FILE__);
+
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $body = (string) $requests[0]->getBody();
+
+ $this->assertContains('Content-Disposition: form-data; name="foo[0]"; filename="', $body);
+ $this->assertContains('Content-Type: application/json', $body);
+ $this->assertContains('Content-Type: text/x-', $body);
+ $this->assertContains('Content-Disposition: form-data; name="foo[1]"; filename="', $body);
+ }
+
+ public function requestMethodProvider()
+ {
+ return array(array('POST'), array('PUT'), array('PATCH'));
+ }
+
+ /**
+ * @dataProvider requestMethodProvider
+ */
+ public function testSendsRequestsWithNoBodyUsingContentLengthZero($method)
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $client->createRequest($method)->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertFalse($requests[0]->hasHeader('Transfer-Encoding'));
+ $this->assertTrue($requests[0]->hasHeader('Content-Length'));
+ $this->assertEquals('0', (string) $requests[0]->getHeader('Content-Length'));
+ }
+
+ /**
+ * @dataProvider provideCurlConfig
+ */
+ public function testParseCurlConfigConvertsStringKeysToConstantKeys($options, $expected)
+ {
+ $actual = CurlHandle::parseCurlConfig($options);
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Data provider for curl configurations
+ *
+ * @return array
+ */
+ public function provideCurlConfig()
+ {
+ return array(
+ // Conversion of option name to constant value
+ array(
+ array(
+ 'CURLOPT_PORT' => 10,
+ 'CURLOPT_TIMEOUT' => 99
+ ),
+ array(
+ CURLOPT_PORT => 10,
+ CURLOPT_TIMEOUT => 99
+ )
+ ),
+ // Keeps non constant options
+ array(
+ array('debug' => true),
+ array('debug' => true)
+ ),
+ // Conversion of constant names to constant values
+ array(
+ array('debug' => 'CURLPROXY_HTTP'),
+ array('debug' => CURLPROXY_HTTP)
+ )
+ );
+ }
+
+ public function testSeeksToBeginningOfStreamWhenSending()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/', null, 'test');
+ $request->send();
+ $request->send();
+
+ $received = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals(2, count($received));
+ $this->assertEquals('test', (string) $received[0]->getBody());
+ $this->assertEquals('test', (string) $received[1]->getBody());
+ }
+
+ public function testAllowsCurloptEncodingToBeSet()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('/', null);
+ $request->getCurlOptions()->set(CURLOPT_ENCODING, '');
+ $this->updateForHandle($request);
+ $request->send();
+ $options = $this->requestHandle->getOptions()->getAll();
+ $this->assertSame('', $options[CURLOPT_ENCODING]);
+ $received = $this->getServer()->getReceivedRequests(false);
+ $this->assertContainsIns('accept: */*', $received[0]);
+ $this->assertContainsIns('accept-encoding: ', $received[0]);
+ }
+
+ public function testSendsExpectHeaderWhenSizeIsGreaterThanCutoff()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/', null, 'test');
+ // Start sending the expect header to 2 bytes
+ $this->updateForHandle($request);
+ $request->setExpectHeaderCutoff(2)->send();
+ $options = $this->requestHandle->getOptions()->getAll();
+ $this->assertContains('Expect: 100-Continue', $options[CURLOPT_HTTPHEADER]);
+ $received = $this->getServer()->getReceivedRequests(false);
+ $this->assertContainsIns('expect: 100-continue', $received[0]);
+ }
+
+ public function testSetsCurloptEncodingWhenAcceptEncodingHeaderIsSet()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('/', array(
+ 'Accept' => 'application/json',
+ 'Accept-Encoding' => 'gzip, deflate',
+ ));
+ $this->updateForHandle($request);
+ $request->send();
+ $options = $this->requestHandle->getOptions()->getAll();
+ $this->assertSame('gzip, deflate', $options[CURLOPT_ENCODING]);
+ $received = $this->getServer()->getReceivedRequests(false);
+ $this->assertContainsIns('accept: application/json', $received[0]);
+ $this->assertContainsIns('accept-encoding: gzip, deflate', $received[0]);
+ }
+
+ public function testSendsPostFieldsForNonPostRequests()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n");
+
+ $client = new Client();
+ $request = $client->put($this->getServer()->getUrl(), null, array(
+ 'foo' => 'baz',
+ 'baz' => 'bar'
+ ));
+
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('PUT', $requests[0]->getMethod());
+ $this->assertEquals(
+ 'application/x-www-form-urlencoded; charset=utf-8',
+ (string) $requests[0]->getHeader('Content-Type')
+ );
+ $this->assertEquals(15, (string) $requests[0]->getHeader('Content-Length'));
+ $this->assertEquals('foo=baz&baz=bar', (string) $requests[0]->getBody());
+ }
+
+ public function testSendsPostFilesForNonPostRequests()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n");
+
+ $client = new Client();
+ $request = $client->put($this->getServer()->getUrl(), null, array(
+ 'foo' => '@' . __FILE__
+ ));
+
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('PUT', $requests[0]->getMethod());
+ $this->assertContains('multipart/form-data', (string) $requests[0]->getHeader('Content-Type'));
+ $this->assertContains('testSendsPostFilesForNonPostRequests', (string) $requests[0]->getBody());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php
new file mode 100644
index 0000000..e04141c
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Guzzle\Tests\Http\Curl;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Curl\CurlMultiProxy;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Curl\CurlMultiProxy
+ */
+class CurlMultiProxyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ const SELECT_TIMEOUT = 23.1;
+
+ const MAX_HANDLES = 2;
+
+ /** @var \Guzzle\Http\Curl\CurlMultiProxy */
+ private $multi;
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->multi = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT);
+ }
+
+ public function tearDown()
+ {
+ unset($this->multi);
+ }
+
+ public function testConstructorSetsMaxHandles()
+ {
+ $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT);
+ $this->assertEquals(self::MAX_HANDLES, $this->readAttribute($m, 'maxHandles'));
+ }
+
+ public function testConstructorSetsSelectTimeout()
+ {
+ $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT);
+ $this->assertEquals(self::SELECT_TIMEOUT, $this->readAttribute($m, 'selectTimeout'));
+ }
+
+ public function testAddingRequestsAddsToQueue()
+ {
+ $r = new Request('GET', 'http://www.foo.com');
+ $this->assertSame($this->multi, $this->multi->add($r));
+ $this->assertEquals(1, count($this->multi));
+ $this->assertEquals(array($r), $this->multi->all());
+
+ $this->assertTrue($this->multi->remove($r));
+ $this->assertFalse($this->multi->remove($r));
+ $this->assertEquals(0, count($this->multi));
+ }
+
+ public function testResetClearsState()
+ {
+ $r = new Request('GET', 'http://www.foo.com');
+ $this->multi->add($r);
+ $this->multi->reset();
+ $this->assertEquals(0, count($this->multi));
+ }
+
+ public function testSendWillSendQueuedRequestsFirst()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $events = array();
+ $client->getCurlMulti()->getEventDispatcher()->addListener(
+ CurlMultiProxy::ADD_REQUEST,
+ function ($e) use (&$events) {
+ $events[] = $e;
+ }
+ );
+ $request = $client->get();
+ $request->getEventDispatcher()->addListener('request.complete', function () use ($client) {
+ $client->get('/foo')->send();
+ });
+ $request->send();
+ $received = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals(2, count($received));
+ $this->assertEquals($this->getServer()->getUrl(), $received[0]->getUrl());
+ $this->assertEquals($this->getServer()->getUrl() . 'foo', $received[1]->getUrl());
+ $this->assertEquals(2, count($events));
+ }
+
+ public function testTrimsDownMaxHandleCount()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $client->setCurlMulti(new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT));
+ $request = $client->get();
+ $request->send();
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ $handles = $this->readAttribute($client->getCurlMulti(), 'handles');
+ $this->assertEquals(2, count($handles));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php
new file mode 100644
index 0000000..1272281
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php
@@ -0,0 +1,455 @@
+<?php
+
+namespace Guzzle\Tests\Http\Curl;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Curl\CurlMulti;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Tests\Mock\MockMulti;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Curl\CurlMulti
+ */
+class CurlMultiTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var \Guzzle\Http\Curl\CurlMulti */
+ private $multi;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->multi = new MockMulti();
+ }
+
+ public function tearDown()
+ {
+ unset($this->multi);
+ }
+
+ public function testConstructorCreateMultiHandle()
+ {
+ $this->assertInternalType('resource', $this->multi->getHandle());
+ $this->assertEquals('curl_multi', get_resource_type($this->multi->getHandle()));
+ }
+
+ public function testDestructorClosesMultiHandle()
+ {
+ $handle = $this->multi->getHandle();
+ $this->multi->__destruct();
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testRequestsCanBeAddedAndCounted()
+ {
+ $multi = new CurlMulti();
+ $request1 = new Request('GET', 'http://www.google.com/');
+ $multi->add($request1);
+ $this->assertEquals(array($request1), $multi->all());
+ $request2 = new Request('POST', 'http://www.google.com/');
+ $multi->add($request2);
+ $this->assertEquals(array($request1, $request2), $multi->all());
+ $this->assertEquals(2, count($multi));
+ }
+
+ public function testRequestsCanBeRemoved()
+ {
+ $request1 = new Request('GET', 'http://www.google.com/');
+ $this->multi->add($request1);
+ $request2 = new Request('PUT', 'http://www.google.com/');
+ $this->multi->add($request2);
+ $this->assertEquals(array($request1, $request2), $this->multi->all());
+ $this->assertTrue($this->multi->remove($request1));
+ $this->assertFalse($this->multi->remove($request1));
+ $this->assertEquals(array($request2), $this->multi->all());
+ }
+
+ public function testsResetRemovesRequestsAndResetsState()
+ {
+ $this->multi->add(new Request('GET', 'http://www.google.com/'));
+ $this->multi->reset();
+ $this->assertEquals(array(), $this->multi->all());
+ }
+
+ public function testSendsRequestsThroughCurl()
+ {
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 204 No content\r\n" .
+ "Content-Length: 0\r\n" .
+ "Server: Jetty(6.1.3)\r\n\r\n",
+ "HTTP/1.1 200 OK\r\n" .
+ "Content-Type: text/html; charset=utf-8\r\n" .
+ "Content-Length: 4\r\n" .
+ "Server: Jetty(6.1.3)\r\n\r\n" .
+ "data"
+ ));
+
+ $request1 = new Request('GET', $this->getServer()->getUrl());
+ $request2 = new Request('GET', $this->getServer()->getUrl());
+ $this->multi->add($request1);
+ $this->multi->add($request2);
+ $this->multi->send();
+
+ $response1 = $request1->getResponse();
+ $response2 = $request2->getResponse();
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2);
+
+ $this->assertTrue($response1->getBody(true) == 'data' || $response2->getBody(true) == 'data');
+ $this->assertTrue($response1->getBody(true) == '' || $response2->getBody(true) == '');
+ $this->assertTrue($response1->getStatusCode() == '204' || $response2->getStatusCode() == '204');
+ $this->assertNotEquals((string) $response1, (string) $response2);
+ }
+
+ public function testSendsThroughCurlAndAggregatesRequestExceptions()
+ {
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n" .
+ "Content-Type: text/html; charset=utf-8\r\n" .
+ "Content-Length: 4\r\n" .
+ "Server: Jetty(6.1.3)\r\n" .
+ "\r\n" .
+ "data",
+ "HTTP/1.1 204 No content\r\n" .
+ "Content-Length: 0\r\n" .
+ "Server: Jetty(6.1.3)\r\n" .
+ "\r\n",
+ "HTTP/1.1 404 Not Found\r\n" .
+ "Content-Length: 0\r\n" .
+ "\r\n"
+ ));
+
+ $request1 = new Request('GET', $this->getServer()->getUrl());
+ $request2 = new Request('HEAD', $this->getServer()->getUrl());
+ $request3 = new Request('GET', $this->getServer()->getUrl());
+ $this->multi->add($request1);
+ $this->multi->add($request2);
+ $this->multi->add($request3);
+
+ try {
+ $this->multi->send();
+ $this->fail('MultiTransferException not thrown when aggregating request exceptions');
+ } catch (MultiTransferException $e) {
+
+ $this->assertTrue($e->containsRequest($request1));
+ $this->assertTrue($e->containsRequest($request2));
+ $this->assertTrue($e->containsRequest($request3));
+ $this->assertInstanceOf('ArrayIterator', $e->getIterator());
+ $this->assertEquals(1, count($e));
+ $exceptions = $e->getIterator();
+
+ $response1 = $request1->getResponse();
+ $response2 = $request2->getResponse();
+ $response3 = $request3->getResponse();
+
+ $this->assertNotEquals((string) $response1, (string) $response2);
+ $this->assertNotEquals((string) $response3, (string) $response1);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response3);
+
+ $failed = $exceptions[0]->getResponse();
+ $this->assertEquals(404, $failed->getStatusCode());
+ $this->assertEquals(1, count($e));
+
+ // Test the IteratorAggregate functionality
+ foreach ($e as $except) {
+ $this->assertEquals($failed, $except->getResponse());
+ }
+
+ $this->assertEquals(1, count($e->getFailedRequests()));
+ $this->assertEquals(2, count($e->getSuccessfulRequests()));
+ $this->assertEquals(3, count($e->getAllRequests()));
+ }
+ }
+
+ public function testCurlErrorsAreCaught()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ try {
+ $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/');
+ $request->setClient(new Client());
+ $request->getCurlOptions()->set(CURLOPT_FRESH_CONNECT, true);
+ $request->getCurlOptions()->set(CURLOPT_FORBID_REUSE, true);
+ $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, 5);
+ $request->send();
+ $this->fail('CurlException not thrown');
+ } catch (CurlException $e) {
+ $m = $e->getMessage();
+ $this->assertContains('[curl] ', $m);
+ $this->assertContains('[url] http://127.0.0.1:9876/', $m);
+ $this->assertInternalType('array', $e->getCurlInfo());
+ }
+ }
+
+ public function testRemovesQueuedRequests()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/');
+ $r = new Response(200);
+ $request->setClient(new Client());
+ $request->setResponse($r, true);
+ $this->multi->add($request);
+ $this->multi->send();
+ $this->assertSame($r, $request->getResponse());
+ }
+
+ public function testRemovesQueuedRequestsAddedInTransit()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
+ $client = new Client($this->getServer()->getUrl());
+ $r = $client->get();
+ $r->getEventDispatcher()->addListener('request.receive.status_line', function (Event $event) use ($client) {
+ // Create a request using a queued response
+ $request = $client->get()->setResponse(new Response(200), true);
+ $request->send();
+ });
+ $r->send();
+ $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false)));
+ }
+
+ public function testCatchesExceptionsBeforeSendingSingleRequest()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $multi = new CurlMulti();
+ $client->setCurlMulti($multi);
+ $request = $client->get();
+ $request->getEventDispatcher()->addListener('request.before_send', function() {
+ throw new \RuntimeException('Testing!');
+ });
+ try {
+ $request->send();
+ $this->fail('Did not throw');
+ } catch (\RuntimeException $e) {
+ // Ensure it was removed
+ $this->assertEquals(0, count($multi));
+ }
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\ExceptionCollection
+ * @expectedExceptionMessage Thrown before sending!
+ */
+ public function testCatchesExceptionsBeforeSendingMultipleRequests()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get();
+ $request->getEventDispatcher()->addListener('request.before_send', function() {
+ throw new \RuntimeException('Thrown before sending!');
+ });
+ $client->send(array($request));
+ }
+
+ public function testCatchesExceptionsWhenRemovingQueuedRequests()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $r = $client->get();
+ $r->getEventDispatcher()->addListener('request.sent', function() use ($client) {
+ // Create a request using a queued response
+ $client->get()->setResponse(new Response(404), true)->send();
+ });
+ try {
+ $r->send();
+ $this->fail('Did not throw');
+ } catch (BadResponseException $e) {
+ $this->assertCount(0, $client->getCurlMulti());
+ }
+ }
+
+ public function testCatchesExceptionsWhenRemovingQueuedRequestsBeforeSending()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $client = new Client($this->getServer()->getUrl());
+ $r = $client->get();
+ $r->getEventDispatcher()->addListener('request.before_send', function() use ($client) {
+ // Create a request using a queued response
+ $client->get()->setResponse(new Response(404), true)->send();
+ });
+ try {
+ $r->send();
+ $this->fail('Did not throw');
+ } catch (BadResponseException $e) {
+ $this->assertCount(0, $client->getCurlMulti());
+ }
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage test
+ */
+ public function testDoesNotCatchRandomExceptionsThrownDuringPerform()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $multi = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('perform'));
+ $multi->expects($this->once())
+ ->method('perform')
+ ->will($this->throwException(new \RuntimeException('test')));
+ $multi->add($client->get());
+ $multi->send();
+ }
+
+ public function testDoesNotSendRequestsDecliningToBeSent()
+ {
+ if (!defined('CURLOPT_TIMEOUT_MS')) {
+ $this->markTestSkipped('Update curl');
+ }
+
+ // Create a client that is bound to fail connecting
+ $client = new Client('http://127.0.0.1:123', array(
+ 'curl.CURLOPT_PORT' => 123,
+ 'curl.CURLOPT_CONNECTTIMEOUT_MS' => 1,
+ ));
+
+ $request = $client->get();
+ $multi = new CurlMulti();
+ $multi->add($request);
+
+ // Listen for request exceptions, and when they occur, first change the
+ // state of the request back to transferring, and then just allow it to
+ // exception out
+ $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use ($multi) {
+ $retries = $event['request']->getParams()->get('retries');
+ // Allow the first failure to retry
+ if ($retries == 0) {
+ $event['request']->setState('transfer');
+ $event['request']->getParams()->set('retries', 1);
+ // Remove the request to try again
+ $multi->remove($event['request']);
+ $multi->add($event['request']);
+ }
+ });
+
+ try {
+ $multi->send();
+ $this->fail('Did not throw an exception at all!?!');
+ } catch (\Exception $e) {
+ $this->assertEquals(1, $request->getParams()->get('retries'));
+ }
+ }
+
+ public function testDoesNotThrowExceptionsWhenRequestsRecoverWithRetry()
+ {
+ $this->getServer()->flush();
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get();
+ $request->getEventDispatcher()->addListener('request.before_send', function(Event $event) {
+ $event['request']->setResponse(new Response(200));
+ });
+
+ $multi = new CurlMulti();
+ $multi->add($request);
+ $multi->send();
+ $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false)));
+ }
+
+ public function testDoesNotThrowExceptionsWhenRequestsRecoverWithSuccess()
+ {
+ // Attempt a port that 99.9% is not listening
+ $client = new Client('http://127.0.0.1:123');
+ $request = $client->get();
+ // Ensure it times out quickly if needed
+ $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, 1)->set(CURLOPT_CONNECTTIMEOUT_MS, 1);
+
+ $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use (&$count) {
+ $event['request']->setResponse(new Response(200));
+ });
+
+ $multi = new CurlMulti();
+ $multi->add($request);
+ $multi->send();
+
+ // Ensure that the exception was caught, and the response was set manually
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ }
+
+ public function testHardResetReopensMultiHandle()
+ {
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ $stream = fopen('php://temp', 'w+');
+ $client = new Client($this->getServer()->getUrl());
+ $client->getConfig()->set('curl.CURLOPT_VERBOSE', true)->set('curl.CURLOPT_STDERR', $stream);
+
+ $request = $client->get();
+ $multi = new CurlMulti();
+ $multi->add($request);
+ $multi->send();
+ $multi->reset(true);
+ $multi->add($request);
+ $multi->send();
+
+ rewind($stream);
+ $this->assertNotContains('Re-using existing connection', stream_get_contents($stream));
+ }
+
+ public function testThrowsMeaningfulExceptionsForCurlMultiErrors()
+ {
+ $multi = new CurlMulti();
+
+ // Set the state of the multi object to sending to trigger the exception
+ $reflector = new \ReflectionMethod('Guzzle\Http\Curl\CurlMulti', 'checkCurlResult');
+ $reflector->setAccessible(true);
+
+ // Successful
+ $reflector->invoke($multi, 0);
+
+ // Known error
+ try {
+ $reflector->invoke($multi, CURLM_BAD_HANDLE);
+ $this->fail('Expected an exception here');
+ } catch (CurlException $e) {
+ $this->assertContains('The passed-in handle is not a valid CURLM handle.', $e->getMessage());
+ $this->assertContains('CURLM_BAD_HANDLE', $e->getMessage());
+ $this->assertContains(strval(CURLM_BAD_HANDLE), $e->getMessage());
+ }
+
+ // Unknown error
+ try {
+ $reflector->invoke($multi, 255);
+ $this->fail('Expected an exception here');
+ } catch (CurlException $e) {
+ $this->assertEquals('Unexpected cURL error: 255', $e->getMessage());
+ }
+ }
+
+ public function testRequestBeforeSendIncludesContentLengthHeaderIfEmptyBody()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request = new Request('PUT', $this->getServer()->getUrl());
+ $that = $this;
+ $request->getEventDispatcher()->addListener('request.before_send', function ($event) use ($that) {
+ $that->assertEquals(0, $event['request']->getHeader('Content-Length'));
+ });
+ $this->multi->add($request);
+ $this->multi->send();
+ }
+
+ public function testRemovesConflictingTransferEncodingHeader()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put('/', null, fopen($this->getServer()->getUrl(), 'r'));
+ $request->setHeader('Content-Length', 4);
+ $request->send();
+ $received = $this->getServer()->getReceivedRequests(true);
+ $this->assertFalse($received[1]->hasHeader('Transfer-Encoding'));
+ $this->assertEquals(4, (string) $received[1]->getHeader('Content-Length'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php
new file mode 100644
index 0000000..c7b5ee6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Tests\Http\Curl;
+
+use Guzzle\Http\Curl\CurlVersion;
+
+/**
+ * @covers Guzzle\Http\Curl\CurlVersion
+ */
+class CurlVersionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCachesCurlInfo()
+ {
+ $info = curl_version();
+ $instance = CurlVersion::getInstance();
+
+ // Clear out the info cache
+ $refObject = new \ReflectionObject($instance);
+ $refProperty = $refObject->getProperty('version');
+ $refProperty->setAccessible(true);
+ $refProperty->setValue($instance, array());
+
+ $this->assertEquals($info, $instance->getAll());
+ $this->assertEquals($info, $instance->getAll());
+
+ $this->assertEquals($info['version'], $instance->get('version'));
+ $this->assertFalse($instance->get('foo'));
+ }
+
+ public function testIsSingleton()
+ {
+ $refObject = new \ReflectionClass('Guzzle\Http\Curl\CurlVersion');
+ $refProperty = $refObject->getProperty('instance');
+ $refProperty->setAccessible(true);
+ $refProperty->setValue(null, null);
+
+ $this->assertInstanceOf('Guzzle\Http\Curl\CurlVersion', CurlVersion::getInstance());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php
new file mode 100644
index 0000000..c69e0c9
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Guzzle\Tests\Http\Curl;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Curl\RequestMediator;
+
+/**
+ * @covers Guzzle\Http\Curl\RequestMediator
+ */
+class RequestMediatorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public $events = array();
+
+ public function event($event)
+ {
+ $this->events[] = $event;
+ }
+
+ public function testEmitsEvents()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://www.example.com');
+ $request->setBody('foo');
+ $request->setResponse(new Response(200));
+
+ // Ensure that IO events are emitted
+ $request->getCurlOptions()->set('emit_io', true);
+
+ // Attach listeners for each event type
+ $request->getEventDispatcher()->addListener('curl.callback.progress', array($this, 'event'));
+ $request->getEventDispatcher()->addListener('curl.callback.read', array($this, 'event'));
+ $request->getEventDispatcher()->addListener('curl.callback.write', array($this, 'event'));
+
+ $mediator = new RequestMediator($request, true);
+
+ $mediator->progress('a', 'b', 'c', 'd');
+ $this->assertEquals(1, count($this->events));
+ $this->assertEquals('curl.callback.progress', $this->events[0]->getName());
+
+ $this->assertEquals(3, $mediator->writeResponseBody('foo', 'bar'));
+ $this->assertEquals(2, count($this->events));
+ $this->assertEquals('curl.callback.write', $this->events[1]->getName());
+ $this->assertEquals('bar', $this->events[1]['write']);
+ $this->assertSame($request, $this->events[1]['request']);
+
+ $this->assertEquals('foo', $mediator->readRequestBody('a', 'b', 3));
+ $this->assertEquals(3, count($this->events));
+ $this->assertEquals('curl.callback.read', $this->events[2]->getName());
+ $this->assertEquals('foo', $this->events[2]['read']);
+ $this->assertSame($request, $this->events[2]['request']);
+ }
+
+ public function testDoesNotUseRequestResponseBodyWhenNotCustom()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 307 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nHI",
+ "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nFI",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest",
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $response = $client->get()->send();
+ $this->assertEquals('test', $response->getBody(true));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php
new file mode 100644
index 0000000..124a44d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\QueryString;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\EntityBody
+ */
+class EntityBodyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testFactoryThrowsException()
+ {
+ $body = EntityBody::factory(false);
+ }
+
+ public function testFactory()
+ {
+ $body = EntityBody::factory('data');
+ $this->assertEquals('data', (string) $body);
+ $this->assertEquals(4, $body->getContentLength());
+ $this->assertEquals('PHP', $body->getWrapper());
+ $this->assertEquals('TEMP', $body->getStreamType());
+
+ $handle = fopen(__DIR__ . '/../../../../phpunit.xml.dist', 'r');
+ if (!$handle) {
+ $this->fail('Could not open test file');
+ }
+ $body = EntityBody::factory($handle);
+ $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri());
+ $this->assertTrue($body->isLocal());
+ $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri());
+ $this->assertEquals(filesize(__DIR__ . '/../../../../phpunit.xml.dist'), $body->getContentLength());
+
+ // make sure that a body will return as the same object
+ $this->assertTrue($body === EntityBody::factory($body));
+ }
+
+ public function testFactoryCreatesTempStreamByDefault()
+ {
+ $body = EntityBody::factory('');
+ $this->assertEquals('PHP', $body->getWrapper());
+ $this->assertEquals('TEMP', $body->getStreamType());
+ $body = EntityBody::factory();
+ $this->assertEquals('PHP', $body->getWrapper());
+ $this->assertEquals('TEMP', $body->getStreamType());
+ }
+
+ public function testFactoryCanCreateFromObject()
+ {
+ $body = EntityBody::factory(new QueryString(array('foo' => 'bar')));
+ $this->assertEquals('foo=bar', (string) $body);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testFactoryEnsuresObjectsHaveToStringMethod()
+ {
+ EntityBody::factory(new \stdClass('a'));
+ }
+
+ public function testHandlesCompression()
+ {
+ $body = EntityBody::factory('testing 123...testing 123');
+ $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must initially return FALSE');
+ $size = $body->getContentLength();
+ $body->compress();
+ $this->assertEquals('gzip', $body->getContentEncoding(), '-> getContentEncoding() must return the correct encoding after compressing');
+ $this->assertEquals(gzdeflate('testing 123...testing 123'), (string) $body);
+ $this->assertTrue($body->getContentLength() < $size);
+ $this->assertTrue($body->uncompress());
+ $this->assertEquals('testing 123...testing 123', (string) $body);
+ $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must reset to FALSE');
+
+ if (in_array('bzip2.*', stream_get_filters())) {
+ $this->assertTrue($body->compress('bzip2.compress'));
+ $this->assertEquals('compress', $body->getContentEncoding(), '-> compress() must set \'compress\' as the Content-Encoding');
+ }
+
+ $this->assertFalse($body->compress('non-existent'), '-> compress() must return false when a non-existent stream filter is used');
+
+ // Release the body
+ unset($body);
+
+ // Use gzip compression on the initial content. This will include a
+ // gzip header which will need to be stripped when deflating the stream
+ $body = EntityBody::factory(gzencode('test'));
+ $this->assertSame($body, $body->setStreamFilterContentEncoding('zlib.deflate'));
+ $this->assertTrue($body->uncompress('zlib.inflate'));
+ $this->assertEquals('test', (string) $body);
+ unset($body);
+
+ // Test using a very long string
+ $largeString = '';
+ for ($i = 0; $i < 25000; $i++) {
+ $largeString .= chr(rand(33, 126));
+ }
+ $body = EntityBody::factory($largeString);
+ $this->assertEquals($largeString, (string) $body);
+ $this->assertTrue($body->compress());
+ $this->assertNotEquals($largeString, (string) $body);
+ $compressed = (string) $body;
+ $this->assertTrue($body->uncompress());
+ $this->assertEquals($largeString, (string) $body);
+ $this->assertEquals($compressed, gzdeflate($largeString));
+
+ $body = EntityBody::factory(fopen(__DIR__ . '/../TestData/compress_test', 'w'));
+ $this->assertFalse($body->compress());
+ unset($body);
+
+ unlink(__DIR__ . '/../TestData/compress_test');
+ }
+
+ public function testDeterminesContentType()
+ {
+ // Test using a string/temp stream
+ $body = EntityBody::factory('testing 123...testing 123');
+ $this->assertNull($body->getContentType());
+
+ // Use a local file
+ $body = EntityBody::factory(fopen(__FILE__, 'r'));
+ $this->assertContains('text/x-', $body->getContentType());
+ }
+
+ public function testCreatesMd5Checksum()
+ {
+ $body = EntityBody::factory('testing 123...testing 123');
+ $this->assertEquals(md5('testing 123...testing 123'), $body->getContentMd5());
+
+ $server = $this->getServer()->enqueue(
+ "HTTP/1.1 200 OK" . "\r\n" .
+ "Content-Length: 3" . "\r\n\r\n" .
+ "abc"
+ );
+
+ $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r'));
+ $this->assertFalse($body->getContentMd5());
+ }
+
+ public function testSeeksToOriginalPosAfterMd5()
+ {
+ $body = EntityBody::factory('testing 123');
+ $body->seek(4);
+ $this->assertEquals(md5('testing 123'), $body->getContentMd5());
+ $this->assertEquals(4, $body->ftell());
+ $this->assertEquals('ing 123', $body->read(1000));
+ }
+
+ public function testGetTypeFormBodyFactoring()
+ {
+ $body = EntityBody::factory(array('key1' => 'val1', 'key2' => 'val2'));
+ $this->assertEquals('key1=val1&key2=val2', (string) $body);
+ }
+
+ public function testAllowsCustomRewind()
+ {
+ $body = EntityBody::factory('foo');
+ $rewound = false;
+ $body->setRewindFunction(function ($body) use (&$rewound) {
+ $rewound = true;
+ return $body->seek(0);
+ });
+ $body->seek(2);
+ $this->assertTrue($body->rewind());
+ $this->assertTrue($rewound);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testCustomRewindFunctionMustBeCallable()
+ {
+ $body = EntityBody::factory();
+ $body->setRewindFunction('foo');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php
new file mode 100644
index 0000000..df3e4b7
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Tests\Http\Exception;
+
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Http\Curl\CurlHandle;
+
+/**
+ * @covers Guzzle\Http\Exception\CurlException
+ */
+class CurlExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testStoresCurlError()
+ {
+ $e = new CurlException();
+ $this->assertNull($e->getError());
+ $this->assertNull($e->getErrorNo());
+ $this->assertSame($e, $e->setError('test', 12));
+ $this->assertEquals('test', $e->getError());
+ $this->assertEquals(12, $e->getErrorNo());
+
+ $handle = new CurlHandle(curl_init(), array());
+ $e->setCurlHandle($handle);
+ $this->assertSame($handle, $e->getCurlHandle());
+ $handle->close();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php
new file mode 100644
index 0000000..12cfd36
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Guzzle\Tests\Http\Exception;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\Exception\BadResponseException;
+
+class ExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @covers Guzzle\Http\Exception\RequestException
+ */
+ public function testRequestException()
+ {
+ $e = new RequestException('Message');
+ $request = new Request('GET', 'http://www.guzzle-project.com/');
+ $e->setRequest($request);
+ $this->assertEquals($request, $e->getRequest());
+ }
+
+ /**
+ * @covers Guzzle\Http\Exception\BadResponseException
+ */
+ public function testBadResponseException()
+ {
+ $e = new BadResponseException('Message');
+ $response = new Response(200);
+ $e->setResponse($response);
+ $this->assertEquals($response, $e->getResponse());
+ }
+
+ /**
+ * @covers Guzzle\Http\Exception\BadResponseException::factory
+ */
+ public function testCreatesGenericErrorExceptionOnError()
+ {
+ $request = new Request('GET', 'http://www.example.com');
+ $response = new Response(307);
+ $e = BadResponseException::factory($request, $response);
+ $this->assertInstanceOf('Guzzle\Http\Exception\BadResponseException', $e);
+ }
+
+ /**
+ * @covers Guzzle\Http\Exception\BadResponseException::factory
+ */
+ public function testCreatesClientErrorExceptionOnClientError()
+ {
+ $request = new Request('GET', 'http://www.example.com');
+ $response = new Response(404);
+ $e = BadResponseException::factory($request, $response);
+ $this->assertInstanceOf('Guzzle\Http\Exception\ClientErrorResponseException', $e);
+ }
+
+ /**
+ * @covers Guzzle\Http\Exception\BadResponseException::factory
+ */
+ public function testCreatesServerErrorExceptionOnServerError()
+ {
+ $request = new Request('GET', 'http://www.example.com');
+ $response = new Response(503);
+ $e = BadResponseException::factory($request, $response);
+ $this->assertInstanceOf('Guzzle\Http\Exception\ServerErrorResponseException', $e);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php
new file mode 100644
index 0000000..fa4ec26
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Guzzle\Tests\Http\Exception;
+
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Http\Message\Request;
+
+/**
+ * @covers Guzzle\Http\Exception\MultiTransferException
+ */
+class MultiTransferExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testHasRequests()
+ {
+ $r1 = new Request('GET', 'http://www.foo.com');
+ $r2 = new Request('GET', 'http://www.foo.com');
+ $e = new MultiTransferException();
+ $e->addSuccessfulRequest($r1);
+ $e->addFailedRequest($r2);
+ $this->assertEquals(array($r1), $e->getSuccessfulRequests());
+ $this->assertEquals(array($r2), $e->getSuccessfulRequests());
+ $this->assertEquals(array($r1, $r2), $e->getAllRequests());
+ $this->assertTrue($e->containsRequest($r1));
+ $this->assertTrue($e->containsRequest($r2));
+ $this->assertFalse($e->containsRequest(new Request('POST', '/foo')));
+ }
+
+ public function testCanSetRequests()
+ {
+ $s = array($r1 = new Request('GET', 'http://www.foo.com'));
+ $f = array($r2 = new Request('GET', 'http://www.foo.com'));
+ $e = new MultiTransferException();
+ $e->setSuccessfulRequests($s);
+ $e->setFailedRequests($f);
+ $this->assertEquals(array($r1), $e->getSuccessfulRequests());
+ $this->assertEquals(array($r2), $e->getSuccessfulRequests());
+ }
+
+ public function testAssociatesExceptionsWithRequests()
+ {
+ $r1 = new Request('GET', 'http://www.foo.com');
+ $re1 = new \Exception('foo');
+ $re2 = new \Exception('bar');
+ $e = new MultiTransferException();
+ $e->add($re2);
+ $e->addFailedRequestWithException($r1, $re1);
+ $this->assertSame($re1, $e->getExceptionForFailedRequest($r1));
+ $this->assertNull($e->getExceptionForFailedRequest(new Request('POST', '/foo')));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php
new file mode 100644
index 0000000..cd6355f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\IoEmittingEntityBody;
+
+/**
+ * @covers Guzzle\Http\IoEmittingEntityBody
+ */
+class IoEmittingEntityBodyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $body;
+ protected $decorated;
+
+ public function setUp()
+ {
+ $this->decorated = EntityBody::factory('hello');
+ $this->body = new IoEmittingEntityBody($this->decorated);
+ }
+
+ public function testEmitsReadEvents()
+ {
+ $e = null;
+ $this->body->getEventDispatcher()->addListener('body.read', function ($event) use (&$e) {
+ $e = $event;
+ });
+ $this->assertEquals('hel', $this->body->read(3));
+ $this->assertEquals('hel', $e['read']);
+ $this->assertEquals(3, $e['length']);
+ $this->assertSame($this->body, $e['body']);
+ }
+
+ public function testEmitsWriteEvents()
+ {
+ $e = null;
+ $this->body->getEventDispatcher()->addListener('body.write', function ($event) use (&$e) {
+ $e = $event;
+ });
+ $this->body->seek(0, SEEK_END);
+ $this->assertEquals(5, $this->body->write('there'));
+ $this->assertEquals('there', $e['write']);
+ $this->assertEquals(5, $e['result']);
+ $this->assertSame($this->body, $e['body']);
+ $this->assertEquals('hellothere', (string) $this->body);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php
new file mode 100644
index 0000000..9447d8c
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Http\Message\Header;
+use Guzzle\Http\Message\Request;
+use Guzzle\Common\Collection;
+
+/**
+ * @covers Guzzle\Http\Message\AbstractMessage
+ */
+class AbstractMessageTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Request Request object */
+ private $request;
+
+ /** @var AbstractMessage */
+ private $mock;
+
+ public function setUp()
+ {
+ parent::setUp();
+ $this->mock = $this->getMockForAbstractClass('Guzzle\Http\Message\AbstractMessage');
+ }
+
+ public function tearDown()
+ {
+ $this->mock = $this->request = null;
+ }
+
+ public function testGetParams()
+ {
+ $request = new Request('GET', 'http://example.com');
+ $this->assertInstanceOf('Guzzle\\Common\\Collection', $request->getParams());
+ }
+
+ public function testAddHeaders()
+ {
+ $this->mock->setHeader('A', 'B');
+
+ $this->assertEquals($this->mock, $this->mock->addHeaders(array(
+ 'X-Data' => '123'
+ )));
+
+ $this->assertTrue($this->mock->hasHeader('X-Data') !== false);
+ $this->assertTrue($this->mock->hasHeader('A') !== false);
+ }
+
+ public function testAllowsHeaderToSetAsHeader()
+ {
+ $h = new Header('A', 'B');
+ $this->mock->setHeader('A', $h);
+ $this->assertSame($h, $this->mock->getHeader('A'));
+ }
+
+ public function testGetHeader()
+ {
+ $this->mock->setHeader('Test', '123');
+ $this->assertEquals('123', $this->mock->getHeader('Test'));
+ }
+
+ public function testGetHeaders()
+ {
+ $this->assertSame($this->mock, $this->mock->setHeaders(array('a' => 'b', 'c' => 'd')));
+ $h = $this->mock->getHeaders();
+ $this->assertArrayHasKey('a', $h->toArray());
+ $this->assertArrayHasKey('c', $h->toArray());
+ $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('a'));
+ $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('c'));
+ }
+
+ public function testGetHeaderLinesUsesGlue()
+ {
+ $this->mock->setHeaders(array('a' => 'b', 'c' => 'd'));
+ $this->mock->addHeader('a', 'e');
+ $this->mock->getHeader('a')->setGlue('!');
+ $this->assertEquals(array(
+ 'a: b! e',
+ 'c: d'
+ ), $this->mock->getHeaderLines());
+ }
+
+ public function testHasHeader()
+ {
+ $this->assertFalse($this->mock->hasHeader('Foo'));
+ $this->mock->setHeader('Foo', 'Bar');
+ $this->assertEquals(true, $this->mock->hasHeader('Foo'));
+ $this->mock->setHeader('foo', 'yoo');
+ $this->assertEquals(true, $this->mock->hasHeader('Foo'));
+ $this->assertEquals(true, $this->mock->hasHeader('foo'));
+ $this->assertEquals(false, $this->mock->hasHeader('bar'));
+ }
+
+ public function testRemoveHeader()
+ {
+ $this->mock->setHeader('Foo', 'Bar');
+ $this->assertEquals(true, $this->mock->hasHeader('Foo'));
+ $this->mock->removeHeader('Foo');
+ $this->assertFalse($this->mock->hasHeader('Foo'));
+ }
+
+ public function testReturnsNullWhenHeaderIsNotFound()
+ {
+ $this->assertNull($this->mock->getHeader('foo'));
+ }
+
+ public function testAddingHeadersPreservesOriginalHeaderCase()
+ {
+ $this->mock->addHeaders(array(
+ 'test' => '123',
+ 'Test' => 'abc'
+ ));
+ $this->mock->addHeader('test', '456');
+ $this->mock->addHeader('test', '789');
+
+ $header = $this->mock->getHeader('test');
+ $this->assertContains('123', $header->toArray());
+ $this->assertContains('456', $header->toArray());
+ $this->assertContains('789', $header->toArray());
+ $this->assertContains('abc', $header->toArray());
+ }
+
+ public function testCanStoreEmptyHeaders()
+ {
+ $this->mock->setHeader('Content-Length', 0);
+ $this->assertTrue($this->mock->hasHeader('Content-Length'));
+ $this->assertEquals(0, (string) $this->mock->getHeader('Content-Length'));
+ }
+
+ public function testCanSetCustomHeaderFactory()
+ {
+ $f = new Header\HeaderFactory();
+ $this->mock->setHeaderFactory($f);
+ $this->assertSame($f, $this->readAttribute($this->mock, 'headerFactory'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php
new file mode 100644
index 0000000..191b022
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php
@@ -0,0 +1,434 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\PostFile;
+use Guzzle\Http\QueryString;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Message\EntityEnclosingRequest
+ */
+class EntityEnclosingRequestTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $client;
+
+ public function setUp()
+ {
+ $this->client = new Client();
+ }
+
+ public function tearDown()
+ {
+ $this->client = null;
+ }
+
+ public function testConstructorConfiguresRequest()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com', array(
+ 'X-Test' => '123'
+ ));
+ $request->setBody('Test');
+ $this->assertEquals('123', $request->getHeader('X-Test'));
+ $this->assertNull($request->getHeader('Expect'));
+ }
+
+ public function testCanSetBodyWithoutOverridingContentType()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com', array('Content-Type' => 'foooooo'));
+ $request->setBody('{"a":"b"}');
+ $this->assertEquals('foooooo', $request->getHeader('Content-Type'));
+ }
+
+ public function testRequestIncludesBodyInMessage()
+ {
+
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data');
+ $this->assertEquals("PUT / HTTP/1.1\r\n"
+ . "Host: www.guzzle-project.com\r\n"
+ . "Content-Length: 4\r\n\r\n"
+ . "data", (string) $request);
+ }
+
+ public function testRequestIncludesPostBodyInMessageOnlyWhenNoPostFiles()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array(
+ 'foo' => 'bar'
+ ));
+ $this->assertEquals("POST / HTTP/1.1\r\n"
+ . "Host: www.guzzle-project.com\r\n"
+ . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n"
+ . "foo=bar", (string) $request);
+
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array(
+ 'foo' => '@' . __FILE__
+ ));
+ $this->assertEquals("POST / HTTP/1.1\r\n"
+ . "Host: www.guzzle-project.com\r\n"
+ . "Content-Type: multipart/form-data\r\n"
+ . "Expect: 100-Continue\r\n\r\n", (string) $request);
+ }
+
+ public function testAddsPostFieldsAndSetsContentLength()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array(
+ 'data' => '123'
+ ));
+ $this->assertEquals("POST / HTTP/1.1\r\n"
+ . "Host: www.guzzle-project.com\r\n"
+ . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n"
+ . "data=123", (string) $request);
+ }
+
+ public function testAddsPostFilesAndSetsContentType()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/')
+ ->addPostFiles(array(
+ 'file' => __FILE__
+ ))->addPostFields(array(
+ 'a' => 'b'
+ ));
+ $message = (string) $request;
+ $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type'));
+ $this->assertEquals('100-Continue', $request->getHeader('Expect'));
+ }
+
+ public function testRequestBodyContainsPostFiles()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/');
+ $request->addPostFields(array(
+ 'test' => '123'
+ ));
+ $this->assertContains("\r\n\r\ntest=123", (string) $request);
+ }
+
+ public function testRequestBodyAddsContentLength()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/');
+ $request->setBody(EntityBody::factory('test'));
+ $this->assertEquals(4, (string) $request->getHeader('Content-Length'));
+ $this->assertFalse($request->hasHeader('Transfer-Encoding'));
+ }
+
+ public function testRequestBodyDoesNotUseContentLengthWhenChunked()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/', array(
+ 'Transfer-Encoding' => 'chunked'
+ ), 'test');
+ $this->assertNull($request->getHeader('Content-Length'));
+ $this->assertTrue($request->hasHeader('Transfer-Encoding'));
+ }
+
+ public function testRequestHasMutableBody()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data');
+ $body = $request->getBody();
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $body);
+ $this->assertSame($body, $request->getBody());
+
+ $newBody = EntityBody::factory('foobar');
+ $request->setBody($newBody);
+ $this->assertEquals('foobar', (string) $request->getBody());
+ $this->assertSame($newBody, $request->getBody());
+ }
+
+ public function testSetPostFields()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/');
+ $this->assertInstanceOf('Guzzle\\Http\\QueryString', $request->getPostFields());
+
+ $fields = new QueryString(array(
+ 'a' => 'b'
+ ));
+ $request->addPostFields($fields);
+ $this->assertEquals($fields->getAll(), $request->getPostFields()->getAll());
+ $this->assertEquals(array(), $request->getPostFiles());
+ }
+
+ public function testSetPostFiles()
+ {
+ $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl())
+ ->setClient(new Client())
+ ->addPostFiles(array(__FILE__))
+ ->addPostFields(array(
+ 'test' => 'abc'
+ ));
+
+ $request->getCurlOptions()->set('debug', true);
+
+ $this->assertEquals(array(
+ 'test' => 'abc'
+ ), $request->getPostFields()->getAll());
+
+ $files = $request->getPostFiles();
+ $post = $files['file'][0];
+ $this->assertEquals('file', $post->getFieldName());
+ $this->assertContains('text/x-', $post->getContentType());
+ $this->assertEquals(__FILE__, $post->getFilename());
+
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request->send();
+
+ $this->assertNotNull($request->getHeader('Content-Length'));
+ $this->assertContains('multipart/form-data; boundary=', (string) $request->getHeader('Content-Type'), '-> cURL must add the boundary');
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testSetPostFilesThrowsExceptionWhenFileIsNotFound()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/')
+ ->addPostFiles(array(
+ 'file' => 'filenotfound.ini'
+ ));
+ }
+
+ /**
+ * @expectedException Guzzle\Http\Exception\RequestException
+ */
+ public function testThrowsExceptionWhenNonStringsAreAddedToPost()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/')
+ ->addPostFile('foo', new \stdClass());
+ }
+
+ public function testAllowsContentTypeInPostUploads()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/')
+ ->addPostFile('foo', __FILE__, 'text/plain');
+
+ $this->assertEquals(array(
+ new PostFile('foo', __FILE__, 'text/plain')
+ ), $request->getPostFile('foo'));
+ }
+
+ public function testGuessesContentTypeOfPostUpload()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/')
+ ->addPostFile('foo', __FILE__);
+ $file = $request->getPostFile('foo');
+ $this->assertContains('text/x-', $file[0]->getContentType());
+ }
+
+ public function testAllowsContentDispositionFieldsInPostUploadsWhenSettingInBulk()
+ {
+ $postFile = new PostFile('foo', __FILE__, 'text/x-php');
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/')
+ ->addPostFiles(array('foo' => $postFile));
+
+ $this->assertEquals(array($postFile), $request->getPostFile('foo'));
+ }
+
+ public function testPostRequestsUseApplicationXwwwForUrlEncodedForArrays()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/');
+ $request->setPostField('a', 'b');
+ $this->assertContains("\r\n\r\na=b", (string) $request);
+ $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type'));
+ }
+
+ public function testProcessMethodAddsContentType()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/');
+ $request->setPostField('a', 'b');
+ $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type'));
+ }
+
+ public function testPostRequestsUseMultipartFormDataWithFiles()
+ {
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/');
+ $request->addPostFiles(array('file' => __FILE__));
+ $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type'));
+ }
+
+ public function testCanSendMultipleRequestsUsingASingleRequestObject()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ // Send the first request
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl())
+ ->setBody('test')
+ ->setClient(new Client());
+ $request->send();
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+
+ // Send the second request
+ $request->setBody('abcdefg', 'application/json', false);
+ $request->send();
+ $this->assertEquals(201, $request->getResponse()->getStatusCode());
+
+ // Ensure that the same request was sent twice with different bodies
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals(2, count($requests));
+ $this->assertEquals(4, (string) $requests[0]->getHeader('Content-Length'));
+ $this->assertEquals(7, (string) $requests[1]->getHeader('Content-Length'));
+ }
+
+ public function testRemovingPostFieldRebuildsPostFields()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com');
+ $request->setPostField('test', 'value');
+ $request->removePostField('test');
+ $this->assertNull($request->getPostField('test'));
+ }
+
+ public function testUsesChunkedTransferWhenBodyLengthCannotBeDetermined()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/');
+ $request->setBody(fopen($this->getServer()->getUrl(), 'r'));
+ $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding'));
+ $this->assertFalse($request->hasHeader('Content-Length'));
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\RequestException
+ */
+ public function testThrowsExceptionWhenContentLengthCannotBeDeterminedAndUsingHttp1()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/');
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request->setProtocolVersion('1.0');
+ $request->setBody(fopen($this->getServer()->getUrl(), 'r'));
+ }
+
+ public function testAllowsNestedPostData()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFields(array(
+ 'a' => array('b', 'c')
+ ));
+ $this->assertEquals(array(
+ 'a' => array('b', 'c')
+ ), $request->getPostFields()->getAll());
+ }
+
+ public function testAllowsEmptyFields()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFields(array(
+ 'a' => ''
+ ));
+ $this->assertEquals(array(
+ 'a' => ''
+ ), $request->getPostFields()->getAll());
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\RequestException
+ */
+ public function testFailsOnInvalidFiles()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFiles(array(
+ 'a' => new \stdClass()
+ ));
+ }
+
+ public function testHandlesEmptyStrings()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFields(array(
+ 'a' => '',
+ 'b' => null,
+ 'c' => 'Foo'
+ ));
+ $this->assertEquals(array(
+ 'a' => '',
+ 'b' => null,
+ 'c' => 'Foo'
+ ), $request->getPostFields()->getAll());
+ }
+
+ public function testHoldsPostFiles()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFile('foo', __FILE__);
+ $request->addPostFile(new PostFile('foo', __FILE__));
+
+ $this->assertArrayHasKey('foo', $request->getPostFiles());
+ $foo = $request->getPostFile('foo');
+ $this->assertEquals(2, count($foo));
+ $this->assertEquals(__FILE__, $foo[0]->getFilename());
+ $this->assertEquals(__FILE__, $foo[1]->getFilename());
+
+ $request->removePostFile('foo');
+ $this->assertEquals(array(), $request->getPostFiles());
+ }
+
+ public function testAllowsAtPrefixWhenAddingPostFiles()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->addPostFiles(array(
+ 'foo' => '@' . __FILE__
+ ));
+ $foo = $request->getPostFile('foo');
+ $this->assertEquals(__FILE__, $foo[0]->getFilename());
+ }
+
+ public function testSetStateToTransferWithEmptyBodySetsContentLengthToZero()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://test.com/');
+ $request->setState($request::STATE_TRANSFER);
+ $this->assertEquals('0', (string) $request->getHeader('Content-Length'));
+ }
+
+ public function testSettingExpectHeaderCutoffChangesRequest()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/');
+ $request->setHeader('Expect', '100-Continue');
+ $request->setExpectHeaderCutoff(false);
+ $this->assertNull($request->getHeader('Expect'));
+ // There is not body, so remove the expect header
+ $request->setHeader('Expect', '100-Continue');
+ $request->setExpectHeaderCutoff(10);
+ $this->assertNull($request->getHeader('Expect'));
+ // The size is less than the cutoff
+ $request->setBody('foo');
+ $this->assertNull($request->getHeader('Expect'));
+ // The size is greater than the cutoff
+ $request->setBody('foobazbarbamboo');
+ $this->assertNotNull($request->getHeader('Expect'));
+ }
+
+ public function testStrictRedirectsCanBeSpecifiedOnEntityEnclosingRequests()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/');
+ $request->configureRedirects(true);
+ $this->assertTrue($request->getParams()->get(RedirectPlugin::STRICT_REDIRECTS));
+ }
+
+ public function testCanDisableRedirects()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/');
+ $request->configureRedirects(false, false);
+ $this->assertTrue($request->getParams()->get(RedirectPlugin::DISABLE));
+ }
+
+ public function testSetsContentTypeWhenSettingBodyByGuessingFromEntityBody()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo');
+ $request->setBody(EntityBody::factory(fopen(__FILE__, 'r')));
+ $this->assertEquals('text/x-php', (string) $request->getHeader('Content-Type'));
+ }
+
+ public function testDoesNotCloneBody()
+ {
+ $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo');
+ $request->setBody('test');
+ $newRequest = clone $request;
+ $newRequest->setBody('foo');
+ $this->assertInternalType('string', (string) $request->getBody());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php
new file mode 100644
index 0000000..62ca555
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message\Header;
+
+use Guzzle\Http\Message\Header\HeaderFactory;
+
+/**
+ * @covers Guzzle\Http\Message\Header\HeaderFactory
+ */
+class HeaderFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCreatesBasicHeaders()
+ {
+ $f = new HeaderFactory();
+ $h = $f->createHeader('Foo', 'Bar');
+ $this->assertInstanceOf('Guzzle\Http\Message\Header', $h);
+ $this->assertEquals('Foo', $h->getName());
+ $this->assertEquals('Bar', (string) $h);
+ }
+
+ public function testCreatesSpecificHeaders()
+ {
+ $f = new HeaderFactory();
+ $h = $f->createHeader('Link', '<http>; rel="test"');
+ $this->assertInstanceOf('Guzzle\Http\Message\Header\Link', $h);
+ $this->assertEquals('Link', $h->getName());
+ $this->assertEquals('<http>; rel="test"', (string) $h);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php
new file mode 100644
index 0000000..c834d10
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message\Header;
+
+use Guzzle\Http\Message\Header\Link;
+use Guzzle\Tests\GuzzleTestCase;
+
+class LinkTest extends GuzzleTestCase
+{
+ public function testParsesLinks()
+ {
+ $link = new Link('Link', '<http:/.../front.jpeg>; rel=front; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg", <http://.../side.jpeg?test=1>; rel=side; type="image/jpeg"');
+ $links = $link->getLinks();
+ $this->assertEquals(array(
+ array(
+ 'rel' => 'front',
+ 'type' => 'image/jpeg',
+ 'url' => 'http:/.../front.jpeg',
+ ),
+ array(
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ 'url' => 'http://.../back.jpeg',
+ ),
+ array(
+ 'rel' => 'side',
+ 'type' => 'image/jpeg',
+ 'url' => 'http://.../side.jpeg?test=1'
+ )
+ ), $links);
+
+ $this->assertEquals(array(
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ 'url' => 'http://.../back.jpeg',
+ ), $link->getLink('back'));
+
+ $this->assertTrue($link->hasLink('front'));
+ $this->assertFalse($link->hasLink('foo'));
+ }
+
+ public function testCanAddLink()
+ {
+ $link = new Link('Link', '<http://foo>; rel=a; type="image/jpeg"');
+ $link->addLink('http://test.com', 'test', array('foo' => 'bar'));
+ $this->assertEquals(
+ '<http://foo>; rel=a; type="image/jpeg", <http://test.com>; rel="test"; foo="bar"',
+ (string) $link
+ );
+ }
+
+ public function testCanParseLinksWithCommas()
+ {
+ $link = new Link('Link', '<http://example.com/TheBook/chapter1>; rel="previous"; title="start, index"');
+ $this->assertEquals(array(
+ array(
+ 'rel' => 'previous',
+ 'title' => 'start, index',
+ 'url' => 'http://example.com/TheBook/chapter1',
+ )
+ ), $link->getLinks());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php
new file mode 100644
index 0000000..a3f511b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Http\Message\Header\HeaderCollection;
+
+/**
+ * Class used to compare HTTP headers using a custom DSL
+ */
+class HeaderComparison
+{
+ /**
+ * Compare HTTP headers and use special markup to filter values
+ * A header prefixed with '!' means it must not exist
+ * A header prefixed with '_' means it must be ignored
+ * A header value of '*' means anything after the * will be ignored
+ *
+ * @param array $filteredHeaders Array of special headers
+ * @param array $actualHeaders Array of headers to check against
+ *
+ * @return array|bool Returns an array of the differences or FALSE if none
+ */
+ public function compare($filteredHeaders, $actualHeaders)
+ {
+ $expected = array();
+ $ignore = array();
+ $absent = array();
+
+ if ($actualHeaders instanceof HeaderCollection) {
+ $actualHeaders = $actualHeaders->toArray();
+ }
+
+ foreach ($filteredHeaders as $k => $v) {
+ if ($k[0] == '_') {
+ // This header should be ignored
+ $ignore[] = str_replace('_', '', $k);
+ } elseif ($k[0] == '!') {
+ // This header must not be present
+ $absent[] = str_replace('!', '', $k);
+ } else {
+ $expected[$k] = $v;
+ }
+ }
+
+ return $this->compareArray($expected, $actualHeaders, $ignore, $absent);
+ }
+
+ /**
+ * Check if an array of HTTP headers matches another array of HTTP headers while taking * into account as a wildcard
+ *
+ * @param array $expected Expected HTTP headers (allows wildcard values)
+ * @param array|Collection $actual Actual HTTP header array
+ * @param array $ignore Headers to ignore from the comparison
+ * @param array $absent Array of headers that must not be present
+ *
+ * @return array|bool Returns an array of the differences or FALSE if none
+ */
+ public function compareArray(array $expected, $actual, array $ignore = array(), array $absent = array())
+ {
+ $differences = array();
+
+ // Add information about headers that were present but weren't supposed to be
+ foreach ($absent as $header) {
+ if ($this->hasKey($header, $actual)) {
+ $differences["++ {$header}"] = $actual[$header];
+ unset($actual[$header]);
+ }
+ }
+
+ // Check if expected headers are missing
+ foreach ($expected as $header => $value) {
+ if (!$this->hasKey($header, $actual)) {
+ $differences["- {$header}"] = $value;
+ }
+ }
+
+ // Flip the ignore array so it works with the case insensitive helper
+ $ignore = array_flip($ignore);
+ // Allow case-insensitive comparisons in wildcards
+ $expected = array_change_key_case($expected);
+
+ // Compare the expected and actual HTTP headers in no particular order
+ foreach ($actual as $key => $value) {
+
+ // If this is to be ignored, the skip it
+ if ($this->hasKey($key, $ignore)) {
+ continue;
+ }
+
+ // If the header was not expected
+ if (!$this->hasKey($key, $expected)) {
+ $differences["+ {$key}"] = $value;
+ continue;
+ }
+
+ // Check values and take wildcards into account
+ $lkey = strtolower($key);
+ $pos = is_string($expected[$lkey]) ? strpos($expected[$lkey], '*') : false;
+
+ foreach ((array) $actual[$key] as $v) {
+ if (($pos === false && $v != $expected[$lkey]) || $pos > 0 && substr($v, 0, $pos) != substr($expected[$lkey], 0, $pos)) {
+ $differences[$key] = "{$value} != {$expected[$lkey]}";
+ }
+ }
+ }
+
+ return empty($differences) ? false : $differences;
+ }
+
+ /**
+ * Case insensitive check if an array have a key
+ *
+ * @param string $key Key to check
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ protected function hasKey($key, $array)
+ {
+ if ($array instanceof Collection) {
+ $keys = $array->getKeys();
+ } else {
+ $keys = array_keys($array);
+ }
+
+ foreach ($keys as $k) {
+ if (!strcasecmp($k, $key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php
new file mode 100644
index 0000000..86c4fe8
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Guzzle\Tests\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Tests\Http\Message\HeaderComparison;
+
+class HeaderComparisonTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function filterProvider()
+ {
+ return array(
+
+ // Headers match
+ array(array(
+ 'Content-Length' => 'Foo'
+ ), array(
+ 'Content-Length' => 'Foo'
+ ), false),
+
+ // Missing header
+ array(array(
+ 'X-Foo' => 'Bar'
+ ), array(), array(
+ '- X-Foo' => 'Bar'
+ )),
+
+ // Extra headers is present
+ array(array(
+ 'X-Foo' => 'Bar'
+ ), array(
+ 'X-Foo' => 'Bar',
+ 'X-Baz' => 'Jar'
+ ), array(
+ '+ X-Baz' => 'Jar'
+ )),
+
+ // Header is present but must be absent
+ array(array(
+ '!X-Foo' => '*'
+ ), array(
+ 'X-Foo' => 'Bar'
+ ), array(
+ '++ X-Foo' => 'Bar'
+ )),
+
+ // Different values
+ array(array(
+ 'X-Foo' => 'Bar'
+ ), array(
+ 'X-Foo' => 'Baz'
+ ), array(
+ 'X-Foo' => 'Baz != Bar'
+ )),
+
+ // Wildcard search passes
+ array(array(
+ 'X-Foo' => '*'
+ ), array(
+ 'X-Foo' => 'Bar'
+ ), false),
+
+ // Wildcard search fails
+ array(array(
+ 'X-Foo' => '*'
+ ), array(), array(
+ '- X-Foo' => '*'
+ )),
+
+ // Ignore extra header if present
+ array(array(
+ 'X-Foo' => '*',
+ '_X-Bar' => '*',
+ ), array(
+ 'X-Foo' => 'Baz',
+ 'X-Bar' => 'Jar'
+ ), false),
+
+ // Ignore extra header if present and is not
+ array(array(
+ 'X-Foo' => '*',
+ '_X-Bar' => '*',
+ ), array(
+ 'X-Foo' => 'Baz'
+ ), false),
+
+ // Case insensitive
+ array(array(
+ 'X-Foo' => '*',
+ '_X-Bar' => '*',
+ ), array(
+ 'x-foo' => 'Baz',
+ 'x-BAR' => 'baz'
+ ), false),
+
+ // Case insensitive with collection
+ array(array(
+ 'X-Foo' => '*',
+ '_X-Bar' => '*',
+ ), new Collection(array(
+ 'x-foo' => 'Baz',
+ 'x-BAR' => 'baz'
+ )), false),
+ );
+ }
+
+ /**
+ * @dataProvider filterProvider
+ */
+ public function testComparesHeaders($filters, $headers, $result)
+ {
+ $compare = new HeaderComparison();
+ $this->assertEquals($result, $compare->compare($filters, $headers));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php
new file mode 100644
index 0000000..c750234
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Http\Message\Header;
+use Guzzle\Http\Message\Response;
+
+/**
+ * @covers Guzzle\Http\Message\Header
+ */
+class HeaderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $test = array(
+ 'zoo' => array('foo', 'Foo'),
+ 'Zoo' => 'bar',
+ );
+
+ public function testStoresHeaderName()
+ {
+ $i = new Header('Zoo', $this->test);
+ $this->assertEquals('Zoo', $i->getName());
+ }
+
+ public function testConvertsToString()
+ {
+ $i = new Header('Zoo', $this->test);
+ $this->assertEquals('foo, Foo, bar', (string) $i);
+ $i->setGlue(';');
+ $this->assertEquals('foo; Foo; bar', (string) $i);
+ }
+
+ public function testNormalizesGluedHeaders()
+ {
+ $h = new Header('Zoo', array('foo, Faz', 'bar'));
+ $result = $h->normalize(true)->toArray();
+ natsort($result);
+ $this->assertEquals(array('bar', 'foo', 'Faz'), $result);
+ }
+
+ public function testCanSearchForValues()
+ {
+ $h = new Header('Zoo', $this->test);
+ $this->assertTrue($h->hasValue('foo'));
+ $this->assertTrue($h->hasValue('Foo'));
+ $this->assertTrue($h->hasValue('bar'));
+ $this->assertFalse($h->hasValue('moo'));
+ $this->assertFalse($h->hasValue('FoO'));
+ }
+
+ public function testIsCountable()
+ {
+ $h = new Header('Zoo', $this->test);
+ $this->assertEquals(3, count($h));
+ }
+
+ public function testCanBeIterated()
+ {
+ $h = new Header('Zoo', $this->test);
+ $results = array();
+ foreach ($h as $key => $value) {
+ $results[$key] = $value;
+ }
+ $this->assertEquals(array(
+ 'foo', 'Foo', 'bar'
+ ), $results);
+ }
+
+ public function testAllowsFalseyValues()
+ {
+ // Allows 0
+ $h = new Header('Foo', 0, ';');
+ $this->assertEquals('0', (string) $h);
+ $this->assertEquals(1, count($h));
+ $this->assertEquals(';', $h->getGlue());
+
+ // Does not add a null header by default
+ $h = new Header('Foo');
+ $this->assertEquals('', (string) $h);
+ $this->assertEquals(0, count($h));
+
+ // Allows null array for a single null header
+ $h = new Header('Foo', array(null));
+ $this->assertEquals('', (string) $h);
+
+ // Allows empty string
+ $h = new Header('Foo', '');
+ $this->assertEquals('', (string) $h);
+ $this->assertEquals(1, count($h));
+ $this->assertEquals(1, count($h->normalize()->toArray()));
+ }
+
+ public function testCanRemoveValues()
+ {
+ $h = new Header('Foo', array('Foo', 'baz', 'bar'));
+ $h->removeValue('bar');
+ $this->assertTrue($h->hasValue('Foo'));
+ $this->assertFalse($h->hasValue('bar'));
+ $this->assertTrue($h->hasValue('baz'));
+ }
+
+ public function testAllowsArrayInConstructor()
+ {
+ $h = new Header('Foo', array('Testing', '123', 'Foo=baz'));
+ $this->assertEquals(array('Testing', '123', 'Foo=baz'), $h->toArray());
+ }
+
+ public function parseParamsProvider()
+ {
+ $res1 = array(
+ array(
+ '<http:/.../front.jpeg>' => '',
+ 'rel' => 'front',
+ 'type' => 'image/jpeg',
+ ),
+ array(
+ '<http://.../back.jpeg>' => '',
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ ),
+ );
+
+ return array(
+ array(
+ '<http:/.../front.jpeg>; rel="front"; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ '<http:/.../front.jpeg>; rel="front"; type="image/jpeg",<http://.../back.jpeg>; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
+ array(
+ array('foo' => 'baz', 'bar' => '123'),
+ array('boo' => ''),
+ array('test' => '123'),
+ array('foobar' => 'foo;bar')
+ )
+ ),
+ array(
+ '<http://.../side.jpeg?test=1>; rel="side"; type="image/jpeg",<http://.../side.jpeg?test=2>; rel=side; type="image/jpeg"',
+ array(
+ array('<http://.../side.jpeg?test=1>' => '', 'rel' => 'side', 'type' => 'image/jpeg'),
+ array('<http://.../side.jpeg?test=2>' => '', 'rel' => 'side', 'type' => 'image/jpeg')
+ )
+ ),
+ array(
+ '',
+ array()
+ )
+ );
+ }
+
+ /**
+ * @dataProvider parseParamsProvider
+ */
+ public function testParseParams($header, $result)
+ {
+ $response = new Response(200, array('Link' => $header));
+ $this->assertEquals($result, $response->getHeader('Link')->parseParams());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php
new file mode 100644
index 0000000..be048cb
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\PostFile;
+
+/**
+ * @covers Guzzle\Http\Message\PostFile
+ * @group server
+ */
+class PostFileTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testConstructorConfiguresPostFile()
+ {
+ $file = new PostFile('foo', __FILE__, 'x-foo', 'boo');
+ $this->assertEquals('foo', $file->getFieldName());
+ $this->assertEquals(__FILE__, $file->getFilename());
+ $this->assertEquals('boo', $file->getPostName());
+ $this->assertEquals('x-foo', $file->getContentType());
+ }
+
+ public function testRemovesLeadingAtSymbolFromPath()
+ {
+ $file = new PostFile('foo', '@' . __FILE__);
+ $this->assertEquals(__FILE__, $file->getFilename());
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresFileIsReadable()
+ {
+ $file = new PostFile('foo', '/foo/baz/bar');
+ }
+
+ public function testCanChangeContentType()
+ {
+ $file = new PostFile('foo', '@' . __FILE__);
+ $file->setContentType('Boo');
+ $this->assertEquals('Boo', $file->getContentType());
+ }
+
+ public function testCanChangeFieldName()
+ {
+ $file = new PostFile('foo', '@' . __FILE__);
+ $file->setFieldName('Boo');
+ $this->assertEquals('Boo', $file->getFieldName());
+ }
+
+ public function testReturnsCurlValueString()
+ {
+ $file = new PostFile('foo', __FILE__);
+ if (version_compare(phpversion(), '5.5.0', '<')) {
+ $this->assertContains('@' . __FILE__ . ';filename=PostFileTest.php;type=text/x-', $file->getCurlValue());
+ } else {
+ $c = $file->getCurlValue();
+ $this->assertEquals(__FILE__, $c->getFilename());
+ $this->assertEquals('PostFileTest.php', $c->getPostFilename());
+ $this->assertContains('text/x-', $c->getMimeType());
+ }
+ }
+
+ public function testReturnsCurlValueStringAndPostname()
+ {
+ $file = new PostFile('foo', __FILE__, null, 'NewPostFileTest.php');
+ if (version_compare(phpversion(), '5.5.0', '<')) {
+ $this->assertContains('@' . __FILE__ . ';filename=NewPostFileTest.php;type=text/x-', $file->getCurlValue());
+ } else {
+ $c = $file->getCurlValue();
+ $this->assertEquals(__FILE__, $c->getFilename());
+ $this->assertEquals('NewPostFileTest.php', $c->getPostFilename());
+ $this->assertContains('text/x-', $c->getMimeType());
+ }
+ }
+
+ public function testContentDispositionFilePathIsStripped()
+ {
+ $this->getServer()->flush();
+ $client = new Client($this->getServer()->getUrl());
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request = $client->post()->addPostFile('file', __FILE__);
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(false);
+ $this->assertContains('POST / HTTP/1.1', $requests[0]);
+ $this->assertContains('Content-Disposition: form-data; name="file"; filename="PostFileTest.php"', $requests[0]);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php
new file mode 100644
index 0000000..80b8d54
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php
@@ -0,0 +1,616 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Url;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\QueryString;
+use Guzzle\Parser\Message\MessageParser;
+use Guzzle\Plugin\Log\LogPlugin;
+use Guzzle\Plugin\Mock\MockPlugin;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Message\RequestFactory
+ */
+class HttpRequestFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCachesSingletonInstance()
+ {
+ $factory = RequestFactory::getInstance();
+ $this->assertSame($factory, RequestFactory::getInstance());
+ }
+
+ public function testCreatesNewGetRequests()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.google.com/');
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\MessageInterface', $request);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $request);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $request);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('http', $request->getScheme());
+ $this->assertEquals('http://www.google.com/', $request->getUrl());
+ $this->assertEquals('www.google.com', $request->getHost());
+ $this->assertEquals('/', $request->getPath());
+ $this->assertEquals('/', $request->getResource());
+
+ // Create a GET request with a custom receiving body
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $b = EntityBody::factory();
+ $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl(), null, $b);
+ $request->setClient(new Client());
+ $response = $request->send();
+ $this->assertSame($b, $response->getBody());
+ }
+
+ public function testCreatesPutRequests()
+ {
+ // Test using a string
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data');
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('http', $request->getScheme());
+ $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl());
+ $this->assertEquals('www.google.com', $request->getHost());
+ $this->assertEquals('/path', $request->getPath());
+ $this->assertEquals('/path?q=1&v=2', $request->getResource());
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody());
+ $this->assertEquals('Data', (string) $request->getBody());
+ unset($request);
+
+ // Test using an EntityBody
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, EntityBody::factory('Data'));
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('Data', (string) $request->getBody());
+
+ // Test using a resource
+ $resource = fopen('php://temp', 'w+');
+ fwrite($resource, 'Data');
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, $resource);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('Data', (string) $request->getBody());
+
+ // Test using an object that can be cast as a string
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, Url::factory('http://www.example.com/'));
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('http://www.example.com/', (string) $request->getBody());
+ }
+
+ public function testCreatesHeadAndDeleteRequests()
+ {
+ $request = RequestFactory::getInstance()->create('DELETE', 'http://www.test.com/');
+ $this->assertEquals('DELETE', $request->getMethod());
+ $request = RequestFactory::getInstance()->create('HEAD', 'http://www.test.com/');
+ $this->assertEquals('HEAD', $request->getMethod());
+ }
+
+ public function testCreatesOptionsRequests()
+ {
+ $request = RequestFactory::getInstance()->create('OPTIONS', 'http://www.example.com/');
+ $this->assertEquals('OPTIONS', $request->getMethod());
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ }
+
+ public function testCreatesNewPutRequestWithBody()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data');
+ $this->assertEquals('Data', (string) $request->getBody());
+ }
+
+ public function testCreatesNewPostRequestWithFields()
+ {
+ // Use an array
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, array(
+ 'a' => 'b'
+ ));
+ $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll());
+ unset($request);
+
+ // Use a collection
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new Collection(array(
+ 'a' => 'b'
+ )));
+ $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll());
+
+ // Use a QueryString
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new QueryString(array(
+ 'a' => 'b'
+ )));
+ $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll());
+
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/', null, array(
+ 'a' => 'b',
+ 'file' => '@' . __FILE__
+ ));
+
+ $this->assertEquals(array(
+ 'a' => 'b'
+ ), $request->getPostFields()->getAll());
+
+ $files = $request->getPostFiles();
+ $this->assertInstanceOf('Guzzle\Http\Message\PostFile', $files['file'][0]);
+ }
+
+ public function testCreatesFromParts()
+ {
+ $parts = parse_url('http://michael:123@www.google.com:8080/path?q=1&v=2');
+
+ $request = RequestFactory::getInstance()->fromParts('PUT', $parts, null, 'Data');
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('http', $request->getScheme());
+ $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl());
+ $this->assertEquals('www.google.com', $request->getHost());
+ $this->assertEquals('www.google.com:8080', $request->getHeader('Host'));
+ $this->assertEquals('/path', $request->getPath());
+ $this->assertEquals('/path?q=1&v=2', $request->getResource());
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody());
+ $this->assertEquals('Data', (string) $request->getBody());
+ $this->assertEquals('michael', $request->getUsername());
+ $this->assertEquals('123', $request->getPassword());
+ $this->assertEquals('8080', $request->getPort());
+ $this->assertEquals(array(
+ 'scheme' => 'http',
+ 'host' => 'www.google.com',
+ 'port' => 8080,
+ 'path' => '/path',
+ 'query' => 'q=1&v=2',
+ ), parse_url($request->getUrl()));
+ }
+
+ public function testCreatesFromMessage()
+ {
+ $auth = base64_encode('michael:123');
+ $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com:8080\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData";
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('http', $request->getScheme());
+ $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl());
+ $this->assertEquals('www.google.com', $request->getHost());
+ $this->assertEquals('www.google.com:8080', $request->getHeader('Host'));
+ $this->assertEquals('/path', $request->getPath());
+ $this->assertEquals('/path?q=1&v=2', $request->getResource());
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody());
+ $this->assertEquals('Data', (string) $request->getBody());
+ $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization'));
+ $this->assertEquals('8080', $request->getPort());
+
+ // Test passing a blank message returns false
+ $this->assertFalse($request = RequestFactory::getInstance()->fromMessage(''));
+
+ // Test passing a url with no port
+ $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData";
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('http', $request->getScheme());
+ $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl());
+ $this->assertEquals('www.google.com', $request->getHost());
+ $this->assertEquals('/path', $request->getPath());
+ $this->assertEquals('/path?q=1&v=2', $request->getResource());
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody());
+ $this->assertEquals('Data', (string) $request->getBody());
+ $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization'));
+ $this->assertEquals(80, $request->getPort());
+ }
+
+ public function testCreatesNewTraceRequest()
+ {
+ $request = RequestFactory::getInstance()->create('TRACE', 'http://www.google.com/');
+ $this->assertFalse($request instanceof \Guzzle\Http\Message\EntityEnclosingRequest);
+ $this->assertEquals('TRACE', $request->getMethod());
+ }
+
+ public function testCreatesProperTransferEncodingRequests()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/', array(
+ 'Transfer-Encoding' => 'chunked'
+ ), 'hello');
+ $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding'));
+ $this->assertFalse($request->hasHeader('Content-Length'));
+ }
+
+ public function testProperlyDealsWithDuplicateHeaders()
+ {
+ $parser = new MessageParser();
+
+ $message = "POST / http/1.1\r\n"
+ . "DATE:Mon, 09 Sep 2011 23:36:00 GMT\r\n"
+ . "host:host.foo.com\r\n"
+ . "ZOO:abc\r\n"
+ . "ZOO:123\r\n"
+ . "ZOO:HI\r\n"
+ . "zoo:456\r\n\r\n";
+
+ $parts = $parser->parseRequest($message);
+ $this->assertEquals(array (
+ 'DATE' => 'Mon, 09 Sep 2011 23:36:00 GMT',
+ 'host' => 'host.foo.com',
+ 'ZOO' => array('abc', '123', 'HI'),
+ 'zoo' => '456',
+ ), $parts['headers']);
+
+ $request = RequestFactory::getInstance()->fromMessage($message);
+
+ $this->assertEquals(array(
+ 'abc', '123', 'HI', '456'
+ ), $request->getHeader('zoo')->toArray());
+ }
+
+ public function testCreatesHttpMessagesWithBodiesAndNormalizesLineEndings()
+ {
+ $message = "POST / http/1.1\r\n"
+ . "Content-Type:application/x-www-form-urlencoded; charset=utf8\r\n"
+ . "Date:Mon, 09 Sep 2011 23:36:00 GMT\r\n"
+ . "Host:host.foo.com\r\n\r\n"
+ . "foo=bar";
+
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertEquals('application/x-www-form-urlencoded; charset=utf8', (string) $request->getHeader('Content-Type'));
+ $this->assertEquals('foo=bar', (string) $request->getBody());
+
+ $message = "POST / http/1.1\n"
+ . "Content-Type:application/x-www-form-urlencoded; charset=utf8\n"
+ . "Date:Mon, 09 Sep 2011 23:36:00 GMT\n"
+ . "Host:host.foo.com\n\n"
+ . "foo=bar";
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertEquals('foo=bar', (string) $request->getBody());
+
+ $message = "PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertTrue($request->hasHeader('Content-Length'));
+ $this->assertEquals(0, (string) $request->getHeader('Content-Length'));
+ }
+
+ public function testBugPathIncorrectlyHandled()
+ {
+ $message = "POST /foo\r\n\r\nBODY";
+ $request = RequestFactory::getInstance()->fromMessage($message);
+ $this->assertSame('POST', $request->getMethod());
+ $this->assertSame('/foo', $request->getPath());
+ $this->assertSame('BODY', (string) $request->getBody());
+ }
+
+ public function testHandlesChunkedTransferEncoding()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.foo.com/', array(
+ 'Transfer-Encoding' => 'chunked'
+ ), 'Test');
+ $this->assertFalse($request->hasHeader('Content-Length'));
+ $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding'));
+
+ $request = RequestFactory::getInstance()->create('POST', 'http://www.foo.com/', array(
+ 'transfer-encoding' => 'chunked'
+ ), array(
+ 'foo' => 'bar'
+ ));
+
+ $this->assertFalse($request->hasHeader('Content-Length'));
+ $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding'));
+ }
+
+ public function testClonesRequestsWithMethodWithoutClient()
+ {
+ $f = RequestFactory::getInstance();
+ $request = $f->create('GET', 'http://www.test.com', array('X-Foo' => 'Bar'));
+ $request->getParams()->replace(array('test' => '123'));
+ $request->getCurlOptions()->set('foo', 'bar');
+ $cloned = $f->cloneRequestWithMethod($request, 'PUT');
+ $this->assertEquals('PUT', $cloned->getMethod());
+ $this->assertEquals('Bar', (string) $cloned->getHeader('X-Foo'));
+ $this->assertEquals('http://www.test.com', $cloned->getUrl());
+ // Ensure params are cloned and cleaned up
+ $this->assertEquals(1, count($cloned->getParams()->getAll()));
+ $this->assertEquals('123', $cloned->getParams()->get('test'));
+ // Ensure curl options are cloned
+ $this->assertEquals('bar', $cloned->getCurlOptions()->get('foo'));
+ // Ensure event dispatcher is cloned
+ $this->assertNotSame($request->getEventDispatcher(), $cloned->getEventDispatcher());
+ }
+
+ public function testClonesRequestsWithMethodWithClient()
+ {
+ $f = RequestFactory::getInstance();
+ $client = new Client();
+ $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test');
+ $cloned = $f->cloneRequestWithMethod($request, 'GET');
+ $this->assertEquals('GET', $cloned->getMethod());
+ $this->assertNull($cloned->getHeader('Content-Length'));
+ $this->assertEquals('http://www.test.com', $cloned->getUrl());
+ $this->assertSame($request->getClient(), $cloned->getClient());
+ }
+
+ public function testClonesRequestsWithMethodWithClientWithEntityEnclosingChange()
+ {
+ $f = RequestFactory::getInstance();
+ $client = new Client();
+ $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test');
+ $cloned = $f->cloneRequestWithMethod($request, 'POST');
+ $this->assertEquals('POST', $cloned->getMethod());
+ $this->assertEquals('test', (string) $cloned->getBody());
+ }
+
+ public function testCanDisableRedirects()
+ {
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 307\r\nLocation: " . $this->getServer()->getUrl() . "\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $response = $client->get('/', array(), array('allow_redirects' => false))->send();
+ $this->assertEquals(307, $response->getStatusCode());
+ }
+
+ public function testCanAddCookies()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('/', array(), array('cookies' => array('Foo' => 'Bar')));
+ $this->assertEquals('Bar', $request->getCookie('Foo'));
+ }
+
+ public function testCanAddQueryString()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array(
+ 'query' => array('Foo' => 'Bar')
+ ));
+ $this->assertEquals('Bar', $request->getQuery()->get('Foo'));
+ }
+
+ public function testCanSetDefaultQueryString()
+ {
+ $request = new Request('GET', 'http://www.foo.com?test=abc');
+ RequestFactory::getInstance()->applyOptions($request, array(
+ 'query' => array('test' => '123', 'other' => 't123')
+ ), RequestFactory::OPTIONS_AS_DEFAULTS);
+ $this->assertEquals('abc', $request->getQuery()->get('test'));
+ $this->assertEquals('t123', $request->getQuery()->get('other'));
+ }
+
+ public function testCanAddBasicAuth()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array(
+ 'auth' => array('michael', 'test')
+ ));
+ $this->assertEquals('michael', $request->getUsername());
+ $this->assertEquals('test', $request->getPassword());
+ }
+
+ public function testCanAddDigestAuth()
+ {
+ $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array(
+ 'auth' => array('michael', 'test', 'digest')
+ ));
+ $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH));
+ $this->assertEquals('michael', $request->getUsername());
+ $this->assertEquals('test', $request->getPassword());
+ }
+
+ public function testCanAddEvents()
+ {
+ $foo = null;
+ $client = new Client();
+ $client->addSubscriber(new MockPlugin(array(new Response(200))));
+ $request = $client->get($this->getServer()->getUrl(), array(), array(
+ 'events' => array(
+ 'request.before_send' => function () use (&$foo) { $foo = true; }
+ )
+ ));
+ $request->send();
+ $this->assertTrue($foo);
+ }
+
+ public function testCanAddEventsWithPriority()
+ {
+ $foo = null;
+ $client = new Client();
+ $client->addSubscriber(new MockPlugin(array(new Response(200))));
+ $request = $client->get($this->getServer()->getUrl(), array(), array(
+ 'events' => array(
+ 'request.before_send' => array(function () use (&$foo) { $foo = true; }, 100)
+ )
+ ));
+ $request->send();
+ $this->assertTrue($foo);
+ }
+
+ public function testCanAddPlugins()
+ {
+ $mock = new MockPlugin(array(
+ new Response(200),
+ new Response(200)
+ ));
+ $client = new Client();
+ $client->addSubscriber($mock);
+ $request = $client->get('/', array(), array(
+ 'plugins' => array($mock)
+ ));
+ $request->send();
+ }
+
+ public function testCanDisableExceptions()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array(
+ 'plugins' => array(new MockPlugin(array(new Response(500)))),
+ 'exceptions' => false
+ ));
+ $this->assertEquals(500, $request->send()->getStatusCode());
+ }
+
+ public function testCanDisableExceptionsWithErrorListener()
+ {
+ $client = new Client();
+ $client->getEventDispatcher()->addListener('request.error', function () {});
+ $request = $client->get('/', array(), array(
+ 'plugins' => array(new MockPlugin(array(new Response(500)))),
+ 'exceptions' => false
+ ));
+ $this->assertEquals(500, $request->send()->getStatusCode());
+ }
+
+ public function testCanChangeSaveToLocation()
+ {
+ $r = EntityBody::factory();
+ $client = new Client();
+ $request = $client->get('/', array(), array(
+ 'plugins' => array(new MockPlugin(array(new Response(200, array(), 'testing')))),
+ 'save_to' => $r
+ ));
+ $request->send();
+ $this->assertEquals('testing', (string) $r);
+ }
+
+ public function testCanSetProxy()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('proxy' => '192.168.16.121'));
+ $this->assertEquals('192.168.16.121', $request->getCurlOptions()->get(CURLOPT_PROXY));
+ }
+
+ public function testCanSetHeadersOption()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('headers' => array('Foo' => 'Bar')));
+ $this->assertEquals('Bar', (string) $request->getHeader('Foo'));
+ }
+
+ public function testCanSetDefaultHeadersOptions()
+ {
+ $request = new Request('GET', 'http://www.foo.com', array('Foo' => 'Bar'));
+ RequestFactory::getInstance()->applyOptions($request, array(
+ 'headers' => array('Foo' => 'Baz', 'Bam' => 't123')
+ ), RequestFactory::OPTIONS_AS_DEFAULTS);
+ $this->assertEquals('Bar', (string) $request->getHeader('Foo'));
+ $this->assertEquals('t123', (string) $request->getHeader('Bam'));
+ }
+
+ public function testCanSetBodyOption()
+ {
+ $client = new Client();
+ $request = $client->put('/', array(), null, array('body' => 'test'));
+ $this->assertEquals('test', (string) $request->getBody());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesBodyOption()
+ {
+ $client = new Client();
+ $client->get('/', array(), array('body' => 'test'));
+ }
+
+ public function testCanSetTimeoutOption()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('timeout' => 1.5));
+ $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_TIMEOUT_MS));
+ }
+
+ public function testCanSetConnectTimeoutOption()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('connect_timeout' => 1.5));
+ $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_CONNECTTIMEOUT_MS));
+ }
+
+ public function testCanSetDebug()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('debug' => true));
+ $this->assertTrue($request->getCurlOptions()->get(CURLOPT_VERBOSE));
+ }
+
+ public function testCanSetVerifyToOff()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('verify' => false));
+ $this->assertNull($request->getCurlOptions()->get(CURLOPT_CAINFO));
+ $this->assertSame(0, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST));
+ $this->assertFalse($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER));
+ }
+
+ public function testCanSetVerifyToOn()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('verify' => true));
+ $this->assertNotNull($request->getCurlOptions()->get(CURLOPT_CAINFO));
+ $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST));
+ $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER));
+ }
+
+ public function testCanSetVerifyToPath()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('verify' => '/foo.pem'));
+ $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_CAINFO));
+ $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST));
+ $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER));
+ }
+
+ public function inputValidation()
+ {
+ return array_map(function ($option) { return array($option); }, array(
+ 'headers', 'query', 'cookies', 'auth', 'events', 'plugins', 'params'
+ ));
+ }
+
+ /**
+ * @dataProvider inputValidation
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesInput($option)
+ {
+ $client = new Client();
+ $client->get('/', array(), array($option => 'foo'));
+ }
+
+ public function testCanAddRequestParams()
+ {
+ $client = new Client();
+ $request = $client->put('/', array(), null, array('params' => array('foo' => 'test')));
+ $this->assertEquals('test', $request->getParams()->get('foo'));
+ }
+
+ public function testCanAddSslKey()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('ssl_key' => '/foo.pem'));
+ $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY));
+ }
+
+ public function testCanAddSslKeyPassword()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('ssl_key' => array('/foo.pem', 'bar')));
+ $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY));
+ $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLKEYPASSWD));
+ }
+
+ public function testCanAddSslCert()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('cert' => '/foo.pem'));
+ $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT));
+ }
+
+ public function testCanAddSslCertPassword()
+ {
+ $client = new Client();
+ $request = $client->get('/', array(), array('cert' => array('/foo.pem', 'bar')));
+ $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT));
+ $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLCERTPASSWD));
+ }
+
+ public function testCreatesBodyWithoutZeroString()
+ {
+ $request = RequestFactory::getInstance()->create('PUT', 'http://test.com', array(), '0');
+ $this->assertSame('0', (string) $request->getBody());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php
new file mode 100644
index 0000000..5bf6248
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php
@@ -0,0 +1,639 @@
+<?php
+
+namespace Guzzle\Tests\Http\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Url;
+use Guzzle\Http\Client;
+use Guzzle\Plugin\Async\AsyncPlugin;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Http\Exception\BadResponseException;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Message\Request
+ * @covers Guzzle\Http\Message\AbstractMessage
+ */
+class RequestTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Request */
+ protected $request;
+
+ /** @var Client */
+ protected $client;
+
+ protected function setUp()
+ {
+ $this->client = new Client($this->getServer()->getUrl());
+ $this->request = $this->client->get();
+ }
+
+ public function tearDown()
+ {
+ unset($this->request);
+ unset($this->client);
+ }
+
+ public function testConstructorBuildsRequestWithArrayHeaders()
+ {
+ // Test passing an array of headers
+ $request = new Request('GET', 'http://www.guzzle-project.com/', array(
+ 'foo' => 'bar'
+ ));
+
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('http://www.guzzle-project.com/', $request->getUrl());
+ $this->assertEquals('bar', $request->getHeader('foo'));
+ }
+
+ public function testDescribesEvents()
+ {
+ $this->assertInternalType('array', Request::getAllEvents());
+ }
+
+ public function testConstructorBuildsRequestWithCollectionHeaders()
+ {
+ $request = new Request('GET', 'http://www.guzzle-project.com/', new Collection(array(
+ 'foo' => 'bar'
+ )));
+ $this->assertEquals('bar', $request->getHeader('foo'));
+ }
+
+ public function testConstructorBuildsRequestWithNoHeaders()
+ {
+ $request = new Request('GET', 'http://www.guzzle-project.com/', null);
+ $this->assertFalse($request->hasHeader('foo'));
+ }
+
+ public function testConstructorHandlesNonBasicAuth()
+ {
+ $request = new Request('GET', 'http://www.guzzle-project.com/', array(
+ 'Authorization' => 'Foo bar'
+ ));
+ $this->assertNull($request->getUserName());
+ $this->assertNull($request->getPassword());
+ $this->assertEquals('Foo bar', (string) $request->getHeader('Authorization'));
+ }
+
+ public function testRequestsCanBeConvertedToRawMessageStrings()
+ {
+ $auth = base64_encode('michael:123');
+ $message = "PUT /path?q=1&v=2 HTTP/1.1\r\n"
+ . "Host: www.google.com\r\n"
+ . "Authorization: Basic {$auth}\r\n"
+ . "Content-Length: 4\r\n\r\nData";
+
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', array(
+ 'Authorization' => 'Basic ' . $auth
+ ), 'Data');
+
+ $this->assertEquals($message, $request->__toString());
+ }
+
+ /**
+ * Add authorization after the fact and see that it was put in the message
+ */
+ public function testRequestStringsIncludeAuth()
+ {
+ $auth = base64_encode('michael:123');
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl(), null, 'Data')
+ ->setClient($this->client)
+ ->setAuth('michael', '123', CURLAUTH_BASIC);
+ $request->send();
+
+ $this->assertContains('Authorization: Basic ' . $auth, (string) $request);
+ }
+
+ public function testGetEventDispatcher()
+ {
+ $d = $this->request->getEventDispatcher();
+ $this->assertInstanceOf('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface', $d);
+ $this->assertEquals($d, $this->request->getEventDispatcher());
+ }
+
+ public function testRequestsManageClients()
+ {
+ $request = new Request('GET', 'http://test.com');
+ $this->assertNull($request->getClient());
+ $request->setClient($this->client);
+ $this->assertSame($this->client, $request->getClient());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage A client must be set on the request
+ */
+ public function testRequestsRequireClients()
+ {
+ $request = new Request('GET', 'http://test.com');
+ $request->send();
+ }
+
+ public function testSend()
+ {
+ $response = new Response(200, array(
+ 'Content-Length' => 3
+ ), 'abc');
+ $this->request->setResponse($response, true);
+ $r = $this->request->send();
+
+ $this->assertSame($response, $r);
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $this->request->getResponse());
+ $this->assertSame($r, $this->request->getResponse());
+ $this->assertEquals('complete', $this->request->getState());
+ }
+
+ public function testGetResponse()
+ {
+ $this->assertNull($this->request->getResponse());
+ $response = new Response(200, array('Content-Length' => 3), 'abc');
+
+ $this->request->setResponse($response);
+ $this->assertEquals($response, $this->request->getResponse());
+
+ $client = new Client('http://www.google.com');
+ $request = $client->get('http://www.google.com/');
+ $request->setResponse($response, true);
+ $request->send();
+ $requestResponse = $request->getResponse();
+ $this->assertSame($response, $requestResponse);
+
+ // Try again, making sure it's still the same response
+ $this->assertSame($requestResponse, $request->getResponse());
+
+ $response = new Response(204);
+ $request = $client->get();
+ $request->setResponse($response, true);
+ $request->send();
+ $requestResponse = $request->getResponse();
+ $this->assertSame($response, $requestResponse);
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody());
+ }
+
+ public function testRequestThrowsExceptionOnBadResponse()
+ {
+ try {
+ $this->request->setResponse(new Response(404, array('Content-Length' => 3), 'abc'), true);
+ $this->request->send();
+ $this->fail('Expected exception not thrown');
+ } catch (BadResponseException $e) {
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $e->getRequest());
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $e->getResponse());
+ $this->assertContains('Client error response', $e->getMessage());
+ }
+ }
+
+ public function testManagesQuery()
+ {
+ $this->assertInstanceOf('Guzzle\\Http\\QueryString', $this->request->getQuery());
+ $this->request->getQuery()->set('test', '123');
+ $this->assertEquals('test=123', $this->request->getQuery(true));
+ }
+
+ public function testRequestHasMethod()
+ {
+ $this->assertEquals('GET', $this->request->getMethod());
+ }
+
+ public function testRequestHasScheme()
+ {
+ $this->assertEquals('http', $this->request->getScheme());
+ $this->assertEquals($this->request, $this->request->setScheme('https'));
+ $this->assertEquals('https', $this->request->getScheme());
+ }
+
+ public function testRequestHasHost()
+ {
+ $this->assertEquals('127.0.0.1', $this->request->getHost());
+ $this->assertEquals('127.0.0.1:8124', (string) $this->request->getHeader('Host'));
+
+ $this->assertSame($this->request, $this->request->setHost('www2.google.com'));
+ $this->assertEquals('www2.google.com', $this->request->getHost());
+ $this->assertEquals('www2.google.com:8124', (string) $this->request->getHeader('Host'));
+
+ $this->assertSame($this->request, $this->request->setHost('www.test.com:8081'));
+ $this->assertEquals('www.test.com', $this->request->getHost());
+ $this->assertEquals(8081, $this->request->getPort());
+ }
+
+ public function testRequestHasProtocol()
+ {
+ $this->assertEquals('1.1', $this->request->getProtocolVersion());
+ $this->assertEquals($this->request, $this->request->setProtocolVersion('1.1'));
+ $this->assertEquals('1.1', $this->request->getProtocolVersion());
+ $this->assertEquals($this->request, $this->request->setProtocolVersion('1.0'));
+ $this->assertEquals('1.0', $this->request->getProtocolVersion());
+ }
+
+ public function testRequestHasPath()
+ {
+ $this->assertEquals('/', $this->request->getPath());
+ $this->assertEquals($this->request, $this->request->setPath('/index.html'));
+ $this->assertEquals('/index.html', $this->request->getPath());
+ $this->assertEquals($this->request, $this->request->setPath('index.html'));
+ $this->assertEquals('/index.html', $this->request->getPath());
+ }
+
+ public function testPermitsFalsyComponents()
+ {
+ $request = new Request('GET', 'http://0/0?0');
+ $this->assertSame('0', $request->getHost());
+ $this->assertSame('/0', $request->getPath());
+ $this->assertSame('0', $request->getQuery(true));
+
+ $request = new Request('GET', '0');
+ $this->assertEquals('/0', $request->getPath());
+ }
+
+ public function testRequestHasPort()
+ {
+ $this->assertEquals(8124, $this->request->getPort());
+ $this->assertEquals('127.0.0.1:8124', $this->request->getHeader('Host'));
+
+ $this->assertEquals($this->request, $this->request->setPort('8080'));
+ $this->assertEquals('8080', $this->request->getPort());
+ $this->assertEquals('127.0.0.1:8080', $this->request->getHeader('Host'));
+
+ $this->request->setPort(80);
+ $this->assertEquals('127.0.0.1', $this->request->getHeader('Host'));
+ }
+
+ public function testRequestHandlesAuthorization()
+ {
+ // Uninitialized auth
+ $this->assertEquals(null, $this->request->getUsername());
+ $this->assertEquals(null, $this->request->getPassword());
+
+ // Set an auth
+ $this->assertSame($this->request, $this->request->setAuth('michael', '123'));
+ $this->assertEquals('michael', $this->request->getUsername());
+ $this->assertEquals('123', $this->request->getPassword());
+
+ // Set an auth with blank password
+ $this->assertSame($this->request, $this->request->setAuth('michael', ''));
+ $this->assertEquals('michael', $this->request->getUsername());
+ $this->assertEquals('', $this->request->getPassword());
+
+ // Remove the auth
+ $this->request->setAuth(false);
+ $this->assertEquals(null, $this->request->getUsername());
+ $this->assertEquals(null, $this->request->getPassword());
+
+ // Make sure that the cURL based auth works too
+ $request = new Request('GET', $this->getServer()->getUrl());
+ $request->setAuth('michael', 'password', CURLAUTH_DIGEST);
+ $this->assertEquals('michael:password', $request->getCurlOptions()->get(CURLOPT_USERPWD));
+ $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH));
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesAuth()
+ {
+ $this->request->setAuth('foo', 'bar', 'bam');
+ }
+
+ public function testGetResourceUri()
+ {
+ $this->assertEquals('/', $this->request->getResource());
+ $this->request->setPath('/index.html');
+ $this->assertEquals('/index.html', $this->request->getResource());
+ $this->request->getQuery()->add('v', '1');
+ $this->assertEquals('/index.html?v=1', $this->request->getResource());
+ }
+
+ public function testRequestHasMutableUrl()
+ {
+ $url = 'http://www.test.com:8081/path?q=123#fragment';
+ $u = Url::factory($url);
+ $this->assertSame($this->request, $this->request->setUrl($url));
+ $this->assertEquals($url, $this->request->getUrl());
+
+ $this->assertSame($this->request, $this->request->setUrl($u));
+ $this->assertEquals($url, $this->request->getUrl());
+ }
+
+ public function testRequestHasState()
+ {
+ $this->assertEquals(RequestInterface::STATE_NEW, $this->request->getState());
+ $this->request->setState(RequestInterface::STATE_TRANSFER);
+ $this->assertEquals(RequestInterface::STATE_TRANSFER, $this->request->getState());
+ }
+
+ public function testSetManualResponse()
+ {
+ $response = new Response(200, array(
+ 'Date' => 'Sat, 16 Oct 2010 17:27:14 GMT',
+ 'Expires' => '-1',
+ 'Cache-Control' => 'private, max-age=0',
+ 'Content-Type' => 'text/html; charset=ISO-8859-1',
+ ), 'response body');
+
+ $this->assertSame($this->request, $this->request->setResponse($response), '-> setResponse() must use a fluent interface');
+ $this->assertEquals('complete', $this->request->getState(), '-> setResponse() must change the state of the request to complete');
+ $this->assertSame($response, $this->request->getResponse(), '-> setResponse() must set the exact same response that was passed in to it');
+ }
+
+ public function testRequestCanHaveManuallySetResponseBody()
+ {
+ $file = __DIR__ . '/../../TestData/temp.out';
+ if (file_exists($file)) {
+ unlink($file);
+ }
+
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata");
+ $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl());
+ $request->setClient($this->client);
+ $entityBody = EntityBody::factory(fopen($file, 'w+'));
+ $request->setResponseBody($entityBody);
+ $response = $request->send();
+ $this->assertSame($entityBody, $response->getBody());
+
+ $this->assertTrue(file_exists($file));
+ $this->assertEquals('data', file_get_contents($file));
+ unlink($file);
+
+ $this->assertEquals('data', $response->getBody(true));
+ }
+
+ public function testHoldsCookies()
+ {
+ $this->assertNull($this->request->getCookie('test'));
+
+ // Set a cookie
+ $this->assertSame($this->request, $this->request->addCookie('test', 'abc'));
+ $this->assertEquals('abc', $this->request->getCookie('test'));
+
+ // Multiple cookies by setting the Cookie header
+ $this->request->setHeader('Cookie', '__utma=1.638370270.1344367610.1374365610.1944450276.2; __utmz=1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); hl=de; PHPSESSID=ak93pqashi5uubuoq8fjv60897');
+ $this->assertEquals('1.638370270.1344367610.1374365610.1944450276.2', $this->request->getCookie('__utma'));
+ $this->assertEquals('1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)', $this->request->getCookie('__utmz'));
+ $this->assertEquals('de', $this->request->getCookie('hl'));
+ $this->assertEquals('ak93pqashi5uubuoq8fjv60897', $this->request->getCookie('PHPSESSID'));
+
+ // Unset the cookies by setting the Cookie header to null
+ $this->request->setHeader('Cookie', null);
+ $this->assertNull($this->request->getCookie('test'));
+ $this->request->removeHeader('Cookie');
+
+ // Set and remove a cookie
+ $this->assertSame($this->request, $this->request->addCookie('test', 'abc'));
+ $this->assertEquals('abc', $this->request->getCookie('test'));
+ $this->assertSame($this->request, $this->request->removeCookie('test'));
+ $this->assertNull($this->request->getCookie('test'));
+
+ // Remove the cookie header
+ $this->assertSame($this->request, $this->request->addCookie('test', 'abc'));
+ $this->request->removeHeader('Cookie');
+ $this->assertEquals('', (string) $this->request->getHeader('Cookie'));
+
+ // Remove a cookie value
+ $this->request->addCookie('foo', 'bar')->addCookie('baz', 'boo');
+ $this->request->removeCookie('foo');
+ $this->assertEquals(array(
+ 'baz' => 'boo'
+ ), $this->request->getCookies());
+
+ $this->request->addCookie('foo', 'bar');
+ $this->assertEquals('baz=boo; foo=bar', (string) $this->request->getHeader('Cookie'));
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\RequestException
+ * @expectedExceptionMessage Error completing request
+ */
+ public function testRequestThrowsExceptionWhenSetToCompleteWithNoResponse()
+ {
+ $this->request->setState(RequestInterface::STATE_COMPLETE);
+ }
+
+ public function testClonedRequestsUseNewInternalState()
+ {
+ $p = new AsyncPlugin();
+ $this->request->getEventDispatcher()->addSubscriber($p);
+ $h = $this->request->getHeader('Host');
+
+ $r = clone $this->request;
+ $this->assertEquals(RequestInterface::STATE_NEW, $r->getState());
+ $this->assertNotSame($r->getQuery(), $this->request->getQuery());
+ $this->assertNotSame($r->getCurlOptions(), $this->request->getCurlOptions());
+ $this->assertNotSame($r->getEventDispatcher(), $this->request->getEventDispatcher());
+ $this->assertEquals($r->getHeaders(), $this->request->getHeaders());
+ $this->assertNotSame($h, $r->getHeader('Host'));
+ $this->assertNotSame($r->getParams(), $this->request->getParams());
+ $this->assertTrue($this->request->getEventDispatcher()->hasListeners('request.sent'));
+ }
+
+ public function testRecognizesBasicAuthCredentialsInUrls()
+ {
+ $this->request->setUrl('http://michael:test@test.com/');
+ $this->assertEquals('michael', $this->request->getUsername());
+ $this->assertEquals('test', $this->request->getPassword());
+ }
+
+ public function testRequestCanBeSentUsingCurl()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata",
+ "HTTP/1.1 404 Not Found\r\nContent-Encoding: application/xml\r\nContent-Length: 48\r\n\r\n<error><mesage>File not found</message></error>"
+ ));
+
+ $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl());
+ $request->setClient($this->client);
+ $response = $request->send();
+
+ $this->assertEquals('data', $response->getBody(true));
+ $this->assertEquals(200, (int) $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals(4, $response->getContentLength());
+ $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $response->getExpires());
+
+ // Test that the same handle can be sent twice without setting state to new
+ $response2 = $request->send();
+ $this->assertNotSame($response, $response2);
+
+ try {
+ $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl() . 'index.html');
+ $request->setClient($this->client);
+ $response = $request->send();
+ $this->fail('Request did not receive a 404 response');
+ } catch (BadResponseException $e) {
+ }
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $messages = $this->getServer()->getReceivedRequests(false);
+ $port = $this->getServer()->getPort();
+
+ $userAgent = $this->client->getDefaultUserAgent();
+
+ $this->assertEquals('127.0.0.1:' . $port, $requests[0]->getHeader('Host'));
+ $this->assertEquals('127.0.0.1:' . $port, $requests[1]->getHeader('Host'));
+ $this->assertEquals('127.0.0.1:' . $port, $requests[2]->getHeader('Host'));
+
+ $this->assertEquals('/', $requests[0]->getPath());
+ $this->assertEquals('/', $requests[1]->getPath());
+ $this->assertEquals('/index.html', $requests[2]->getPath());
+
+ $parts = explode("\r\n", $messages[0]);
+ $this->assertEquals('GET / HTTP/1.1', $parts[0]);
+
+ $parts = explode("\r\n", $messages[1]);
+ $this->assertEquals('GET / HTTP/1.1', $parts[0]);
+
+ $parts = explode("\r\n", $messages[2]);
+ $this->assertEquals('GET /index.html HTTP/1.1', $parts[0]);
+ }
+
+ public function testThrowsExceptionsWhenUnsuccessfulResponseIsReceivedByDefault()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 404 Not found\r\nContent-Length: 0\r\n\r\n");
+
+ try {
+ $request = $this->client->get('/index.html');
+ $response = $request->send();
+ $this->fail('Request did not receive a 404 response');
+ } catch (BadResponseException $e) {
+ $this->assertContains('Client error response', $e->getMessage());
+ $this->assertContains('[status code] 404', $e->getMessage());
+ $this->assertContains('[reason phrase] Not found', $e->getMessage());
+ }
+ }
+
+ public function testCanShortCircuitErrorHandling()
+ {
+ $request = $this->request;
+ $response = new Response(404);
+ $request->setResponse($response, true);
+ $out = '';
+ $that = $this;
+ $request->getEventDispatcher()->addListener('request.error', function($event) use (&$out, $that) {
+ $out .= $event['request'] . "\n" . $event['response'] . "\n";
+ $event->stopPropagation();
+ });
+ $request->send();
+ $this->assertContains((string) $request, $out);
+ $this->assertContains((string) $request->getResponse(), $out);
+ $this->assertSame($response, $request->getResponse());
+ }
+
+ public function testCanOverrideUnsuccessfulResponses()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 404 NOT FOUND\r\n" .
+ "Content-Length: 0\r\n" .
+ "\r\n",
+ "HTTP/1.1 200 OK\r\n" .
+ "Content-Length: 0\r\n" .
+ "\r\n"
+ ));
+
+ $newResponse = null;
+
+ $request = $this->request;
+ $request->getEventDispatcher()->addListener('request.error', function($event) use (&$newResponse) {
+ if ($event['response']->getStatusCode() == 404) {
+ $newRequest = clone $event['request'];
+ $newResponse = $newRequest->send();
+ // Override the original response and bypass additional response processing
+ $event['response'] = $newResponse;
+ // Call $event['request']->setResponse($newResponse); to re-apply events
+ $event->stopPropagation();
+ }
+ });
+
+ $request->send();
+
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ $this->assertSame($newResponse, $request->getResponse());
+ $this->assertEquals(2, count($this->getServer()->getReceivedRequests()));
+ }
+
+ public function testCanRetrieveUrlObject()
+ {
+ $request = new Request('GET', 'http://www.example.com/foo?abc=d');
+ $this->assertInstanceOf('Guzzle\Http\Url', $request->getUrl(true));
+ $this->assertEquals('http://www.example.com/foo?abc=d', $request->getUrl());
+ $this->assertEquals('http://www.example.com/foo?abc=d', (string) $request->getUrl(true));
+ }
+
+ public function testUnresolvedRedirectsReturnResponse()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 303 SEE OTHER\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $request = $this->request;
+ $this->assertEquals(303, $request->send()->getStatusCode());
+ $request->getParams()->set(RedirectPlugin::DISABLE, true);
+ $this->assertEquals(301, $request->send()->getStatusCode());
+ }
+
+ public function testCanSendCustomRequests()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $request = $this->client->createRequest('PROPFIND', $this->getServer()->getUrl(), array(
+ 'Content-Type' => 'text/plain'
+ ), 'foo');
+ $response = $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('PROPFIND', $requests[0]->getMethod());
+ $this->assertEquals(3, (string) $requests[0]->getHeader('Content-Length'));
+ $this->assertEquals('foo', (string) $requests[0]->getBody());
+ }
+
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Warning
+ */
+ public function testEnsuresFileCanBeCreated()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest");
+ $this->client->get('/')->setResponseBody('/wefwefefefefwewefwe/wefwefwefefwe/wefwefewfw.txt')->send();
+ }
+
+ public function testAllowsFilenameForDownloadingContent()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest");
+ $name = sys_get_temp_dir() . '/foo.txt';
+ $this->client->get('/')->setResponseBody($name)->send();
+ $this->assertEquals('test', file_get_contents($name));
+ unlink($name);
+ }
+
+ public function testUsesCustomResponseBodyWhenItIsCustom()
+ {
+ $en = EntityBody::factory();
+ $request = $this->client->get();
+ $request->setResponseBody($en);
+ $request->setResponse(new Response(200, array(), 'foo'));
+ $this->assertEquals('foo', (string) $en);
+ }
+
+ public function testCanChangePortThroughScheme()
+ {
+ $request = new Request('GET', 'http://foo.com');
+ $request->setScheme('https');
+ $this->assertEquals('https://foo.com', (string) $request->getUrl());
+ $this->assertEquals('foo.com', $request->getHost());
+ $request->setScheme('http');
+ $this->assertEquals('http://foo.com', (string) $request->getUrl());
+ $this->assertEquals('foo.com', $request->getHost());
+ $request->setPort(null);
+ $this->assertEquals('http://foo.com', (string) $request->getUrl());
+ $this->assertEquals('foo.com', $request->getHost());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php
new file mode 100644
index 0000000..08b4df8
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php
@@ -0,0 +1,677 @@
+<?php
+
+namespace Guzzle\Tests\Message;
+
+use Guzzle\Common\Collection;
+use Guzzle\Http\ClientInterface;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\HttpException;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\Message\Response;
+
+/**
+ * @group server
+ * @covers Guzzle\Http\Message\Response
+ */
+class ResponseTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Response The response object to test */
+ protected $response;
+
+ public function setup()
+ {
+ $this->response = new Response(200, new Collection(array(
+ 'Accept-Ranges' => 'bytes',
+ 'Age' => '12',
+ 'Allow' => 'GET, HEAD',
+ 'Cache-Control' => 'no-cache',
+ 'Content-Encoding' => 'gzip',
+ 'Content-Language' => 'da',
+ 'Content-Length' => '348',
+ 'Content-Location' => '/index.htm',
+ 'Content-Disposition' => 'attachment; filename=fname.ext',
+ 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==',
+ 'Content-Range' => 'bytes 21010-47021/47022',
+ 'Content-Type' => 'text/html; charset=utf-8',
+ 'Date' => 'Tue, 15 Nov 1994 08:12:31 GMT',
+ 'ETag' => '737060cd8c284d8af7ad3082f209582d',
+ 'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT',
+ 'Last-Modified' => 'Tue, 15 Nov 1994 12:45:26 GMT',
+ 'Location' => 'http://www.w3.org/pub/WWW/People.html',
+ 'Pragma' => 'no-cache',
+ 'Proxy-Authenticate' => 'Basic',
+ 'Retry-After' => '120',
+ 'Server' => 'Apache/1.3.27 (Unix) (Red-Hat/Linux)',
+ 'Set-Cookie' => 'UserID=JohnDoe; Max-Age=3600; Version=1',
+ 'Trailer' => 'Max-Forwards',
+ 'Transfer-Encoding' => 'chunked',
+ 'Vary' => '*',
+ 'Via' => '1.0 fred, 1.1 nowhere.com (Apache/1.1)',
+ 'Warning' => '199 Miscellaneous warning',
+ 'WWW-Authenticate' => 'Basic'
+ )), 'body');
+ }
+
+ public function tearDown()
+ {
+ unset($this->response);
+ }
+
+ public function testConstructor()
+ {
+ $params = new Collection();
+ $body = EntityBody::factory('');
+ $response = new Response(200, $params, $body);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals($body, $response->getBody());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders());
+
+ // Make sure Content-Length is set automatically
+ $response = new Response(200, $params);
+ $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders());
+
+ // Pass bodies to the response
+ $response = new Response(200, null, 'data');
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody());
+ $response = new Response(200, null, EntityBody::factory('data'));
+ $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody());
+ $this->assertEquals('data', $response->getBody(true));
+ $response = new Response(200, null, '0');
+ $this->assertSame('0', $response->getBody(true), 'getBody(true) should return "0" if response body is "0".');
+
+ // Make sure the proper exception is thrown
+ try {
+ //$response = new Response(200, null, array('foo' => 'bar'));
+ //$this->fail('Response did not throw exception when passing invalid body');
+ } catch (HttpException $e) {
+ }
+
+ // Ensure custom codes can be set
+ $response = new Response(2);
+ $this->assertEquals(2, $response->getStatusCode());
+ $this->assertEquals('', $response->getReasonPhrase());
+
+ // Make sure the proper exception is thrown when sending invalid headers
+ try {
+ $response = new Response(200, 'adidas');
+ $this->fail('Response did not throw exception when passing invalid $headers');
+ } catch (BadResponseException $e) {
+ }
+ }
+
+ public function test__toString()
+ {
+ $response = new Response(200);
+ $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response);
+
+ // Add another header
+ $response = new Response(200, array(
+ 'X-Test' => 'Guzzle'
+ ));
+ $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response);
+
+ $response = new Response(200, array(
+ 'Content-Length' => 4
+ ), 'test');
+ $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response);
+ }
+
+ public function testFactory()
+ {
+ $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest");
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals(4, (string) $response->getContentLength());
+ $this->assertEquals('test', $response->getBody(true));
+
+ // Make sure that automatic Content-Length works
+ $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest");
+ $this->assertEquals(4, (string) $response->getContentLength());
+ $this->assertEquals('test', $response->getBody(true));
+ }
+
+ public function testFactoryCanCreateHeadResponses()
+ {
+ $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n");
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals(4, (string) $response->getContentLength());
+ $this->assertEquals('', $response->getBody(true));
+ }
+
+ public function testFactoryRequiresMessage()
+ {
+ $this->assertFalse(Response::fromMessage(''));
+ }
+
+ public function testGetBody()
+ {
+ $body = EntityBody::factory('');
+ $response = new Response(403, new Collection(), $body);
+ $this->assertEquals($body, $response->getBody());
+ $response->setBody('foo');
+ $this->assertEquals('foo', $response->getBody(true));
+ }
+
+ public function testManagesStatusCode()
+ {
+ $response = new Response(403);
+ $this->assertEquals(403, $response->getStatusCode());
+ }
+
+ public function testGetMessage()
+ {
+ $response = new Response(200, new Collection(array(
+ 'Content-Length' => 4
+ )), 'body');
+
+ $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nbody", $response->getMessage());
+ }
+
+ public function testGetRawHeaders()
+ {
+ $response = new Response(200, new Collection(array(
+ 'Keep-Alive' => 155,
+ 'User-Agent' => 'Guzzle',
+ 'Content-Length' => 4
+ )), 'body');
+
+ $this->assertEquals("HTTP/1.1 200 OK\r\nKeep-Alive: 155\r\nUser-Agent: Guzzle\r\nContent-Length: 4\r\n\r\n", $response->getRawHeaders());
+ }
+
+ public function testHandlesStatusAndStatusCodes()
+ {
+ $response = new Response(200, new Collection(), 'body');
+ $this->assertEquals('OK', $response->getReasonPhrase());
+
+ $this->assertSame($response, $response->setStatus(204));
+ $this->assertEquals('No Content', $response->getReasonPhrase());
+ $this->assertEquals(204, $response->getStatusCode());
+
+ $this->assertSame($response, $response->setStatus(204, 'Testing!'));
+ $this->assertEquals('Testing!', $response->getReasonPhrase());
+ $this->assertEquals(204, $response->getStatusCode());
+
+ $response->setStatus(2000);
+ $this->assertEquals(2000, $response->getStatusCode());
+ $this->assertEquals('', $response->getReasonPhrase());
+
+ $response->setStatus(200, 'Foo');
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('Foo', $response->getReasonPhrase());
+ }
+
+ public function testIsClientError()
+ {
+ $response = new Response(403);
+ $this->assertTrue($response->isClientError());
+ $response = new Response(200);
+ $this->assertFalse($response->isClientError());
+ }
+
+ public function testIsError()
+ {
+ $response = new Response(403);
+ $this->assertTrue($response->isError());
+ $response = new Response(200);
+ $this->assertFalse($response->isError());
+ $response = new Response(500);
+ $this->assertTrue($response->isError());
+ }
+
+ public function testIsInformational()
+ {
+ $response = new Response(100);
+ $this->assertTrue($response->isInformational());
+ $response = new Response(200);
+ $this->assertFalse($response->isInformational());
+ }
+
+ public function testIsRedirect()
+ {
+ $response = new Response(301);
+ $this->assertTrue($response->isRedirect());
+ $response = new Response(200);
+ $this->assertFalse($response->isRedirect());
+ }
+
+ public function testIsServerError()
+ {
+ $response = new Response(500);
+ $this->assertTrue($response->isServerError());
+ $response = new Response(400);
+ $this->assertFalse($response->isServerError());
+ }
+
+ public function testIsSuccessful()
+ {
+ $response = new Response(200);
+ $this->assertTrue($response->isSuccessful());
+ $response = new Response(403);
+ $this->assertFalse($response->isSuccessful());
+ }
+
+ public function testGetAcceptRanges()
+ {
+ $this->assertEquals('bytes', $this->response->getAcceptRanges());
+ }
+
+ public function testCalculatesAge()
+ {
+ $this->assertEquals(12, $this->response->calculateAge());
+
+ $this->response->removeHeader('Age');
+ $this->response->removeHeader('Date');
+ $this->assertNull($this->response->calculateAge());
+
+ $this->response->setHeader('Date', gmdate(ClientInterface::HTTP_DATE, strtotime('-1 minute')));
+ // If the test runs slowly, still pass with a +5 second allowance
+ $this->assertTrue($this->response->getAge() - 60 <= 5);
+ }
+
+ public function testGetAllow()
+ {
+ $this->assertEquals('GET, HEAD', $this->response->getAllow());
+ }
+
+ public function testGetCacheControl()
+ {
+ $this->assertEquals('no-cache', $this->response->getCacheControl());
+ }
+
+ public function testGetContentEncoding()
+ {
+ $this->assertEquals('gzip', $this->response->getContentEncoding());
+ }
+
+ public function testGetContentLanguage()
+ {
+ $this->assertEquals('da', $this->response->getContentLanguage());
+ }
+
+ public function testGetContentLength()
+ {
+ $this->assertEquals('348', $this->response->getContentLength());
+ }
+
+ public function testGetContentLocation()
+ {
+ $this->assertEquals('/index.htm', $this->response->getContentLocation());
+ }
+
+ public function testGetContentDisposition()
+ {
+ $this->assertEquals('attachment; filename=fname.ext', $this->response->getContentDisposition());
+ }
+
+ public function testGetContentMd5()
+ {
+ $this->assertEquals('Q2hlY2sgSW50ZWdyaXR5IQ==', $this->response->getContentMd5());
+ }
+
+ public function testGetContentRange()
+ {
+ $this->assertEquals('bytes 21010-47021/47022', $this->response->getContentRange());
+ }
+
+ public function testGetContentType()
+ {
+ $this->assertEquals('text/html; charset=utf-8', $this->response->getContentType());
+ }
+
+ public function testGetDate()
+ {
+ $this->assertEquals('Tue, 15 Nov 1994 08:12:31 GMT', $this->response->getDate());
+ }
+
+ public function testGetEtag()
+ {
+ $this->assertEquals('737060cd8c284d8af7ad3082f209582d', $this->response->getEtag());
+ }
+
+ public function testGetExpires()
+ {
+ $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $this->response->getExpires());
+ }
+
+ public function testGetLastModified()
+ {
+ $this->assertEquals('Tue, 15 Nov 1994 12:45:26 GMT', $this->response->getLastModified());
+ }
+
+ public function testGetLocation()
+ {
+ $this->assertEquals('http://www.w3.org/pub/WWW/People.html', $this->response->getLocation());
+ }
+
+ public function testGetPragma()
+ {
+ $this->assertEquals('no-cache', $this->response->getPragma());
+ }
+
+ public function testGetProxyAuthenticate()
+ {
+ $this->assertEquals('Basic', $this->response->getProxyAuthenticate());
+ }
+
+ public function testGetServer()
+ {
+ $this->assertEquals('Apache/1.3.27 (Unix) (Red-Hat/Linux)', $this->response->getServer());
+ }
+
+ public function testGetSetCookie()
+ {
+ $this->assertEquals('UserID=JohnDoe; Max-Age=3600; Version=1', $this->response->getSetCookie());
+ }
+
+ public function testGetMultipleSetCookie()
+ {
+ $this->response->addHeader('Set-Cookie', 'UserID=Mike; Max-Age=200');
+ $this->assertEquals(array(
+ 'UserID=JohnDoe; Max-Age=3600; Version=1',
+ 'UserID=Mike; Max-Age=200',
+ ), $this->response->getHeader('Set-Cookie')->toArray());
+ }
+
+ public function testGetSetCookieNormalizesHeaders()
+ {
+ $this->response->addHeaders(array(
+ 'Set-Cooke' => 'boo',
+ 'set-cookie' => 'foo'
+ ));
+
+ $this->assertEquals(array(
+ 'UserID=JohnDoe; Max-Age=3600; Version=1',
+ 'foo'
+ ), $this->response->getHeader('Set-Cookie')->toArray());
+
+ $this->response->addHeaders(array(
+ 'set-cookie' => 'fubu'
+ ));
+ $this->assertEquals(
+ array('UserID=JohnDoe; Max-Age=3600; Version=1', 'foo', 'fubu'),
+ $this->response->getHeader('Set-Cookie')->toArray()
+ );
+ }
+
+ public function testGetTrailer()
+ {
+ $this->assertEquals('Max-Forwards', $this->response->getTrailer());
+ }
+
+ public function testGetTransferEncoding()
+ {
+ $this->assertEquals('chunked', $this->response->getTransferEncoding());
+ }
+
+ public function testGetVary()
+ {
+ $this->assertEquals('*', $this->response->getVary());
+ }
+
+ public function testReturnsViaHeader()
+ {
+ $this->assertEquals('1.0 fred, 1.1 nowhere.com (Apache/1.1)', $this->response->getVia());
+ }
+ public function testGetWarning()
+ {
+ $this->assertEquals('199 Miscellaneous warning', $this->response->getWarning());
+ }
+
+ public function testReturnsWwwAuthenticateHeader()
+ {
+ $this->assertEquals('Basic', $this->response->getWwwAuthenticate());
+ }
+
+ public function testReturnsConnectionHeader()
+ {
+ $this->assertEquals(null, $this->response->getConnection());
+ $this->response->setHeader('Connection', 'close');
+ $this->assertEquals('close', $this->response->getConnection());
+ }
+
+ public function testReturnsHeaders()
+ {
+ $this->assertEquals('Basic', $this->response->getHeader('WWW-Authenticate', null, true));
+ $this->assertEquals('chunked', $this->response->getHeader('Transfer-Encoding', null, false));
+ }
+
+ public function testHasTransferInfo()
+ {
+ $stats = array (
+ 'url' => 'http://www.google.com/',
+ 'content_type' => 'text/html; charset=ISO-8859-1',
+ 'http_code' => 200,
+ 'header_size' => 606,
+ 'request_size' => 53,
+ 'filetime' => -1,
+ 'ssl_verify_result' => 0,
+ 'redirect_count' => 0,
+ 'total_time' => 0.093284,
+ 'namelookup_time' => 0.001349,
+ 'connect_time' => 0.01635,
+ 'pretransfer_time' => 0.016358,
+ 'size_upload' => 0,
+ 'size_download' => 10330,
+ 'speed_download' => 110737,
+ 'speed_upload' => 0,
+ 'download_content_length' => -1,
+ 'upload_content_length' => 0,
+ 'starttransfer_time' => 0.07066,
+ 'redirect_time' => 0,
+ );
+
+ // Uninitialized state
+ $this->assertNull($this->response->getInfo('url'));
+ $this->assertEquals(array(), $this->response->getInfo());
+
+ // Set the stats
+ $this->response->setInfo($stats);
+ $this->assertEquals($stats, $this->response->getInfo());
+ $this->assertEquals(606, $this->response->getInfo('header_size'));
+ $this->assertNull($this->response->getInfo('does_not_exist'));
+ }
+
+ /**
+ * @return Response
+ */
+ private function getResponse($code, array $headers = null, EntityBody $body = null)
+ {
+ return new Response($code, $headers, $body);
+ }
+
+ public function testDeterminesIfItCanBeCached()
+ {
+ $this->assertTrue($this->getResponse(200)->canCache());
+ $this->assertTrue($this->getResponse(410)->canCache());
+ $this->assertFalse($this->getResponse(404)->canCache());
+ $this->assertTrue($this->getResponse(200, array(
+ 'Cache-Control' => 'public'
+ ))->canCache());
+
+ // This has the no-store directive
+ $this->assertFalse($this->getResponse(200, array(
+ 'Cache-Control' => 'private, no-store'
+ ))->canCache());
+
+ // The body cannot be read, so it cannot be cached
+ $tmp = tempnam('/tmp', 'not-readable');
+ $resource = fopen($tmp, 'w');
+ $this->assertFalse($this->getResponse(200, array(
+ 'Transfer-Encoding' => 'chunked'
+ ), EntityBody::factory($resource, 10))->canCache());
+ unlink($tmp);
+
+ // The body is 0 length, cannot be read, so it can be cached
+ $tmp = tempnam('/tmp', 'not-readable');
+ $resource = fopen($tmp, 'w');
+ $this->assertTrue($this->getResponse(200, array(array(
+ 'Content-Length' => 0
+ )), EntityBody::factory($resource, 0))->canCache());
+ unlink($tmp);
+ }
+
+ public function testDeterminesResponseMaxAge()
+ {
+ $this->assertEquals(null, $this->getResponse(200)->getMaxAge());
+
+ // Uses the response's s-maxage
+ $this->assertEquals(140, $this->getResponse(200, array(
+ 'Cache-Control' => 's-maxage=140'
+ ))->getMaxAge());
+
+ // Uses the response's max-age
+ $this->assertEquals(120, $this->getResponse(200, array(
+ 'Cache-Control' => 'max-age=120'
+ ))->getMaxAge());
+
+ // Uses the response's max-age
+ $this->assertEquals(120, $this->getResponse(200, array(
+ 'Cache-Control' => 'max-age=120',
+ 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day'))
+ ))->getMaxAge());
+
+ // Uses the Expires date
+ $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array(
+ 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day'))
+ ))->getMaxAge());
+
+ // Uses the Expires date
+ $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array(
+ 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day'))
+ ))->getMaxAge());
+ }
+
+ public function testDeterminesIfItCanValidate()
+ {
+ $response = new Response(200);
+ $this->assertFalse($response->canValidate());
+ $response->setHeader('ETag', '123');
+ $this->assertTrue($response->canValidate());
+ $response->removeHeader('ETag');
+ $this->assertFalse($response->canValidate());
+ $response->setHeader('Last-Modified', '123');
+ $this->assertTrue($response->canValidate());
+ }
+
+ public function testCalculatesFreshness()
+ {
+ $response = new Response(200);
+ $this->assertNull($response->isFresh());
+ $this->assertNull($response->getFreshness());
+
+ $response->setHeader('Cache-Control', 'max-age=120');
+ $response->setHeader('Age', 100);
+ $this->assertEquals(20, $response->getFreshness());
+ $this->assertTrue($response->isFresh());
+
+ $response->setHeader('Age', 120);
+ $this->assertEquals(0, $response->getFreshness());
+ $this->assertTrue($response->isFresh());
+
+ $response->setHeader('Age', 150);
+ $this->assertEquals(-30, $response->getFreshness());
+ $this->assertFalse($response->isFresh());
+ }
+
+ public function testHandlesProtocols()
+ {
+ $this->assertSame($this->response, $this->response->setProtocol('HTTP', '1.0'));
+ $this->assertEquals('HTTP', $this->response->getProtocol());
+ $this->assertEquals('1.0', $this->response->getProtocolVersion());
+ }
+
+ public function testComparesContentType()
+ {
+ $response = new Response(200, array(
+ 'Content-Type' => 'text/html; charset=ISO-8859-4'
+ ));
+
+ $this->assertTrue($response->isContentType('text/html'));
+ $this->assertTrue($response->isContentType('TExT/html'));
+ $this->assertTrue($response->isContentType('charset=ISO-8859-4'));
+ $this->assertFalse($response->isContentType('application/xml'));
+ }
+
+ public function testResponseDeterminesIfMethodIsAllowedBaseOnAllowHeader()
+ {
+ $response = new Response(200, array(
+ 'Allow' => 'OPTIONS, POST, deletE,GET'
+ ));
+
+ $this->assertTrue($response->isMethodAllowed('get'));
+ $this->assertTrue($response->isMethodAllowed('GET'));
+ $this->assertTrue($response->isMethodAllowed('options'));
+ $this->assertTrue($response->isMethodAllowed('post'));
+ $this->assertTrue($response->isMethodAllowed('Delete'));
+ $this->assertFalse($response->isMethodAllowed('put'));
+ $this->assertFalse($response->isMethodAllowed('PUT'));
+
+ $response = new Response(200);
+ $this->assertFalse($response->isMethodAllowed('get'));
+ }
+
+ public function testParsesJsonResponses()
+ {
+ $response = new Response(200, array(), '{"foo": "bar"}');
+ $this->assertEquals(array('foo' => 'bar'), $response->json());
+ // Return array when null is a service response
+ $response = new Response(200);
+ $this->assertEquals(array(), $response->json());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage Unable to parse response body into JSON: 4
+ */
+ public function testThrowsExceptionWhenFailsToParseJsonResponse()
+ {
+ $response = new Response(200, array(), '{"foo": "');
+ $response->json();
+ }
+
+ public function testParsesXmlResponses()
+ {
+ $response = new Response(200, array(), '<abc><foo>bar</foo></abc>');
+ $this->assertEquals('bar', (string) $response->xml()->foo);
+ // Always return a SimpleXMLElement from the xml method
+ $response = new Response(200);
+ $this->assertEmpty((string) $response->xml()->foo);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML
+ */
+ public function testThrowsExceptionWhenFailsToParseXmlResponse()
+ {
+ $response = new Response(200, array(), '<abc');
+ $response->xml();
+ }
+
+ public function testResponseIsSerializable()
+ {
+ $response = new Response(200, array('Foo' => 'bar'), 'test');
+ $r = unserialize(serialize($response));
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('bar', (string) $r->getHeader('Foo'));
+ $this->assertEquals('test', (string) $r->getBody());
+ }
+
+ public function testPreventsComplexExternalEntities()
+ {
+ $xml = '<?xml version="1.0"?><!DOCTYPE scan[<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=ResponseTest.php">]><scan>&test;</scan>';
+ $response = new Response(200, array(), $xml);
+
+ $oldCwd = getcwd();
+ chdir(__DIR__);
+ try {
+ $xml = $response->xml();
+ chdir($oldCwd);
+ $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML());
+ } catch (\Exception $e) {
+ chdir($oldCwd);
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php
new file mode 100644
index 0000000..7228453
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\Mimetypes;
+
+/**
+ * @covers Guzzle\Http\Mimetypes
+ */
+class MimetypesTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testGetsFromExtension()
+ {
+ $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php'));
+ }
+
+ public function testGetsFromFilename()
+ {
+ $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__));
+ }
+
+ public function testGetsFromCaseInsensitiveFilename()
+ {
+ $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__)));
+ }
+
+ public function testReturnsNullWhenNoMatchFound()
+ {
+ $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php
new file mode 100644
index 0000000..549d3ed
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\QueryString;
+use Guzzle\Http\QueryAggregator\CommaAggregator as Ag;
+
+class CommaAggregatorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testAggregates()
+ {
+ $query = new QueryString();
+ $a = new Ag();
+ $key = 'test 123';
+ $value = array('foo 123', 'baz', 'bar');
+ $result = $a->aggregate($key, $value, $query);
+ $this->assertEquals(array('test%20123' => 'foo%20123,baz,bar'), $result);
+ }
+
+ public function testEncodes()
+ {
+ $query = new QueryString();
+ $query->useUrlEncoding(false);
+ $a = new Ag();
+ $key = 'test 123';
+ $value = array('foo 123', 'baz', 'bar');
+ $result = $a->aggregate($key, $value, $query);
+ $this->assertEquals(array('test 123' => 'foo 123,baz,bar'), $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php
new file mode 100644
index 0000000..6a4d9d9
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\QueryString;
+use Guzzle\Http\QueryAggregator\DuplicateAggregator as Ag;
+
+class DuplicateAggregatorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testAggregates()
+ {
+ $query = new QueryString();
+ $a = new Ag();
+ $key = 'facet 1';
+ $value = array('size a', 'width b');
+ $result = $a->aggregate($key, $value, $query);
+ $this->assertEquals(array('facet%201' => array('size%20a', 'width%20b')), $result);
+ }
+
+ public function testEncodes()
+ {
+ $query = new QueryString();
+ $query->useUrlEncoding(false);
+ $a = new Ag();
+ $key = 'facet 1';
+ $value = array('size a', 'width b');
+ $result = $a->aggregate($key, $value, $query);
+ $this->assertEquals(array('facet 1' => array('size a', 'width b')), $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php
new file mode 100644
index 0000000..1e7f0c2
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\QueryString;
+use Guzzle\Http\QueryAggregator\PhpAggregator as Ag;
+
+class PhpAggregatorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testEncodes()
+ {
+ $query = new QueryString();
+ $query->useUrlEncoding(false);
+ $a = new Ag();
+ $key = 't';
+ $value = array(
+ 'v1' => 'a',
+ 'v2' => 'b',
+ 'v3' => array(
+ 'v4' => 'c',
+ 'v5' => 'd',
+ )
+ );
+ $result = $a->aggregate($key, $value, $query);
+ $this->assertEquals(array(
+ 't[v1]' => 'a',
+ 't[v2]' => 'b',
+ 't[v3][v4]' => 'c',
+ 't[v3][v5]' => 'd',
+ ), $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php
new file mode 100644
index 0000000..948db44
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php
@@ -0,0 +1,233 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\QueryString;
+use Guzzle\Http\QueryAggregator\DuplicateAggregator;
+use Guzzle\Http\QueryAggregator\CommaAggregator;
+
+class QueryStringTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var \Guzzle\Http\QueryString The query string object to test */
+ protected $q;
+
+ public function setup()
+ {
+ $this->q = new QueryString();
+ }
+
+ public function testGetFieldSeparator()
+ {
+ $this->assertEquals('&', $this->q->getFieldSeparator());
+ }
+
+ public function testGetValueSeparator()
+ {
+ $this->assertEquals('=', $this->q->getValueSeparator());
+ }
+
+ public function testIsUrlEncoding()
+ {
+ $this->assertEquals('RFC 3986', $this->q->getUrlEncoding());
+ $this->assertTrue($this->q->isUrlEncoding());
+ $this->assertEquals('foo%20bar', $this->q->encodeValue('foo bar'));
+
+ $this->q->useUrlEncoding(QueryString::FORM_URLENCODED);
+ $this->assertTrue($this->q->isUrlEncoding());
+ $this->assertEquals(QueryString::FORM_URLENCODED, $this->q->getUrlEncoding());
+ $this->assertEquals('foo+bar', $this->q->encodeValue('foo bar'));
+
+ $this->assertSame($this->q, $this->q->useUrlEncoding(false));
+ $this->assertFalse($this->q->isUrlEncoding());
+ $this->assertFalse($this->q->isUrlEncoding());
+ }
+
+ public function testSetFieldSeparator()
+ {
+ $this->assertEquals($this->q, $this->q->setFieldSeparator('/'));
+ $this->assertEquals('/', $this->q->getFieldSeparator());
+ }
+
+ public function testSetValueSeparator()
+ {
+ $this->assertEquals($this->q, $this->q->setValueSeparator('/'));
+ $this->assertEquals('/', $this->q->getValueSeparator());
+ }
+
+ public function testUrlEncode()
+ {
+ $params = array(
+ 'test' => 'value',
+ 'test 2' => 'this is a test?',
+ 'test3' => array('v1', 'v2', 'v3'),
+ 'ሴ' => 'bar'
+ );
+ $encoded = array(
+ 'test' => 'value',
+ 'test%202' => rawurlencode('this is a test?'),
+ 'test3%5B0%5D' => 'v1',
+ 'test3%5B1%5D' => 'v2',
+ 'test3%5B2%5D' => 'v3',
+ '%E1%88%B4' => 'bar'
+ );
+ $this->q->replace($params);
+ $this->assertEquals($encoded, $this->q->urlEncode());
+
+ // Disable encoding
+ $testData = array('test 2' => 'this is a test');
+ $this->q->replace($testData);
+ $this->q->useUrlEncoding(false);
+ $this->assertEquals($testData, $this->q->urlEncode());
+ }
+
+ public function testToString()
+ {
+ // Check with no parameters
+ $this->assertEquals('', $this->q->__toString());
+
+ $params = array(
+ 'test' => 'value',
+ 'test 2' => 'this is a test?',
+ 'test3' => array('v1', 'v2', 'v3'),
+ 'test4' => null,
+ );
+ $this->q->replace($params);
+ $this->assertEquals('test=value&test%202=this%20is%20a%20test%3F&test3%5B0%5D=v1&test3%5B1%5D=v2&test3%5B2%5D=v3&test4', $this->q->__toString());
+ $this->q->useUrlEncoding(false);
+ $this->assertEquals('test=value&test 2=this is a test?&test3[0]=v1&test3[1]=v2&test3[2]=v3&test4', $this->q->__toString());
+
+ // Use an alternative aggregator
+ $this->q->setAggregator(new CommaAggregator());
+ $this->assertEquals('test=value&test 2=this is a test?&test3=v1,v2,v3&test4', $this->q->__toString());
+ }
+
+ public function testAllowsMultipleValuesPerKey()
+ {
+ $q = new QueryString();
+ $q->add('facet', 'size');
+ $q->add('facet', 'width');
+ $q->add('facet.field', 'foo');
+ // Use the duplicate aggregator
+ $q->setAggregator(new DuplicateAggregator());
+ $this->assertEquals('facet=size&facet=width&facet.field=foo', $q->__toString());
+ }
+
+ public function testAllowsNestedQueryData()
+ {
+ $this->q->replace(array(
+ 'test' => 'value',
+ 't' => array(
+ 'v1' => 'a',
+ 'v2' => 'b',
+ 'v3' => array(
+ 'v4' => 'c',
+ 'v5' => 'd',
+ )
+ )
+ ));
+
+ $this->q->useUrlEncoding(false);
+ $this->assertEquals('test=value&t[v1]=a&t[v2]=b&t[v3][v4]=c&t[v3][v5]=d', $this->q->__toString());
+ }
+
+ public function parseQueryProvider()
+ {
+ return array(
+ // Ensure that multiple query string values are allowed per value
+ array('q=a&q=b', array('q' => array('a', 'b'))),
+ // Ensure that PHP array style query string values are parsed
+ array('q[]=a&q[]=b', array('q' => array('a', 'b'))),
+ // Ensure that a single PHP array style query string value is parsed into an array
+ array('q[]=a', array('q' => array('a'))),
+ // Ensure that decimals are allowed in query strings
+ array('q.a=a&q.b=b', array(
+ 'q.a' => 'a',
+ 'q.b' => 'b'
+ )),
+ // Ensure that query string values are percent decoded
+ array('q%20a=a%20b', array('q a' => 'a b')),
+ // Ensure null values can be added
+ array('q&a', array('q' => false, 'a' => false)),
+ );
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesQueryStrings($query, $data)
+ {
+ $query = QueryString::fromString($query);
+ $this->assertEquals($data, $query->getAll());
+ }
+
+ public function testProperlyDealsWithDuplicateQueryStringValues()
+ {
+ $query = QueryString::fromString('foo=a&foo=b&?µ=c');
+ $this->assertEquals(array('a', 'b'), $query->get('foo'));
+ $this->assertEquals('c', $query->get('?µ'));
+ }
+
+ public function testAllowsBlankQueryStringValues()
+ {
+ $query = QueryString::fromString('foo');
+ $this->assertEquals('foo', (string) $query);
+ $query->set('foo', QueryString::BLANK);
+ $this->assertEquals('foo', (string) $query);
+ }
+
+ public function testAllowsFalsyQueryStringValues()
+ {
+ $query = QueryString::fromString('0');
+ $this->assertEquals('0', (string) $query);
+ $query->set('0', QueryString::BLANK);
+ $this->assertSame('0', (string) $query);
+ }
+
+ public function testFromStringIgnoresQuestionMark()
+ {
+ $query = QueryString::fromString('foo=baz&bar=boo');
+ $this->assertEquals('foo=baz&bar=boo', (string) $query);
+ }
+
+ public function testConvertsPlusSymbolsToSpaces()
+ {
+ $query = QueryString::fromString('var=foo+bar');
+ $this->assertEquals('foo bar', $query->get('var'));
+ }
+
+ public function testFromStringDoesntMangleZeroes()
+ {
+ $query = QueryString::fromString('var=0');
+ $this->assertSame('0', $query->get('var'));
+ }
+
+ public function testAllowsZeroValues()
+ {
+ $query = new QueryString(array(
+ 'foo' => 0,
+ 'baz' => '0',
+ 'bar' => null,
+ 'boo' => false,
+ 'bam' => ''
+ ));
+ $this->assertEquals('foo=0&baz=0&bar&boo&bam=', (string) $query);
+ }
+
+ public function testFromStringDoesntStripTrailingEquals()
+ {
+ $query = QueryString::fromString('data=mF0b3IiLCJUZWFtIERldiJdfX0=');
+ $this->assertEquals('mF0b3IiLCJUZWFtIERldiJdfX0=', $query->get('data'));
+ }
+
+ public function testGuessesIfDuplicateAggregatorShouldBeUsed()
+ {
+ $query = QueryString::fromString('test=a&test=b');
+ $this->assertEquals('test=a&test=b', (string) $query);
+ }
+
+ public function testGuessesIfDuplicateAggregatorShouldBeUsedAndChecksForPhpStyle()
+ {
+ $query = QueryString::fromString('test[]=a&test[]=b');
+ $this->assertEquals('test%5B0%5D=a&test%5B1%5D=b', (string) $query);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php
new file mode 100644
index 0000000..6bb3fed
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\ReadLimitEntityBody;
+
+/**
+ * @covers Guzzle\Http\ReadLimitEntityBody
+ */
+class ReadLimitEntityBodyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var ReadLimitEntityBody */
+ protected $body;
+
+ /** @var EntityBody */
+ protected $decorated;
+
+ public function setUp()
+ {
+ $this->decorated = EntityBody::factory(fopen(__FILE__, 'r'));
+ $this->body = new ReadLimitEntityBody($this->decorated, 10, 3);
+ }
+
+ public function testReturnsSubsetWhenCastToString()
+ {
+ $body = EntityBody::factory('foo_baz_bar');
+ $limited = new ReadLimitEntityBody($body, 3, 4);
+ $this->assertEquals('baz', (string) $limited);
+ }
+
+ public function testReturnsSubsetOfEmptyBodyWhenCastToString()
+ {
+ $body = EntityBody::factory('');
+ $limited = new ReadLimitEntityBody($body, 0, 10);
+ $this->assertEquals('', (string) $limited);
+ }
+
+ public function testSeeksWhenConstructed()
+ {
+ $this->assertEquals(3, $this->body->ftell());
+ }
+
+ public function testAllowsBoundedSeek()
+ {
+ $this->body->seek(100);
+ $this->assertEquals(13, $this->body->ftell());
+ $this->body->seek(0);
+ $this->assertEquals(3, $this->body->ftell());
+ $this->assertEquals(false, $this->body->seek(1000, SEEK_END));
+ }
+
+ public function testReadsOnlySubsetOfData()
+ {
+ $data = $this->body->read(100);
+ $this->assertEquals(10, strlen($data));
+ $this->assertFalse($this->body->read(1000));
+
+ $this->body->setOffset(10);
+ $newData = $this->body->read(100);
+ $this->assertEquals(10, strlen($newData));
+ $this->assertNotSame($data, $newData);
+ }
+
+ public function testClaimsConsumedWhenReadLimitIsReached()
+ {
+ $this->assertFalse($this->body->isConsumed());
+ $this->body->read(1000);
+ $this->assertTrue($this->body->isConsumed());
+ }
+
+ public function testContentLengthIsBounded()
+ {
+ $this->assertEquals(10, $this->body->getContentLength());
+ }
+
+ public function testContentMd5IsBasedOnSubsection()
+ {
+ $this->assertNotSame($this->body->getContentMd5(), $this->decorated->getContentMd5());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php
new file mode 100755
index 0000000..886236d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php
@@ -0,0 +1,277 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Redirect;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\RedirectPlugin;
+use Guzzle\Http\Exception\TooManyRedirectsException;
+use Guzzle\Plugin\History\HistoryPlugin;
+
+/**
+ * @covers Guzzle\Http\RedirectPlugin
+ */
+class RedirectPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRedirectsRequests()
+ {
+ // Flush the server and queue up a redirect followed by a successful response
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ // Create a client that uses the default redirect behavior
+ $client = new Client($this->getServer()->getUrl());
+ $history = new HistoryPlugin();
+ $client->addSubscriber($history);
+
+ $request = $client->get('/foo');
+ $response = $request->send();
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertContains('/redirect2', $response->getEffectiveUrl());
+
+ // Ensure that two requests were sent
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('/foo', $requests[0]->getResource());
+ $this->assertEquals('GET', $requests[0]->getMethod());
+ $this->assertEquals('/redirect1', $requests[1]->getResource());
+ $this->assertEquals('GET', $requests[1]->getMethod());
+ $this->assertEquals('/redirect2', $requests[2]->getResource());
+ $this->assertEquals('GET', $requests[2]->getMethod());
+
+ // Ensure that the redirect count was incremented
+ $this->assertEquals(2, $request->getParams()->get(RedirectPlugin::REDIRECT_COUNT));
+ $this->assertCount(3, $history);
+ $requestHistory = $history->getAll();
+
+ $this->assertEquals(301, $requestHistory[0]['response']->getStatusCode());
+ $this->assertEquals('/redirect1', (string) $requestHistory[0]['response']->getHeader('Location'));
+ $this->assertEquals(301, $requestHistory[1]['response']->getStatusCode());
+ $this->assertEquals('/redirect2', (string) $requestHistory[1]['response']->getHeader('Location'));
+ $this->assertEquals(200, $requestHistory[2]['response']->getStatusCode());
+ }
+
+ public function testCanLimitNumberOfRedirects()
+ {
+ // Flush the server and queue up a redirect followed by a successful response
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ try {
+ $client = new Client($this->getServer()->getUrl());
+ $client->get('/foo')->send();
+ $this->fail('Did not throw expected exception');
+ } catch (TooManyRedirectsException $e) {
+ $this->assertContains(
+ "5 redirects were issued for this request:\nGET /foo HTTP/1.1\r\n",
+ $e->getMessage()
+ );
+ }
+ }
+
+ public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $client->post('/foo', array('X-Baz' => 'bar'), 'testing')->send();
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('POST', $requests[0]->getMethod());
+ $this->assertEquals('GET', $requests[1]->getMethod());
+ $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz'));
+ $this->assertEquals('GET', $requests[2]->getMethod());
+ }
+
+ public function testCanRedirectWithStrictRfcCompliance()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post('/foo', array('X-Baz' => 'bar'), 'testing');
+ $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true);
+ $request->send();
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('POST', $requests[0]->getMethod());
+ $this->assertEquals('POST', $requests[1]->getMethod());
+ $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz'));
+ $this->assertEquals('POST', $requests[2]->getMethod());
+ }
+
+ public function testRedirect303WithGet()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post('/foo');
+ $request->send();
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('POST', $requests[0]->getMethod());
+ $this->assertEquals('GET', $requests[1]->getMethod());
+ }
+
+ public function testRedirect303WithGetWithStrictRfcCompliance()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post('/foo');
+ $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true);
+ $request->send();
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('POST', $requests[0]->getMethod());
+ $this->assertEquals('GET', $requests[1]->getMethod());
+ }
+
+ public function testRewindsStreamWhenRedirectingIfNeeded()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put();
+ $request->configureRedirects(true);
+ $body = EntityBody::factory('foo');
+ $body->read(1);
+ $request->setBody($body);
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('foo', (string) $requests[0]->getBody());
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\CouldNotRewindStreamException
+ */
+ public function testThrowsExceptionWhenStreamCannotBeRewound()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put();
+ $request->configureRedirects(true);
+ $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r'));
+ $body->read(1);
+ $request->setBody($body)->send();
+ }
+
+ public function testRedirectsCanBeDisabledPerRequest()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array("HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n"));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->put();
+ $request->configureRedirects(false, 0);
+ $this->assertEquals(301, $request->send()->getStatusCode());
+ }
+
+ public function testCanRedirectWithNoLeadingSlashAndQuery()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect?foo=bar\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('?foo=bar');
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $requests[0]->getUrl());
+ $this->assertEquals($this->getServer()->getUrl() . 'redirect?foo=bar', $requests[1]->getUrl());
+ // Ensure that the history on the actual request is correct
+ $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $request->getUrl());
+ }
+
+ public function testRedirectWithStrictRfc386Compliance()
+ {
+ // Flush the server and queue up a redirect followed by a successful response
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('/foo');
+ $request->send();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('/redirect', $requests[1]->getResource());
+ }
+
+ public function testResetsHistoryEachSend()
+ {
+ // Flush the server and queue up a redirect followed by a successful response
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ // Create a client that uses the default redirect behavior
+ $client = new Client($this->getServer()->getUrl());
+ $history = new HistoryPlugin();
+ $client->addSubscriber($history);
+
+ $request = $client->get('/foo');
+ $response = $request->send();
+ $this->assertEquals(3, count($history));
+ $this->assertTrue($request->getParams()->hasKey('redirect.count'));
+ $this->assertContains('/redirect2', $response->getEffectiveUrl());
+
+ $request->send();
+ $this->assertFalse($request->getParams()->hasKey('redirect.count'));
+ }
+
+ public function testHandlesRedirectsWithSpacesProperly()
+ {
+ // Flush the server and queue up a redirect followed by a successful response
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->get('/foo');
+ $request->send();
+ $reqs = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('/redirect%201', $reqs[1]->getResource());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php
new file mode 100644
index 0000000..94eb59a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Client;
+
+/**
+ * The Server class is used to control a scripted webserver using node.js that
+ * will respond to HTTP requests with queued responses.
+ *
+ * Queued responses will be served to requests using a FIFO order. All requests
+ * received by the server are stored on the node.js server and can be retrieved
+ * by calling {@see Server::getReceivedRequests()}.
+ *
+ * Mock responses that don't require data to be transmitted over HTTP a great
+ * for testing. Mock response, however, cannot test the actual sending of an
+ * HTTP request using cURL. This test server allows the simulation of any
+ * number of HTTP request response transactions to test the actual sending of
+ * requests over the wire without having to leave an internal network.
+ */
+class Server
+{
+ const DEFAULT_PORT = 8124;
+ const REQUEST_DELIMITER = "\n----[request]\n";
+
+ /** @var int Port that the server will listen on */
+ private $port;
+
+ /** @var bool Is the server running */
+ private $running = false;
+
+ /** @var Client */
+ private $client;
+
+ /**
+ * Create a new scripted server
+ *
+ * @param int $port Port to listen on (defaults to 8124)
+ */
+ public function __construct($port = null)
+ {
+ $this->port = $port ?: self::DEFAULT_PORT;
+ $this->client = new Client($this->getUrl());
+ register_shutdown_function(array($this, 'stop'));
+ }
+
+ /**
+ * Flush the received requests from the server
+ * @throws RuntimeException
+ */
+ public function flush()
+ {
+ $this->client->delete('guzzle-server/requests')->send();
+ }
+
+ /**
+ * Queue an array of responses or a single response on the server.
+ *
+ * Any currently queued responses will be overwritten. Subsequent requests
+ * on the server will return queued responses in FIFO order.
+ *
+ * @param array|Response $responses A single or array of Responses to queue
+ * @throws BadResponseException
+ */
+ public function enqueue($responses)
+ {
+ $data = array();
+ foreach ((array) $responses as $response) {
+
+ // Create the response object from a string
+ if (is_string($response)) {
+ $response = Response::fromMessage($response);
+ } elseif (!($response instanceof Response)) {
+ throw new BadResponseException('Responses must be strings or implement Response');
+ }
+
+ $data[] = array(
+ 'statusCode' => $response->getStatusCode(),
+ 'reasonPhrase' => $response->getReasonPhrase(),
+ 'headers' => $response->getHeaders()->toArray(),
+ 'body' => $response->getBody(true)
+ );
+ }
+
+ $request = $this->client->put('guzzle-server/responses', null, json_encode($data));
+ $request->send();
+ }
+
+ /**
+ * Check if the server is running
+ *
+ * @return bool
+ */
+ public function isRunning()
+ {
+ if ($this->running) {
+ return true;
+ }
+
+ try {
+ $this->client->get('guzzle-server/perf', array(), array('timeout' => 5))->send();
+ $this->running = true;
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Get the URL to the server
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return 'http://127.0.0.1:' . $this->getPort() . '/';
+ }
+
+ /**
+ * Get the port that the server is listening on
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Get all of the received requests
+ *
+ * @param bool $hydrate Set to TRUE to turn the messages into
+ * actual {@see RequestInterface} objects. If $hydrate is FALSE,
+ * requests will be returned as strings.
+ *
+ * @return array
+ * @throws RuntimeException
+ */
+ public function getReceivedRequests($hydrate = false)
+ {
+ $response = $this->client->get('guzzle-server/requests')->send();
+ $data = array_filter(explode(self::REQUEST_DELIMITER, $response->getBody(true)));
+ if ($hydrate) {
+ $data = array_map(function($message) {
+ return RequestFactory::getInstance()->fromMessage($message);
+ }, $data);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Start running the node.js server in the background
+ */
+ public function start()
+ {
+ if (!$this->isRunning()) {
+ exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR
+ . 'server.js ' . $this->port
+ . ' >> /tmp/server.log 2>&1 &');
+ // Wait at most 5 seconds for the server the setup before
+ // proceeding.
+ $start = time();
+ while (!$this->isRunning() && time() - $start < 5);
+ if (!$this->running) {
+ throw new RuntimeException(
+ 'Unable to contact server.js. Have you installed node.js v0.5.0+? node must be in your path.'
+ );
+ }
+ }
+ }
+
+ /**
+ * Stop running the node.js server
+ */
+ public function stop()
+ {
+ if (!$this->isRunning()) {
+ return false;
+ }
+
+ $this->running = false;
+ $this->client->delete('guzzle-server')->send();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php
new file mode 100644
index 0000000..091314b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Redirect;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\StaticClient;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Http\Message\Response;
+use Guzzle\Stream\Stream;
+
+/**
+ * @covers Guzzle\Http\StaticClient
+ */
+class StaticClientTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testMountsClient()
+ {
+ $client = new Client();
+ StaticClient::mount('FooBazBar', $client);
+ $this->assertTrue(class_exists('FooBazBar'));
+ $this->assertSame($client, $this->readAttribute('Guzzle\Http\StaticClient', 'client'));
+ }
+
+ public function requestProvider()
+ {
+ return array_map(
+ function ($m) { return array($m); },
+ array('GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS')
+ );
+ }
+
+ /**
+ * @dataProvider requestProvider
+ */
+ public function testSendsRequests($method)
+ {
+ $mock = new MockPlugin(array(new Response(200)));
+ call_user_func('Guzzle\Http\StaticClient::' . $method, 'http://foo.com', array(
+ 'plugins' => array($mock)
+ ));
+ $requests = $mock->getReceivedRequests();
+ $this->assertCount(1, $requests);
+ $this->assertEquals($method, $requests[0]->getMethod());
+ }
+
+ public function testCanCreateStreamsUsingDefaultFactory()
+ {
+ $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"));
+ $stream = StaticClient::get($this->getServer()->getUrl(), array('stream' => true));
+ $this->assertInstanceOf('Guzzle\Stream\StreamInterface', $stream);
+ $this->assertEquals('test', (string) $stream);
+ }
+
+ public function testCanCreateStreamsUsingCustomFactory()
+ {
+ $stream = $this->getMockBuilder('Guzzle\Stream\StreamRequestFactoryInterface')
+ ->setMethods(array('fromRequest'))
+ ->getMockForAbstractClass();
+ $resource = new Stream(fopen('php://temp', 'r+'));
+ $stream->expects($this->once())
+ ->method('fromRequest')
+ ->will($this->returnValue($resource));
+ $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"));
+ $result = StaticClient::get($this->getServer()->getUrl(), array('stream' => $stream));
+ $this->assertSame($resource, $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php
new file mode 100644
index 0000000..28f2671
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace Guzzle\Tests\Http;
+
+use Guzzle\Http\QueryString;
+use Guzzle\Http\Url;
+
+/**
+ * @covers Guzzle\Http\Url
+ */
+class UrlTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testEmptyUrl()
+ {
+ $url = Url::factory('');
+ $this->assertEquals('', (string) $url);
+ }
+
+ public function testPortIsDeterminedFromScheme()
+ {
+ $this->assertEquals(80, Url::factory('http://www.test.com/')->getPort());
+ $this->assertEquals(443, Url::factory('https://www.test.com/')->getPort());
+ $this->assertEquals(null, Url::factory('ftp://www.test.com/')->getPort());
+ $this->assertEquals(8192, Url::factory('http://www.test.com:8192/')->getPort());
+ }
+
+ public function testCloneCreatesNewInternalObjects()
+ {
+ $u1 = Url::factory('http://www.test.com/');
+ $u2 = clone $u1;
+ $this->assertNotSame($u1->getQuery(), $u2->getQuery());
+ }
+
+ public function testValidatesUrlPartsInFactory()
+ {
+ $url = Url::factory('/index.php');
+ $this->assertEquals('/index.php', (string) $url);
+ $this->assertFalse($url->isAbsolute());
+
+ $url = 'http://michael:test@test.com:80/path/123?q=abc#test';
+ $u = Url::factory($url);
+ $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u);
+ $this->assertTrue($u->isAbsolute());
+ }
+
+ public function testAllowsFalsyUrlParts()
+ {
+ $url = Url::factory('http://0:50/0?0#0');
+ $this->assertSame('0', $url->getHost());
+ $this->assertEquals(50, $url->getPort());
+ $this->assertSame('/0', $url->getPath());
+ $this->assertEquals('0', (string) $url->getQuery());
+ $this->assertSame('0', $url->getFragment());
+ $this->assertEquals('http://0:50/0?0#0', (string) $url);
+
+ $url = Url::factory('');
+ $this->assertSame('', (string) $url);
+
+ $url = Url::factory('0');
+ $this->assertSame('0', (string) $url);
+ }
+
+ public function testBuildsRelativeUrlsWithFalsyParts()
+ {
+ $url = Url::buildUrl(array(
+ 'host' => '0',
+ 'path' => '0',
+ ));
+
+ $this->assertSame('//0/0', $url);
+
+ $url = Url::buildUrl(array(
+ 'path' => '0',
+ ));
+ $this->assertSame('0', $url);
+ }
+
+ public function testUrlStoresParts()
+ {
+ $url = Url::factory('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment');
+ $this->assertEquals('http', $url->getScheme());
+ $this->assertEquals('test', $url->getUsername());
+ $this->assertEquals('pass', $url->getPassword());
+ $this->assertEquals('www.test.com', $url->getHost());
+ $this->assertEquals(8081, $url->getPort());
+ $this->assertEquals('/path/path2/', $url->getPath());
+ $this->assertEquals('fragment', $url->getFragment());
+ $this->assertEquals('a=1&b=2', (string) $url->getQuery());
+
+ $this->assertEquals(array(
+ 'fragment' => 'fragment',
+ 'host' => 'www.test.com',
+ 'pass' => 'pass',
+ 'path' => '/path/path2/',
+ 'port' => 8081,
+ 'query' => 'a=1&b=2',
+ 'scheme' => 'http',
+ 'user' => 'test'
+ ), $url->getParts());
+ }
+
+ public function testHandlesPathsCorrectly()
+ {
+ $url = Url::factory('http://www.test.com');
+ $this->assertEquals('', $url->getPath());
+ $url->setPath('test');
+ $this->assertEquals('test', $url->getPath());
+
+ $url->setPath('/test/123/abc');
+ $this->assertEquals(array('test', '123', 'abc'), $url->getPathSegments());
+
+ $parts = parse_url('http://www.test.com/test');
+ $parts['path'] = '';
+ $this->assertEquals('http://www.test.com', Url::buildUrl($parts));
+ $parts['path'] = 'test';
+ $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts));
+ }
+
+ public function testAddsQueryStringIfPresent()
+ {
+ $this->assertEquals('?foo=bar', Url::buildUrl(array(
+ 'query' => 'foo=bar'
+ )));
+ }
+
+ public function testAddsToPath()
+ {
+ // Does nothing here
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(false));
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(null));
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(array()));
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(new \stdClass()));
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(''));
+ $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/'));
+ $this->assertEquals('http://e.com/baz/foo', (string) Url::factory('http://e.com/baz/')->addPath('foo'));
+ $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('relative'));
+ $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/relative'));
+ $this->assertEquals('http://e.com/base/0', (string) Url::factory('http://e.com/base')->addPath('0'));
+ $this->assertEquals('http://e.com/base/0/1', (string) Url::factory('http://e.com/base')->addPath('0')->addPath('1'));
+ }
+
+ /**
+ * URL combination data provider
+ *
+ * @return array
+ */
+ public function urlCombineDataProvider()
+ {
+ return array(
+ array('http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'),
+ array('http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'),
+ array('http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'),
+ array('http://www.example.com/path', 'more', 'http://www.example.com/path/more'),
+ array('http://www.example.com/path', 'more?q=1', 'http://www.example.com/path/more?q=1'),
+ array('http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'),
+ array('http://www.example.com/path', 'http://test.com', 'http://test.com'),
+ array('http://www.example.com:8080/path', 'http://test.com', 'http://test.com'),
+ array('http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'),
+ array('http://u:a@www.example.com/path', 'test', 'http://u:a@www.example.com/path/test'),
+ array('http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'),
+ array('/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'),
+ array('http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'),
+ array('http://www.example.com/?foo=bar', 'some/path', 'http://www.example.com/some/path?foo=bar'),
+ array('http://www.example.com/?foo=bar', 'some/path?boo=moo', 'http://www.example.com/some/path?boo=moo&foo=bar'),
+ array('http://www.example.com/some/', 'path?foo=bar&foo=baz', 'http://www.example.com/some/path?foo=bar&foo=baz'),
+ );
+ }
+
+ /**
+ * @dataProvider urlCombineDataProvider
+ */
+ public function testCombinesUrls($a, $b, $c)
+ {
+ $this->assertEquals($c, (string) Url::factory($a)->combine($b));
+ }
+
+ public function testHasGettersAndSetters()
+ {
+ $url = Url::factory('http://www.test.com/');
+ $this->assertEquals('example.com', $url->setHost('example.com')->getHost());
+ $this->assertEquals('8080', $url->setPort(8080)->getPort());
+ $this->assertEquals('/foo/bar', $url->setPath(array('foo', 'bar'))->getPath());
+ $this->assertEquals('a', $url->setPassword('a')->getPassword());
+ $this->assertEquals('b', $url->setUsername('b')->getUsername());
+ $this->assertEquals('abc', $url->setFragment('abc')->getFragment());
+ $this->assertEquals('https', $url->setScheme('https')->getScheme());
+ $this->assertEquals('a=123', (string) $url->setQuery('a=123')->getQuery());
+ $this->assertEquals('https://b:a@example.com:8080/foo/bar?a=123#abc', (string) $url);
+ $this->assertEquals('b=boo', (string) $url->setQuery(new QueryString(array(
+ 'b' => 'boo'
+ )))->getQuery());
+ $this->assertEquals('https://b:a@example.com:8080/foo/bar?b=boo#abc', (string) $url);
+ }
+
+ public function testSetQueryAcceptsArray()
+ {
+ $url = Url::factory('http://www.test.com');
+ $url->setQuery(array('a' => 'b'));
+ $this->assertEquals('http://www.test.com?a=b', (string) $url);
+ }
+
+ public function urlProvider()
+ {
+ return array(
+ array('/foo/..', '/'),
+ array('//foo//..', '/'),
+ array('/foo/../..', '/'),
+ array('/foo/../.', '/'),
+ array('/./foo/..', '/'),
+ array('/./foo', '/foo'),
+ array('/./foo/', '/foo/'),
+ array('/./foo/bar/baz/pho/../..', '/foo/bar'),
+ array('*', '*'),
+ array('/foo', '/foo'),
+ array('/abc/123/../foo/', '/abc/foo/'),
+ array('/a/b/c/./../../g', '/a/g'),
+ array('/b/c/./../../g', '/g'),
+ array('/b/c/./../../g', '/g'),
+ array('/c/./../../g', '/g'),
+ array('/./../../g', '/g'),
+ );
+ }
+
+ /**
+ * @dataProvider urlProvider
+ */
+ public function testNormalizesPaths($path, $result)
+ {
+ $url = Url::factory('http://www.example.com/');
+ $url->setPath($path)->normalizePath();
+ $this->assertEquals($result, $url->getPath());
+ }
+
+ public function testSettingHostWithPortModifiesPort()
+ {
+ $url = Url::factory('http://www.example.com');
+ $url->setHost('foo:8983');
+ $this->assertEquals('foo', $url->getHost());
+ $this->assertEquals(8983, $url->getPort());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesUrlCanBeParsed()
+ {
+ Url::factory('foo:////');
+ }
+
+ public function testConvertsSpecialCharsInPathWhenCastingToString()
+ {
+ $url = Url::factory('http://foo.com/baz bar?a=b');
+ $url->addPath('?');
+ $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url);
+ }
+
+ /**
+ * @link http://tools.ietf.org/html/rfc3986#section-5.4.1
+ */
+ public function rfc3986UrlProvider()
+ {
+ $result = array(
+ array('g', 'http://a/b/c/g'),
+ array('./g', 'http://a/b/c/g'),
+ array('g/', 'http://a/b/c/g/'),
+ array('/g', 'http://a/g'),
+ array('?y', 'http://a/b/c/d;p?y'),
+ array('g?y', 'http://a/b/c/g?y'),
+ array('#s', 'http://a/b/c/d;p?q#s'),
+ array('g#s', 'http://a/b/c/g#s'),
+ array('g?y#s', 'http://a/b/c/g?y#s'),
+ array(';x', 'http://a/b/c/;x'),
+ array('g;x', 'http://a/b/c/g;x'),
+ array('g;x?y#s', 'http://a/b/c/g;x?y#s'),
+ array('', 'http://a/b/c/d;p?q'),
+ array('.', 'http://a/b/c'),
+ array('./', 'http://a/b/c/'),
+ array('..', 'http://a/b'),
+ array('../', 'http://a/b/'),
+ array('../g', 'http://a/b/g'),
+ array('../..', 'http://a/'),
+ array('../../', 'http://a/'),
+ array('../../g', 'http://a/g')
+ );
+
+ // This support was added in PHP 5.4.7: https://bugs.php.net/bug.php?id=62844
+ if (version_compare(PHP_VERSION, '5.4.7', '>=')) {
+ $result[] = array('//g', 'http://g');
+ }
+
+ return $result;
+ }
+
+ /**
+ * @dataProvider rfc3986UrlProvider
+ */
+ public function testCombinesUrlsUsingRfc3986($relative, $result)
+ {
+ $a = Url::factory('http://a/b/c/d;p?q');
+ $b = Url::factory($relative);
+ $this->assertEquals($result, trim((string) $a->combine($b, true), '='));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js
new file mode 100644
index 0000000..4156f1a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js
@@ -0,0 +1,146 @@
+/**
+ * Guzzle node.js test server to return queued responses to HTTP requests and
+ * expose a RESTful API for enqueueing responses and retrieving the requests
+ * that have been received.
+ *
+ * - Delete all requests that have been received:
+ * DELETE /guzzle-server/requests
+ * Host: 127.0.0.1:8124
+ *
+ * - Enqueue responses
+ * PUT /guzzle-server/responses
+ * Host: 127.0.0.1:8124
+ *
+ * [{ "statusCode": 200, "reasonPhrase": "OK", "headers": {}, "body": "" }]
+ *
+ * - Get the received requests
+ * GET /guzzle-server/requests
+ * Host: 127.0.0.1:8124
+ *
+ * - Shutdown the server
+ * DELETE /guzzle-server
+ * Host: 127.0.0.1:8124
+ *
+ * @package Guzzle PHP <http://www.guzzlephp.org>
+ * @license See the LICENSE file that was distributed with this source code.
+ */
+
+var http = require("http");
+
+/**
+ * Guzzle node.js server
+ * @class
+ */
+var GuzzleServer = function(port, log) {
+
+ this.port = port;
+ this.log = log;
+ this.responses = [];
+ this.requests = [];
+ var that = this;
+
+ var controlRequest = function(request, req, res) {
+ if (req.url == '/guzzle-server/perf') {
+ res.writeHead(200, "OK", {"Content-Length": 16});
+ res.end("Body of response");
+ } else if (req.method == "DELETE") {
+ if (req.url == "/guzzle-server/requests") {
+ // Clear the received requests
+ that.requests = [];
+ res.writeHead(200, "OK", { "Content-Length": 0 });
+ res.end();
+ if (this.log) {
+ console.log("Flushing requests");
+ }
+ } else if (req.url == "/guzzle-server") {
+ // Shutdown the server
+ res.writeHead(200, "OK", { "Content-Length": 0, "Connection": "close" });
+ res.end();
+ if (this.log) {
+ console.log("Shutting down");
+ }
+ that.server.close();
+ }
+ } else if (req.method == "GET") {
+ if (req.url === "/guzzle-server/requests") {
+ // Get received requests
+ var data = that.requests.join("\n----[request]\n");
+ res.writeHead(200, "OK", { "Content-Length": data.length });
+ res.end(data);
+ if (that.log) {
+ console.log("Sending receiving requests");
+ }
+ }
+ } else if (req.method == "PUT") {
+ if (req.url == "/guzzle-server/responses") {
+ if (that.log) {
+ console.log("Adding responses...");
+ }
+ // Received response to queue
+ var data = request.split("\r\n\r\n")[1];
+ if (!data) {
+ if (that.log) {
+ console.log("No response data was provided");
+ }
+ res.writeHead(400, "NO RESPONSES IN REQUEST", { "Content-Length": 0 });
+ } else {
+ that.responses = eval("(" + data + ")");
+ if (that.log) {
+ console.log(that.responses);
+ }
+ res.writeHead(200, "OK", { "Content-Length": 0 });
+ }
+ res.end();
+ }
+ }
+ };
+
+ var receivedRequest = function(request, req, res) {
+ if (req.url.indexOf("/guzzle-server") === 0) {
+ controlRequest(request, req, res);
+ } else if (req.url.indexOf("/guzzle-server") == -1 && !that.responses.length) {
+ res.writeHead(500);
+ res.end("No responses in queue");
+ } else {
+ var response = that.responses.shift();
+ res.writeHead(response.statusCode, response.reasonPhrase, response.headers);
+ res.end(response.body);
+ that.requests.push(request);
+ }
+ };
+
+ this.start = function() {
+
+ that.server = http.createServer(function(req, res) {
+
+ var request = req.method + " " + req.url + " HTTP/" + req.httpVersion + "\r\n";
+ for (var i in req.headers) {
+ request += i + ": " + req.headers[i] + "\r\n";
+ }
+ request += "\r\n";
+
+ // Receive each chunk of the request body
+ req.addListener("data", function(chunk) {
+ request += chunk;
+ });
+
+ // Called when the request completes
+ req.addListener("end", function() {
+ receivedRequest(request, req, res);
+ });
+ });
+ that.server.listen(port, "127.0.0.1");
+
+ if (this.log) {
+ console.log("Server running at http://127.0.0.1:8124/");
+ }
+ };
+};
+
+// Get the port from the arguments
+port = process.argv.length >= 3 ? process.argv[2] : 8124;
+log = process.argv.length >= 4 ? process.argv[3] : false;
+
+// Start the server
+server = new GuzzleServer(port, log);
+server.start();
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php
new file mode 100644
index 0000000..990c0af
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Guzzle\Tests\Inflection;
+
+use Guzzle\Inflection\Inflector;
+
+/**
+ * @covers Guzzle\Inflection\Inflector
+ */
+class InflectorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testReturnsDefaultInstance()
+ {
+ $this->assertSame(Inflector::getDefault(), Inflector::getDefault());
+ }
+
+ public function testSnake()
+ {
+ $this->assertEquals('camel_case', Inflector::getDefault()->snake('camelCase'));
+ $this->assertEquals('camel_case', Inflector::getDefault()->snake('CamelCase'));
+ $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCaseWords'));
+ $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCase_words'));
+ $this->assertEquals('test', Inflector::getDefault()->snake('test'));
+ $this->assertEquals('test', Inflector::getDefault()->snake('test'));
+ $this->assertEquals('expect100_continue', Inflector::getDefault()->snake('Expect100Continue'));
+ }
+
+ public function testCamel()
+ {
+ $this->assertEquals('CamelCase', Inflector::getDefault()->camel('camel_case'));
+ $this->assertEquals('CamelCaseWords', Inflector::getDefault()->camel('camel_case_words'));
+ $this->assertEquals('Test', Inflector::getDefault()->camel('test'));
+ $this->assertEquals('Expect100Continue', ucfirst(Inflector::getDefault()->camel('expect100_continue')));
+ // Get from cache
+ $this->assertEquals('Test', Inflector::getDefault()->camel('test', false));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php
new file mode 100644
index 0000000..f00b7fa
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Tests\Inflection;
+
+use Guzzle\Inflection\MemoizingInflector;
+use Guzzle\Inflection\Inflector;
+
+/**
+ * @covers Guzzle\Inflection\MemoizingInflector
+ */
+class MemoizingInflectorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testUsesCache()
+ {
+ $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel'));
+ $mock->expects($this->once())->method('snake')->will($this->returnValue('foo_bar'));
+ $mock->expects($this->once())->method('camel')->will($this->returnValue('FooBar'));
+
+ $inflector = new MemoizingInflector($mock);
+ $this->assertEquals('foo_bar', $inflector->snake('FooBar'));
+ $this->assertEquals('foo_bar', $inflector->snake('FooBar'));
+ $this->assertEquals('FooBar', $inflector->camel('foo_bar'));
+ $this->assertEquals('FooBar', $inflector->camel('foo_bar'));
+ }
+
+ public function testProtectsAgainstCacheOverflow()
+ {
+ $inflector = new MemoizingInflector(new Inflector(), 10);
+ for ($i = 1; $i < 11; $i++) {
+ $inflector->camel('foo_' . $i);
+ $inflector->snake('Foo' . $i);
+ }
+
+ $cache = $this->readAttribute($inflector, 'cache');
+ $this->assertEquals(10, count($cache['snake']));
+ $this->assertEquals(10, count($cache['camel']));
+
+ $inflector->camel('baz!');
+ $inflector->snake('baz!');
+
+ // Now ensure that 20% of the cache was removed (2), then the item was added
+ $cache = $this->readAttribute($inflector, 'cache');
+ $this->assertEquals(9, count($cache['snake']));
+ $this->assertEquals(9, count($cache['camel']));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php
new file mode 100644
index 0000000..ff2654c
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Guzzle\Tests\Inflection;
+
+use Guzzle\Inflection\PreComputedInflector;
+
+/**
+ * @covers Guzzle\Inflection\PreComputedInflector
+ */
+class PreComputedInflectorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testUsesPreComputedHash()
+ {
+ $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel'));
+ $mock->expects($this->once())->method('snake')->with('Test')->will($this->returnValue('test'));
+ $mock->expects($this->once())->method('camel')->with('Test')->will($this->returnValue('Test'));
+ $inflector = new PreComputedInflector($mock, array('FooBar' => 'foo_bar'), array('foo_bar' => 'FooBar'));
+ $this->assertEquals('FooBar', $inflector->camel('foo_bar'));
+ $this->assertEquals('foo_bar', $inflector->snake('FooBar'));
+ $this->assertEquals('Test', $inflector->camel('Test'));
+ $this->assertEquals('test', $inflector->snake('Test'));
+ }
+
+ public function testMirrorsPrecomputedValues()
+ {
+ $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel'));
+ $mock->expects($this->never())->method('snake');
+ $mock->expects($this->never())->method('camel');
+ $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array(), true);
+ $this->assertEquals('Zeep', $inflector->camel('zeep'));
+ $this->assertEquals('zeep', $inflector->snake('Zeep'));
+ }
+
+ public function testMirrorsPrecomputedValuesByMerging()
+ {
+ $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel'));
+ $mock->expects($this->never())->method('snake');
+ $mock->expects($this->never())->method('camel');
+ $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array('foo' => 'Foo'), true);
+ $this->assertEquals('Zeep', $inflector->camel('zeep'));
+ $this->assertEquals('zeep', $inflector->snake('Zeep'));
+ $this->assertEquals('Foo', $inflector->camel('foo'));
+ $this->assertEquals('foo', $inflector->snake('Foo'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php
new file mode 100644
index 0000000..8d6ae84
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Guzzle\Tests\Iterator;
+
+use Guzzle\Iterator\AppendIterator;
+
+/**
+ * @covers Guzzle\Iterator\AppendIterator
+ */
+class AppendIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testTraversesIteratorsInOrder()
+ {
+ $a = new \ArrayIterator(array(
+ 'a' => 1,
+ 'b' => 2
+ ));
+ $b = new \ArrayIterator(array());
+ $c = new \ArrayIterator(array(
+ 'c' => 3,
+ 'd' => 4
+ ));
+ $i = new AppendIterator();
+ $i->append($a);
+ $i->append($b);
+ $i->append($c);
+ $this->assertEquals(array(1, 2, 3, 4), iterator_to_array($i, false));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php
new file mode 100644
index 0000000..ec4c129
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Guzzle\Tests\Iterator;
+
+use Guzzle\Iterator\ChunkedIterator;
+
+/**
+ * @covers Guzzle\Iterator\ChunkedIterator
+ */
+class ChunkedIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testChunksIterator()
+ {
+ $chunked = new ChunkedIterator(new \ArrayIterator(range(0, 100)), 10);
+ $chunks = iterator_to_array($chunked, false);
+ $this->assertEquals(11, count($chunks));
+ foreach ($chunks as $j => $chunk) {
+ $this->assertEquals(range($j * 10, min(100, $j * 10 + 9)), $chunk);
+ }
+ }
+
+ public function testChunksIteratorWithOddValues()
+ {
+ $chunked = new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2);
+ $chunks = iterator_to_array($chunked, false);
+ $this->assertEquals(3, count($chunks));
+ $this->assertEquals(array(1, 2), $chunks[0]);
+ $this->assertEquals(array(3, 4), $chunks[1]);
+ $this->assertEquals(array(5), $chunks[2]);
+ }
+
+ public function testMustNotTerminateWithTraversable()
+ {
+ $traversable = simplexml_load_string('<root><foo/><foo/><foo/></root>')->foo;
+ $chunked = new ChunkedIterator($traversable, 2);
+ $actual = iterator_to_array($chunked, false);
+ $this->assertCount(2, $actual);
+ }
+
+ public function testSizeOfZeroMakesIteratorInvalid() {
+ $chunked = new ChunkedIterator(new \ArrayIterator(range(1, 5)), 0);
+ $chunked->rewind();
+ $this->assertFalse($chunked->valid());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSizeLowerZeroThrowsException() {
+ new ChunkedIterator(new \ArrayIterator(range(1, 5)), -1);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php
new file mode 100644
index 0000000..73b4f69
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Guzzle\Tests\Iterator;
+
+use Guzzle\Iterator\FilterIterator;
+
+/**
+ * @covers Guzzle\Iterator\FilterIterator
+ */
+class FilterIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testFiltersValues()
+ {
+ $i = new FilterIterator(new \ArrayIterator(range(0, 100)), function ($value) {
+ return $value % 2;
+ });
+
+ $this->assertEquals(range(1, 99, 2), iterator_to_array($i, false));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesCallable()
+ {
+ $i = new FilterIterator(new \ArrayIterator(), new \stdClass());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php
new file mode 100644
index 0000000..4de4a6b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Guzzle\Tests\Iterator;
+
+use Guzzle\Iterator\MapIterator;
+
+/**
+ * @covers Guzzle\Iterator\MapIterator
+ */
+class MapIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testFiltersValues()
+ {
+ $i = new MapIterator(new \ArrayIterator(range(0, 100)), function ($value) {
+ return $value * 10;
+ });
+
+ $this->assertEquals(range(0, 1000, 10), iterator_to_array($i, false));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesCallable()
+ {
+ $i = new MapIterator(new \ArrayIterator(), new \stdClass());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php
new file mode 100644
index 0000000..5bcf06f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Guzzle\Tests\Iterator;
+
+use Guzzle\Iterator\MethodProxyIterator;
+use Guzzle\Iterator\ChunkedIterator;
+
+/**
+ * @covers Guzzle\Iterator\MethodProxyIterator
+ */
+class MethodProxyIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testProxiesMagicCallsToInnermostIterator()
+ {
+ $i = new \ArrayIterator();
+ $proxy = new MethodProxyIterator(new MethodProxyIterator(new MethodProxyIterator($i)));
+ $proxy->append('a');
+ $proxy->append('b');
+ $this->assertEquals(array('a', 'b'), $i->getArrayCopy());
+ $this->assertEquals(array('a', 'b'), $proxy->getArrayCopy());
+ }
+
+ public function testUsesInnerIterator()
+ {
+ $i = new MethodProxyIterator(new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2));
+ $this->assertEquals(3, count(iterator_to_array($i, false)));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php
new file mode 100644
index 0000000..a66882f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Tests\Log;
+
+use Guzzle\Log\ArrayLogAdapter;
+
+class ArrayLogAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testLog()
+ {
+ $adapter = new ArrayLogAdapter();
+ $adapter->log('test', \LOG_NOTICE, '127.0.0.1');
+ $this->assertEquals(array(array('message' => 'test', 'priority' => \LOG_NOTICE, 'extras' => '127.0.0.1')), $adapter->getLogs());
+ }
+
+ public function testClearLog()
+ {
+ $adapter = new ArrayLogAdapter();
+ $adapter->log('test', \LOG_NOTICE, '127.0.0.1');
+ $adapter->clearLogs();
+ $this->assertEquals(array(), $adapter->getLogs());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php
new file mode 100644
index 0000000..0177dc0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Tests\Log;
+
+use Guzzle\Log\ClosureLogAdapter;
+
+/**
+ * @covers Guzzle\Log\ClosureLogAdapter
+ */
+class ClosureLogAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testClosure()
+ {
+ $that = $this;
+ $modified = null;
+ $this->adapter = new ClosureLogAdapter(function($message, $priority, $extras = null) use ($that, &$modified) {
+ $modified = array($message, $priority, $extras);
+ });
+ $this->adapter->log('test', LOG_NOTICE, '127.0.0.1');
+ $this->assertEquals(array('test', LOG_NOTICE, '127.0.0.1'), $modified);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowsExceptionWhenNotCallable()
+ {
+ $this->adapter = new ClosureLogAdapter(123);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php
new file mode 100644
index 0000000..3ff4b07
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Guzzle\Tests\Log;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Response;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+use Guzzle\Log\ClosureLogAdapter;
+
+/**
+ * @covers Guzzle\Log\MessageFormatter
+ */
+class MessageFormatterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $request;
+ protected $response;
+ protected $handle;
+
+ public function setUp()
+ {
+ $this->request = new EntityEnclosingRequest('POST', 'http://foo.com?q=test', array(
+ 'X-Foo' => 'bar',
+ 'Authorization' => 'Baz'
+ ));
+ $this->request->setBody(EntityBody::factory('Hello'));
+
+ $this->response = new Response(200, array(
+ 'X-Test' => 'Abc'
+ ), 'Foo');
+
+ $this->handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle')
+ ->disableOriginalConstructor()
+ ->setMethods(array('getError', 'getErrorNo', 'getStderr', 'getInfo'))
+ ->getMock();
+
+ $this->handle->expects($this->any())
+ ->method('getError')
+ ->will($this->returnValue('e'));
+
+ $this->handle->expects($this->any())
+ ->method('getErrorNo')
+ ->will($this->returnValue('123'));
+
+ $this->handle->expects($this->any())
+ ->method('getStderr')
+ ->will($this->returnValue('testing'));
+
+ $this->handle->expects($this->any())
+ ->method('getInfo')
+ ->will($this->returnValueMap(array(
+ array(CURLINFO_CONNECT_TIME, '123'),
+ array(CURLINFO_TOTAL_TIME, '456')
+ )));
+ }
+
+ public function logProvider()
+ {
+ return array(
+ // Uses the cache for the second time
+ array('{method} - {method}', 'POST - POST'),
+ array('{url}', 'http://foo.com?q=test'),
+ array('{port}', '80'),
+ array('{resource}', '/?q=test'),
+ array('{host}', 'foo.com'),
+ array('{hostname}', gethostname()),
+ array('{protocol}/{version}', 'HTTP/1.1'),
+ array('{code} {phrase}', '200 OK'),
+ array('{req_header_Foo}', ''),
+ array('{req_header_X-Foo}', 'bar'),
+ array('{req_header_Authorization}', 'Baz'),
+ array('{res_header_foo}', ''),
+ array('{res_header_X-Test}', 'Abc'),
+ array('{req_body}', 'Hello'),
+ array('{res_body}', 'Foo'),
+ array('{curl_stderr}', 'testing'),
+ array('{curl_error}', 'e'),
+ array('{curl_code}', '123'),
+ array('{connect_time}', '123'),
+ array('{total_time}', '456')
+ );
+ }
+
+ /**
+ * @dataProvider logProvider
+ */
+ public function testFormatsMessages($template, $output)
+ {
+ $formatter = new MessageFormatter($template);
+ $this->assertEquals($output, $formatter->format($this->request, $this->response, $this->handle));
+ }
+
+ public function testFormatsRequestsAndResponses()
+ {
+ $formatter = new MessageFormatter();
+ $formatter->setTemplate('{request}{response}');
+ $this->assertEquals($this->request . $this->response, $formatter->format($this->request, $this->response));
+ }
+
+ public function testAddsTimestamp()
+ {
+ $formatter = new MessageFormatter('{ts}');
+ $this->assertNotEmpty($formatter->format($this->request, $this->response));
+ }
+
+ public function testUsesResponseWhenNoHandleAndGettingCurlInformation()
+ {
+ $formatter = new MessageFormatter('{connect_time}/{total_time}');
+ $response = $this->getMockBuilder('Guzzle\Http\Message\Response')
+ ->setConstructorArgs(array(200))
+ ->setMethods(array('getInfo'))
+ ->getMock();
+ $response->expects($this->exactly(2))
+ ->method('getInfo')
+ ->will($this->returnValueMap(array(
+ array('connect_time', '1'),
+ array('total_time', '2'),
+ )));
+ $this->assertEquals('1/2', $formatter->format($this->request, $response));
+ }
+
+ public function testUsesEmptyStringWhenNoHandleAndNoResponse()
+ {
+ $formatter = new MessageFormatter('{connect_time}/{total_time}');
+ $this->assertEquals('/', $formatter->format($this->request));
+ }
+
+ public function testInjectsTotalTime()
+ {
+ $out = '';
+ $formatter = new MessageFormatter('{connect_time}/{total_time}');
+ $adapter = new ClosureLogAdapter(function ($m) use (&$out) { $out .= $m; });
+ $log = new LogPlugin($adapter, $formatter);
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHI");
+ $client = new Client($this->getServer()->getUrl());
+ $client->addSubscriber($log);
+ $client->get('/')->send();
+ $this->assertNotEquals('/', $out);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php
new file mode 100644
index 0000000..7b72dd6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Guzzle\Tests\Log;
+
+use Guzzle\Log\PsrLogAdapter;
+use Monolog\Logger;
+use Monolog\Handler\TestHandler;
+
+/**
+ * @covers Guzzle\Log\PsrLogAdapter
+ * @covers Guzzle\Log\AbstractLogAdapter
+ */
+class PsrLogAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testLogsMessagesToAdaptedObject()
+ {
+ $log = new Logger('test');
+ $handler = new TestHandler();
+ $log->pushHandler($handler);
+ $adapter = new PsrLogAdapter($log);
+ $adapter->log('test!', LOG_INFO);
+ $this->assertTrue($handler->hasInfoRecords());
+ $this->assertSame($log, $adapter->getLogObject());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php
new file mode 100644
index 0000000..1b61283
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Guzzle\Tests\Log;
+
+use Guzzle\Log\Zf2LogAdapter;
+use Zend\Log\Logger;
+use Zend\Log\Writer\Stream;
+
+/**
+ * @covers Guzzle\Log\Zf2LogAdapter
+ */
+class Zf2LogAdapterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Zf2LogAdapter */
+ protected $adapter;
+
+ /** @var Logger */
+ protected $log;
+
+ /** @var resource */
+ protected $stream;
+
+ protected function setUp()
+ {
+ $this->stream = fopen('php://temp', 'r+');
+ $this->log = new Logger();
+ $this->log->addWriter(new Stream($this->stream));
+ $this->adapter = new Zf2LogAdapter($this->log);
+
+ }
+
+ public function testLogsMessagesToAdaptedObject()
+ {
+ // Test without a priority
+ $this->adapter->log('Zend_Test!', \LOG_NOTICE);
+ rewind($this->stream);
+ $contents = stream_get_contents($this->stream);
+ $this->assertEquals(1, substr_count($contents, 'Zend_Test!'));
+
+ // Test with a priority
+ $this->adapter->log('Zend_Test!', \LOG_ALERT);
+ rewind($this->stream);
+ $contents = stream_get_contents($this->stream);
+ $this->assertEquals(2, substr_count($contents, 'Zend_Test!'));
+ }
+
+ public function testExposesAdaptedLogObject()
+ {
+ $this->assertEquals($this->log, $this->adapter->getLogObject());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php
new file mode 100644
index 0000000..3fb6527
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+use Guzzle\Service\Command\ResponseClassInterface;
+use Guzzle\Service\Command\OperationCommand;
+
+class CustomResponseModel implements ResponseClassInterface
+{
+ public $command;
+
+ public static function fromCommand(OperationCommand $command)
+ {
+ return new self($command);
+ }
+
+ public function __construct($command)
+ {
+ $this->command = $command;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php
new file mode 100644
index 0000000..aabb15f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+use Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface;
+use Guzzle\Service\Command\CommandInterface;
+use Guzzle\Http\Message\Response;
+
+class ErrorResponseMock extends \Exception implements ErrorResponseExceptionInterface
+{
+ public $command;
+ public $response;
+
+ public static function fromCommand(CommandInterface $command, Response $response)
+ {
+ return new self($command, $response);
+ }
+
+ public function __construct($command, $response)
+ {
+ $this->command = $command;
+ $this->response = $response;
+ $this->message = 'Error from ' . $response;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php
new file mode 100644
index 0000000..97a1974
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+class ExceptionMock
+{
+ public function __construct()
+ {
+ throw new \Exception('Oh no!');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockMulti.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockMulti.php
new file mode 100644
index 0000000..b4d6b82
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockMulti.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+class MockMulti extends \Guzzle\Http\Curl\CurlMulti
+{
+ public function getHandle()
+ {
+ return $this->multiHandle;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php
new file mode 100644
index 0000000..11e22eb
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+use Guzzle\Common\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class MockObserver implements \Countable, EventSubscriberInterface
+{
+ public $events = array();
+
+ public static function getSubscribedEvents()
+ {
+ return array();
+ }
+
+ public function has($eventName)
+ {
+ foreach ($this->events as $event) {
+ if ($event->getName() == $eventName) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function getLastEvent()
+ {
+ return end($this->events);
+ }
+
+ public function count()
+ {
+ return count($this->events);
+ }
+
+ public function getGrouped()
+ {
+ $events = array();
+ foreach ($this->events as $event) {
+ if (!isset($events[$event->getName()])) {
+ $events[$event->getName()] = array();
+ }
+ $events[$event->getName()][] = $event;
+ }
+
+ return $events;
+ }
+
+ public function getData($event, $key, $occurrence = 0)
+ {
+ $grouped = $this->getGrouped();
+ if (isset($grouped[$event])) {
+ return $grouped[$event][$occurrence][$key];
+ }
+
+ return null;
+ }
+
+ public function update(Event $event)
+ {
+ $this->events[] = $event;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php
new file mode 100644
index 0000000..e011959
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Tests\Mock;
+
+class MockSubject extends \Guzzle\Common\Event\AbstractSubject
+{
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserProvider.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserProvider.php
new file mode 100644
index 0000000..86d43c0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserProvider.php
@@ -0,0 +1,381 @@
+<?php
+
+namespace Guzzle\Tests\Parser\Cookie;
+
+use Guzzle\Http\Url;
+
+/**
+ * @covers Guzzle\Parser\Cookie\CookieParser
+ */
+class CookieParserProvider extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * Provides the parsed information from a cookie
+ *
+ * @return array
+ */
+ public function cookieParserDataProvider()
+ {
+ return array(
+ array(
+ 'ASIHTTPRequestTestCookie=This+is+the+value; expires=Sat, 26-Jul-2008 17:00:42 GMT; path=/tests; domain=allseeing-i.com; PHPSESSID=6c951590e7a9359bcedde25cda73e43c; path=/";',
+ array(
+ 'domain' => 'allseeing-i.com',
+ 'path' => '/',
+ 'data' => array(
+ 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c'
+ ),
+ 'max_age' => NULL,
+ 'expires' => 'Sat, 26-Jul-2008 17:00:42 GMT',
+ 'version' => NULL,
+ 'secure' => NULL,
+ 'discard' => NULL,
+ 'port' => NULL,
+ 'cookies' => array(
+ 'ASIHTTPRequestTestCookie' => 'This+is+the+value'
+ ),
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array('', false),
+ array('foo', false),
+ // Test setting a blank value for a cookie
+ array(array(
+ 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= '),
+ array(
+ 'cookies' => array(
+ 'foo' => ''
+ ),
+ 'data' => array(),
+ 'discard' => null,
+ 'domain' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ // Test setting a value and removing quotes
+ array(array(
+ 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', 'foo="1"', 'foo="1";', 'foo= "1";'),
+ array(
+ 'cookies' => array(
+ 'foo' => '1'
+ ),
+ 'data' => array(),
+ 'discard' => null,
+ 'domain' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ // Test setting multiple values
+ array(array(
+ 'foo=1; bar=2;', 'foo =1; bar = "2"', 'foo=1; bar=2'),
+ array(
+ 'cookies' => array(
+ 'foo' => '1',
+ 'bar' => '2',
+ ),
+ 'data' => array(),
+ 'discard' => null,
+ 'domain' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ // Tests getting the domain and path from a reference request
+ array(array(
+ 'foo=1; port="80,8081"; httponly', 'foo=1; port="80,8081"; domain=www.test.com; HttpOnly;', 'foo=1; ; domain=www.test.com; path=/path; port="80,8081"; HttpOnly;'),
+ array(
+ 'cookies' => array(
+ 'foo' => 1
+ ),
+ 'data' => array(),
+ 'discard' => null,
+ 'domain' => 'www.test.com',
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/path',
+ 'port' => array('80', '8081'),
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => true
+ ),
+ 'http://www.test.com/path/'
+ ),
+ // Some of the following tests are based on http://framework.zend.com/svn/framework/standard/trunk/tests/Zend/Http/CookieTest.php
+ array(
+ 'justacookie=foo; domain=example.com',
+ array(
+ 'cookies' => array(
+ 'justacookie' => 'foo'
+ ),
+ 'domain' => 'example.com',
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array(
+ 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com',
+ array(
+ 'cookies' => array(
+ 'expires' => 'tomorrow'
+ ),
+ 'domain' => '.example.com',
+ 'path' => '/Space Out/',
+ 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
+ 'data' => array(),
+ 'discard' => null,
+ 'port' => null,
+ 'secure' => true,
+ 'version' => null,
+ 'max_age' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array(
+ 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/',
+ array(
+ 'cookies' => array(
+ 'domain' => 'unittests'
+ ),
+ 'domain' => 'example.com',
+ 'path' => '/some value/',
+ 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
+ 'secure' => false,
+ 'data' => array(),
+ 'discard' => null,
+ 'max_age' => null,
+ 'port' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array(
+ 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT',
+ array(
+ 'cookies' => array(
+ 'path' => 'indexAction'
+ ),
+ 'domain' => '.foo.com',
+ 'path' => '/',
+ 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
+ 'secure' => false,
+ 'data' => array(),
+ 'discard' => null,
+ 'max_age' => null,
+ 'port' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array(
+ 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400',
+ array(
+ 'cookies' => array(
+ 'secure' => 'sha1'
+ ),
+ 'domain' => 'some.really.deep.domain.com',
+ 'path' => '/',
+ 'secure' => true,
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => time() + 86400,
+ 'max_age' => 86400,
+ 'port' => null,
+ 'version' => 1,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ array(
+ 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;',
+ array(
+ 'cookies' => array(
+ 'PHPSESSID' => '123456789+abcd%2Cef'
+ ),
+ 'domain' => '.localdomain',
+ 'path' => '/foo/baz',
+ 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
+ 'secure' => true,
+ 'data' => array(),
+ 'discard' => true,
+ 'max_age' => null,
+ 'port' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ )
+ ),
+ // rfc6265#section-5.1.4
+ array(
+ 'cookie=value',
+ array(
+ 'cookies' => array(
+ 'cookie' => 'value'
+ ),
+ 'domain' => 'example.com',
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/some/path',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ ),
+ 'http://example.com/some/path/test.html'
+ ),
+ array(
+ 'empty=path',
+ array(
+ 'cookies' => array(
+ 'empty' => 'path'
+ ),
+ 'domain' => 'example.com',
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ ),
+ 'http://example.com/test.html'
+ ),
+ array(
+ 'baz=qux',
+ array(
+ 'cookies' => array(
+ 'baz' => 'qux'
+ ),
+ 'domain' => 'example.com',
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ ),
+ 'http://example.com?query=here'
+ ),
+ array(
+ 'test=noSlashPath; path=someString',
+ array(
+ 'cookies' => array(
+ 'test' => 'noSlashPath'
+ ),
+ 'domain' => 'example.com',
+ 'data' => array(),
+ 'discard' => null,
+ 'expires' => null,
+ 'max_age' => null,
+ 'path' => '/real/path',
+ 'port' => null,
+ 'secure' => null,
+ 'version' => null,
+ 'comment' => null,
+ 'comment_url' => null,
+ 'http_only' => false
+ ),
+ 'http://example.com/real/path/'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider cookieParserDataProvider
+ */
+ public function testParseCookie($cookie, $parsed, $url = null)
+ {
+ $c = $this->cookieParserClass;
+ $parser = new $c();
+
+ $request = null;
+ if ($url) {
+ $url = Url::factory($url);
+ $host = $url->getHost();
+ $path = $url->getPath();
+ } else {
+ $host = '';
+ $path = '';
+ }
+
+ foreach ((array) $cookie as $c) {
+ $p = $parser->parseCookie($c, $host, $path);
+
+ // Remove expires values from the assertion if they are relatively equal by allowing a 5 minute difference
+ if ($p['expires'] != $parsed['expires']) {
+ if (abs($p['expires'] - $parsed['expires']) < 300) {
+ unset($p['expires']);
+ unset($parsed['expires']);
+ }
+ }
+
+ if (is_array($parsed)) {
+ foreach ($parsed as $key => $value) {
+ $this->assertEquals($parsed[$key], $p[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true));
+ }
+
+ foreach ($p as $key => $value) {
+ $this->assertEquals($p[$key], $parsed[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true));
+ }
+ } else {
+ $this->assertEquals($parsed, $p);
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php
new file mode 100644
index 0000000..75d336f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Guzzle\Tests\Parser\Cookie;
+
+use Guzzle\Parser\Cookie\CookieParser;
+
+/**
+ * @covers Guzzle\Parser\Cookie\CookieParser
+ */
+class CookieParserTest extends CookieParserProvider
+{
+ protected $cookieParserClass = 'Guzzle\Parser\Cookie\CookieParser';
+
+ public function testUrlDecodesCookies()
+ {
+ $parser = new CookieParser();
+ $result = $parser->parseCookie('foo=baz+bar', null, null, true);
+ $this->assertEquals(array(
+ 'foo' => 'baz bar'
+ ), $result['cookies']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php
new file mode 100644
index 0000000..da58bb4
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php
@@ -0,0 +1,225 @@
+<?php
+
+namespace Guzzle\Tests\Parser\Message;
+
+class MessageParserProvider extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function requestProvider()
+ {
+ $auth = base64_encode('michael:foo');
+
+ return array(
+
+ // Empty request
+ array('', false),
+
+ // Converts casing of request. Does not require host header.
+ array("GET / HTTP/1.1\r\n\r\n", array(
+ 'method' => 'GET',
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'request_url' => array(
+ 'scheme' => 'http',
+ 'host' => '',
+ 'port' => '',
+ 'path' => '/',
+ 'query' => ''
+ ),
+ 'headers' => array(),
+ 'body' => ''
+ )),
+ // Path and query string, multiple header values per header and case sensitive storage
+ array("HEAD /path?query=foo HTTP/1.0\r\nHost: example.com\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\nX-Foo: Baz\r\n\r\n", array(
+ 'method' => 'HEAD',
+ 'protocol' => 'HTTP',
+ 'version' => '1.0',
+ 'request_url' => array(
+ 'scheme' => 'http',
+ 'host' => 'example.com',
+ 'port' => '',
+ 'path' => '/path',
+ 'query' => 'query=foo'
+ ),
+ 'headers' => array(
+ 'Host' => 'example.com',
+ 'X-Foo' => array('foo', 'foo', 'Baz'),
+ 'x-foo' => 'Bar'
+ ),
+ 'body' => ''
+ )),
+ // Includes a body
+ array("PUT / HTTP/1.0\r\nhost: example.com:443\r\nContent-Length: 4\r\n\r\ntest", array(
+ 'method' => 'PUT',
+ 'protocol' => 'HTTP',
+ 'version' => '1.0',
+ 'request_url' => array(
+ 'scheme' => 'https',
+ 'host' => 'example.com',
+ 'port' => '443',
+ 'path' => '/',
+ 'query' => ''
+ ),
+ 'headers' => array(
+ 'host' => 'example.com:443',
+ 'Content-Length' => '4'
+ ),
+ 'body' => 'test'
+ )),
+ // Includes Authorization headers
+ array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nAuthorization: Basic {$auth}\r\n\r\n", array(
+ 'method' => 'GET',
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'request_url' => array(
+ 'scheme' => 'http',
+ 'host' => 'example.com',
+ 'port' => '8080',
+ 'path' => '/',
+ 'query' => ''
+ ),
+ 'headers' => array(
+ 'Host' => 'example.com:8080',
+ 'Authorization' => "Basic {$auth}"
+ ),
+ 'body' => ''
+ )),
+ // Include authorization header
+ array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nauthorization: Basic {$auth}\r\n\r\n", array(
+ 'method' => 'GET',
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'request_url' => array(
+ 'scheme' => 'http',
+ 'host' => 'example.com',
+ 'port' => '8080',
+ 'path' => '/',
+ 'query' => ''
+ ),
+ 'headers' => array(
+ 'Host' => 'example.com:8080',
+ 'authorization' => "Basic {$auth}"
+ ),
+ 'body' => ''
+ )),
+ );
+ }
+
+ public function responseProvider()
+ {
+ return array(
+ // Empty request
+ array('', false),
+
+ array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", array(
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'code' => '200',
+ 'reason_phrase' => 'OK',
+ 'headers' => array(
+ 'Content-Length' => 0
+ ),
+ 'body' => ''
+ )),
+ array("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n", array(
+ 'protocol' => 'HTTP',
+ 'version' => '1.0',
+ 'code' => '400',
+ 'reason_phrase' => 'Bad Request',
+ 'headers' => array(
+ 'Content-Length' => 0
+ ),
+ 'body' => ''
+ )),
+ array("HTTP/1.0 100 Continue\r\n\r\n", array(
+ 'protocol' => 'HTTP',
+ 'version' => '1.0',
+ 'code' => '100',
+ 'reason_phrase' => 'Continue',
+ 'headers' => array(),
+ 'body' => ''
+ )),
+ array("HTTP/1.1 204 No Content\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\n\r\n", array(
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'code' => '204',
+ 'reason_phrase' => 'No Content',
+ 'headers' => array(
+ 'X-Foo' => array('foo', 'foo'),
+ 'x-foo' => 'Bar'
+ ),
+ 'body' => ''
+ )),
+ array("HTTP/1.1 200 Ok that is great!\r\nContent-Length: 4\r\n\r\nTest", array(
+ 'protocol' => 'HTTP',
+ 'version' => '1.1',
+ 'code' => '200',
+ 'reason_phrase' => 'Ok that is great!',
+ 'headers' => array(
+ 'Content-Length' => 4
+ ),
+ 'body' => 'Test'
+ )),
+ );
+ }
+
+ public function compareRequestResults($result, $expected)
+ {
+ if (!$result) {
+ $this->assertFalse($expected);
+ return;
+ }
+
+ $this->assertEquals($result['method'], $expected['method']);
+ $this->assertEquals($result['protocol'], $expected['protocol']);
+ $this->assertEquals($result['version'], $expected['version']);
+ $this->assertEquals($result['request_url'], $expected['request_url']);
+ $this->assertEquals($result['body'], $expected['body']);
+ $this->compareHttpHeaders($result['headers'], $expected['headers']);
+ }
+
+ public function compareResponseResults($result, $expected)
+ {
+ if (!$result) {
+ $this->assertFalse($expected);
+ return;
+ }
+
+ $this->assertEquals($result['protocol'], $expected['protocol']);
+ $this->assertEquals($result['version'], $expected['version']);
+ $this->assertEquals($result['code'], $expected['code']);
+ $this->assertEquals($result['reason_phrase'], $expected['reason_phrase']);
+ $this->assertEquals($result['body'], $expected['body']);
+ $this->compareHttpHeaders($result['headers'], $expected['headers']);
+ }
+
+ protected function normalizeHeaders($headers)
+ {
+ $normalized = array();
+ foreach ($headers as $key => $value) {
+ $key = strtolower($key);
+ if (!isset($normalized[$key])) {
+ $normalized[$key] = $value;
+ } elseif (!is_array($normalized[$key])) {
+ $normalized[$key] = array($value);
+ } else {
+ $normalized[$key][] = $value;
+ }
+ }
+
+ foreach ($normalized as $key => &$value) {
+ if (is_array($value)) {
+ sort($value);
+ }
+ }
+
+ return $normalized;
+ }
+
+ public function compareHttpHeaders($result, $expected)
+ {
+ // Aggregate all headers case-insensitively
+ $result = $this->normalizeHeaders($result);
+ $expected = $this->normalizeHeaders($expected);
+ $this->assertEquals($result, $expected);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php
new file mode 100644
index 0000000..2f52228
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Guzzle\Tests\Parser\Message;
+
+use Guzzle\Parser\Message\MessageParser;
+
+/**
+ * @covers Guzzle\Parser\Message\AbstractMessageParser
+ * @covers Guzzle\Parser\Message\MessageParser
+ */
+class MessageParserTest extends MessageParserProvider
+{
+ /**
+ * @dataProvider requestProvider
+ */
+ public function testParsesRequests($message, $parts)
+ {
+ $parser = new MessageParser();
+ $this->compareRequestResults($parts, $parser->parseRequest($message));
+ }
+
+ /**
+ * @dataProvider responseProvider
+ */
+ public function testParsesResponses($message, $parts)
+ {
+ $parser = new MessageParser();
+ $this->compareResponseResults($parts, $parser->parseResponse($message));
+ }
+
+ public function testParsesRequestsWithMissingProtocol()
+ {
+ $parser = new MessageParser();
+ $parts = $parser->parseRequest("GET /\r\nHost: Foo.com\r\n\r\n");
+ $this->assertEquals('GET', $parts['method']);
+ $this->assertEquals('HTTP', $parts['protocol']);
+ $this->assertEquals('1.1', $parts['version']);
+ }
+
+ public function testParsesRequestsWithMissingVersion()
+ {
+ $parser = new MessageParser();
+ $parts = $parser->parseRequest("GET / HTTP\r\nHost: Foo.com\r\n\r\n");
+ $this->assertEquals('GET', $parts['method']);
+ $this->assertEquals('HTTP', $parts['protocol']);
+ $this->assertEquals('1.1', $parts['version']);
+ }
+
+ public function testParsesResponsesWithMissingReasonPhrase()
+ {
+ $parser = new MessageParser();
+ $parts = $parser->parseResponse("HTTP/1.1 200\r\n\r\n");
+ $this->assertEquals('200', $parts['code']);
+ $this->assertEquals('', $parts['reason_phrase']);
+ $this->assertEquals('HTTP', $parts['protocol']);
+ $this->assertEquals('1.1', $parts['version']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php
new file mode 100644
index 0000000..6706e20
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Tests\Parser\Message;
+
+use Guzzle\Parser\Message\PeclHttpMessageParser;
+
+/**
+ * @covers Guzzle\Parser\Message\PeclHttpMessageParser
+ */
+class PeclHttpMessageParserTest extends MessageParserProvider
+{
+ protected function setUp()
+ {
+ if (!function_exists('http_parse_message')) {
+ $this->markTestSkipped('pecl_http is not available.');
+ }
+ }
+
+ /**
+ * @dataProvider requestProvider
+ */
+ public function testParsesRequests($message, $parts)
+ {
+ $parser = new PeclHttpMessageParser();
+ $this->compareRequestResults($parts, $parser->parseRequest($message));
+ }
+
+ /**
+ * @dataProvider responseProvider
+ */
+ public function testParsesResponses($message, $parts)
+ {
+ $parser = new PeclHttpMessageParser();
+ $this->compareResponseResults($parts, $parser->parseResponse($message));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php
new file mode 100644
index 0000000..7675efb
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Guzzle\Tests\Parser;
+
+use Guzzle\Parser\ParserRegistry;
+
+/**
+ * @covers Guzzle\Parser\ParserRegistry
+ */
+class ParserRegistryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testStoresObjects()
+ {
+ $r = new ParserRegistry();
+ $c = new \stdClass();
+ $r->registerParser('foo', $c);
+ $this->assertSame($c, $r->getParser('foo'));
+ }
+
+ public function testReturnsNullWhenNotFound()
+ {
+ $r = new ParserRegistry();
+ $this->assertNull($r->getParser('FOO'));
+ }
+
+ public function testReturnsLazyLoadedDefault()
+ {
+ $r = new ParserRegistry();
+ $c = $r->getParser('cookie');
+ $this->assertInstanceOf('Guzzle\Parser\Cookie\CookieParser', $c);
+ $this->assertSame($c, $r->getParser('cookie'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php
new file mode 100644
index 0000000..a05fc2e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Guzzle\Tests\Parsers\UriTemplate;
+
+abstract class AbstractUriTemplateTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @return array
+ */
+ public function templateProvider()
+ {
+ $t = array();
+ $params = array(
+ 'var' => 'value',
+ 'hello' => 'Hello World!',
+ 'empty' => '',
+ 'path' => '/foo/bar',
+ 'x' => '1024',
+ 'y' => '768',
+ 'null' => null,
+ 'list' => array('red', 'green', 'blue'),
+ 'keys' => array(
+ "semi" => ';',
+ "dot" => '.',
+ "comma" => ','
+ ),
+ 'empty_keys' => array(),
+ );
+
+ return array_map(function($t) use ($params) {
+ $t[] = $params;
+ return $t;
+ }, array(
+ array('foo', 'foo'),
+ array('{var}', 'value'),
+ array('{hello}', 'Hello%20World%21'),
+ array('{+var}', 'value'),
+ array('{+hello}', 'Hello%20World!'),
+ array('{+path}/here', '/foo/bar/here'),
+ array('here?ref={+path}', 'here?ref=/foo/bar'),
+ array('X{#var}', 'X#value'),
+ array('X{#hello}', 'X#Hello%20World!'),
+ array('map?{x,y}', 'map?1024,768'),
+ array('{x,hello,y}', '1024,Hello%20World%21,768'),
+ array('{+x,hello,y}', '1024,Hello%20World!,768'),
+ array('{+path,x}/here', '/foo/bar,1024/here'),
+ array('{#x,hello,y}', '#1024,Hello%20World!,768'),
+ array('{#path,x}/here', '#/foo/bar,1024/here'),
+ array('X{.var}', 'X.value'),
+ array('X{.x,y}', 'X.1024.768'),
+ array('{/var}', '/value'),
+ array('{/var,x}/here', '/value/1024/here'),
+ array('{;x,y}', ';x=1024;y=768'),
+ array('{;x,y,empty}', ';x=1024;y=768;empty'),
+ array('{?x,y}', '?x=1024&y=768'),
+ array('{?x,y,empty}', '?x=1024&y=768&empty='),
+ array('?fixed=yes{&x}', '?fixed=yes&x=1024'),
+ array('{&x,y,empty}', '&x=1024&y=768&empty='),
+ array('{var:3}', 'val'),
+ array('{var:30}', 'value'),
+ array('{list}', 'red,green,blue'),
+ array('{list*}', 'red,green,blue'),
+ array('{keys}', 'semi,%3B,dot,.,comma,%2C'),
+ array('{keys*}', 'semi=%3B,dot=.,comma=%2C'),
+ array('{+path:6}/here', '/foo/b/here'),
+ array('{+list}', 'red,green,blue'),
+ array('{+list*}', 'red,green,blue'),
+ array('{+keys}', 'semi,;,dot,.,comma,,'),
+ array('{+keys*}', 'semi=;,dot=.,comma=,'),
+ array('{#path:6}/here', '#/foo/b/here'),
+ array('{#list}', '#red,green,blue'),
+ array('{#list*}', '#red,green,blue'),
+ array('{#keys}', '#semi,;,dot,.,comma,,'),
+ array('{#keys*}', '#semi=;,dot=.,comma=,'),
+ array('X{.var:3}', 'X.val'),
+ array('X{.list}', 'X.red,green,blue'),
+ array('X{.list*}', 'X.red.green.blue'),
+ array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'),
+ array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'),
+ array('{/var:1,var}', '/v/value'),
+ array('{/list}', '/red,green,blue'),
+ array('{/list*}', '/red/green/blue'),
+ array('{/list*,path:4}', '/red/green/blue/%2Ffoo'),
+ array('{/keys}', '/semi,%3B,dot,.,comma,%2C'),
+ array('{/keys*}', '/semi=%3B/dot=./comma=%2C'),
+ array('{;hello:5}', ';hello=Hello'),
+ array('{;list}', ';list=red,green,blue'),
+ array('{;list*}', ';list=red;list=green;list=blue'),
+ array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'),
+ array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'),
+ array('{?var:3}', '?var=val'),
+ array('{?list}', '?list=red,green,blue'),
+ array('{?list*}', '?list=red&list=green&list=blue'),
+ array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'),
+ array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'),
+ array('{&var:3}', '&var=val'),
+ array('{&list}', '&list=red,green,blue'),
+ array('{&list*}', '&list=red&list=green&list=blue'),
+ array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'),
+ array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'),
+ array('{.null}', ''),
+ array('{.null,var}', '.value'),
+ array('X{.empty_keys*}', 'X'),
+ array('X{.empty_keys}', 'X'),
+ // Test that missing expansions are skipped
+ array('test{&missing*}', 'test'),
+ // Test that multiple expansions can be set
+ array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'),
+ // Test more complex query string stuff
+ array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C')
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php
new file mode 100644
index 0000000..633c5d5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Guzzle\Tests\Parsers\UriTemplate;
+
+use Guzzle\Parser\UriTemplate\PeclUriTemplate;
+
+/**
+ * @covers Guzzle\Parser\UriTemplate\PeclUriTemplate
+ */
+class PeclUriTemplateTest extends AbstractUriTemplateTest
+{
+ protected function setUp()
+ {
+ if (!extension_loaded('uri_template')) {
+ $this->markTestSkipped('uri_template PECL extension must be installed to test PeclUriTemplate');
+ }
+ }
+
+ /**
+ * @dataProvider templateProvider
+ */
+ public function testExpandsUriTemplates($template, $expansion, $params)
+ {
+ $uri = new PeclUriTemplate($template);
+ $this->assertEquals($expansion, $uri->expand($template, $params));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php
new file mode 100644
index 0000000..5130d6f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Guzzle\Tests\Parsers\UriTemplate;
+
+use Guzzle\Parser\UriTemplate\UriTemplate;
+
+/**
+ * @covers Guzzle\Parser\UriTemplate\UriTemplate
+ */
+class UriTemplateTest extends AbstractUriTemplateTest
+{
+ /**
+ * @dataProvider templateProvider
+ */
+ public function testExpandsUriTemplates($template, $expansion, $params)
+ {
+ $uri = new UriTemplate($template);
+ $this->assertEquals($expansion, $uri->expand($template, $params));
+ }
+
+ public function expressionProvider()
+ {
+ return array(
+ array(
+ '{+var*}', array(
+ 'operator' => '+',
+ 'values' => array(
+ array('value' => 'var', 'modifier' => '*')
+ )
+ ),
+ ),
+ array(
+ '{?keys,var,val}', array(
+ 'operator' => '?',
+ 'values' => array(
+ array('value' => 'keys', 'modifier' => ''),
+ array('value' => 'var', 'modifier' => ''),
+ array('value' => 'val', 'modifier' => '')
+ )
+ ),
+ ),
+ array(
+ '{+x,hello,y}', array(
+ 'operator' => '+',
+ 'values' => array(
+ array('value' => 'x', 'modifier' => ''),
+ array('value' => 'hello', 'modifier' => ''),
+ array('value' => 'y', 'modifier' => '')
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * @dataProvider expressionProvider
+ */
+ public function testParsesExpressions($exp, $data)
+ {
+ $template = new UriTemplate($exp);
+
+ // Access the config object
+ $class = new \ReflectionClass($template);
+ $method = $class->getMethod('parseExpression');
+ $method->setAccessible(true);
+
+ $exp = substr($exp, 1, -1);
+ $this->assertEquals($data, $method->invokeArgs($template, array($exp)));
+ }
+
+ /**
+ * @ticket https://github.com/guzzle/guzzle/issues/90
+ */
+ public function testAllowsNestedArrayExpansion()
+ {
+ $template = new UriTemplate();
+
+ $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array(
+ 'path' => '/foo/bar',
+ 'segments' => array('one', 'two'),
+ 'query' => 'test',
+ 'data' => array(
+ 'more' => array('fun', 'ice cream')
+ ),
+ 'foo' => array(
+ 'baz' => array(
+ 'bar' => 'fizz',
+ 'test' => 'buzz'
+ ),
+ 'bam' => 'boo'
+ )
+ ));
+
+ $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result);
+ }
+
+ /**
+ * @ticket https://github.com/guzzle/guzzle/issues/426
+ */
+ public function testSetRegex()
+ {
+ $template = new UriTemplate();
+ $template->setRegex('/\<\$(.+)\>/');
+ $this->assertSame('/foo', $template->expand('/<$a>', array('a' => 'foo')));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php
new file mode 100644
index 0000000..16990a5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Async;
+
+use Guzzle\Plugin\Async\AsyncPlugin;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Common\Event;
+use Guzzle\Http\Client;
+
+/**
+ * @covers Guzzle\Plugin\Async\AsyncPlugin
+ */
+class AsyncPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testSubscribesToEvents()
+ {
+ $events = AsyncPlugin::getSubscribedEvents();
+ $this->assertArrayHasKey('request.before_send', $events);
+ $this->assertArrayHasKey('request.exception', $events);
+ $this->assertArrayHasKey('curl.callback.progress', $events);
+ }
+
+ public function testEnablesProgressCallbacks()
+ {
+ $p = new AsyncPlugin();
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com');
+ $event = new Event(array(
+ 'request' => $request
+ ));
+ $p->onBeforeSend($event);
+ $this->assertEquals(true, $request->getCurlOptions()->get('progress'));
+ }
+
+ public function testAddsTimesOutAfterSending()
+ {
+ $p = new AsyncPlugin();
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com');
+ $handle = CurlHandle::factory($request);
+ $event = new Event(array(
+ 'request' => $request,
+ 'handle' => $handle->getHandle(),
+ 'uploaded' => 10,
+ 'upload_size' => 10,
+ 'downloaded' => 0
+ ));
+ $p->onCurlProgress($event);
+ }
+
+ public function testEnsuresRequestIsSet()
+ {
+ $p = new AsyncPlugin();
+ $event = new Event(array(
+ 'uploaded' => 10,
+ 'upload_size' => 10,
+ 'downloaded' => 0
+ ));
+ $p->onCurlProgress($event);
+ }
+
+ public function testMasksCurlExceptions()
+ {
+ $p = new AsyncPlugin();
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com');
+ $e = new CurlException('Error');
+ $event = new Event(array(
+ 'request' => $request,
+ 'exception' => $e
+ ));
+ $p->onRequestTimeout($event);
+ $this->assertEquals(RequestInterface::STATE_COMPLETE, $request->getState());
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async'));
+ }
+
+ public function testEnsuresIntegration()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 204 FOO\r\nContent-Length: 4\r\n\r\ntest");
+ $client = new Client($this->getServer()->getUrl());
+ $request = $client->post('/', null, array(
+ 'foo' => 'bar'
+ ));
+ $request->getEventDispatcher()->addSubscriber(new AsyncPlugin());
+ $request->send();
+ $this->assertEquals('', $request->getResponse()->getBody(true));
+ $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async'));
+ $received = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals('POST', $received[0]->getMethod());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php
new file mode 100644
index 0000000..72af263
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Http\Message\Request;
+use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
+use Guzzle\Plugin\Backoff\CallbackBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\AbstractBackoffStrategy
+ */
+class AbstractBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected function getMockStrategy()
+ {
+ return $this->getMockBuilder('Guzzle\Plugin\Backoff\AbstractBackoffStrategy')
+ ->setMethods(array('getDelay', 'makesDecision'))
+ ->getMockForAbstractClass();
+ }
+
+ public function testReturnsZeroWhenNoNextAndGotNull()
+ {
+ $request = new Request('GET', 'http://www.foo.com');
+ $mock = $this->getMockStrategy();
+ $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null));
+ $this->assertEquals(0, $mock->getBackoffPeriod(0, $request));
+ }
+
+ public function testReturnsFalse()
+ {
+ $request = new Request('GET', 'http://www.foo.com');
+ $mock = $this->getMockStrategy();
+ $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(false));
+ $this->assertEquals(false, $mock->getBackoffPeriod(0, $request));
+ }
+
+ public function testReturnsNextValueWhenNullOrTrue()
+ {
+ $request = new Request('GET', 'http://www.foo.com');
+ $mock = $this->getMockStrategy();
+ $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null));
+ $mock->expects($this->any())->method('makesDecision')->will($this->returnValue(false));
+
+ $mock2 = $this->getMockStrategy();
+ $mock2->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(10));
+ $mock2->expects($this->atLeastOnce())->method('makesDecision')->will($this->returnValue(true));
+ $mock->setNext($mock2);
+
+ $this->assertEquals(10, $mock->getBackoffPeriod(0, $request));
+ }
+
+ public function testReturnsFalseWhenNullAndNoNext()
+ {
+ $request = new Request('GET', 'http://www.foo.com');
+ $s = new TruncatedBackoffStrategy(2);
+ $this->assertFalse($s->getBackoffPeriod(0, $request));
+ }
+
+ public function testHasNext()
+ {
+ $a = new TruncatedBackoffStrategy(2);
+ $b = new TruncatedBackoffStrategy(2);
+ $a->setNext($b);
+ $this->assertSame($b, $a->getNext());
+ }
+
+ public function testSkipsOtherDecisionsInChainWhenOneReturnsTrue()
+ {
+ $a = new CallbackBackoffStrategy(function () { return null; }, true);
+ $b = new CallbackBackoffStrategy(function () { return true; }, true);
+ $c = new CallbackBackoffStrategy(function () { return null; }, true);
+ $d = new CallbackBackoffStrategy(function () { return 10; }, false);
+ $a->setNext($b);
+ $b->setNext($c);
+ $c->setNext($d);
+ $this->assertEquals(10, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com')));
+ }
+
+ public function testReturnsZeroWhenDecisionMakerReturnsTrueButNoFurtherStrategiesAreInTheChain()
+ {
+ $a = new CallbackBackoffStrategy(function () { return null; }, true);
+ $b = new CallbackBackoffStrategy(function () { return true; }, true);
+ $a->setNext($b);
+ $this->assertSame(0, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com')));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php
new file mode 100644
index 0000000..a64dd82
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Common\Event;
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Http\Curl\CurlHandle;
+use Guzzle\Plugin\Backoff\BackoffLogger;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestFactory;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\BackoffLogger
+ */
+class BackoffLoggerTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public $message;
+
+ public function setUp()
+ {
+ $this->message = '';
+ }
+
+ public function testHasEventList()
+ {
+ $this->assertEquals(1, count(BackoffLogger::getSubscribedEvents()));
+ }
+
+ public function testLogsEvents()
+ {
+ list($logPlugin, $request, $response) = $this->getMocks();
+
+ $response = $this->getMockBuilder('Guzzle\Http\Message\Response')
+ ->setConstructorArgs(array(503))
+ ->setMethods(array('getInfo'))
+ ->getMock();
+
+ $response->expects($this->any())
+ ->method('getInfo')
+ ->will($this->returnValue(2));
+
+ $handle = $this->getMockHandle();
+
+ $event = new Event(array(
+ 'request' => $request,
+ 'response' => $response,
+ 'retries' => 1,
+ 'delay' => 3,
+ 'handle' => $handle
+ ));
+
+ $logPlugin->onRequestRetry($event);
+ $this->assertContains(
+ '] PUT http://www.example.com - 503 Service Unavailable - Retries: 1, Delay: 3, Time: 2, 2, cURL: 30 Foo',
+ $this->message
+ );
+ }
+
+ public function testCanSetTemplate()
+ {
+ $l = new BackoffLogger(new ClosureLogAdapter(function () {}));
+ $l->setTemplate('foo');
+ $t = $this->readAttribute($l, 'formatter');
+ $this->assertEquals('foo', $this->readAttribute($t, 'template'));
+ }
+
+ /**
+ * @return array
+ */
+ protected function getMocks()
+ {
+ $that = $this;
+ $logger = new ClosureLogAdapter(function ($message) use ($that) {
+ $that->message .= $message . "\n";
+ });
+ $logPlugin = new BackoffLogger($logger);
+ $response = new Response(503);
+ $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com', array(
+ 'Content-Length' => 3,
+ 'Foo' => 'Bar'
+ ));
+
+ return array($logPlugin, $request, $response);
+ }
+
+ /**
+ * @return CurlHandle
+ */
+ protected function getMockHandle()
+ {
+ $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle')
+ ->disableOriginalConstructor()
+ ->setMethods(array('getError', 'getErrorNo', 'getInfo'))
+ ->getMock();
+
+ $handle->expects($this->once())
+ ->method('getError')
+ ->will($this->returnValue('Foo'));
+
+ $handle->expects($this->once())
+ ->method('getErrorNo')
+ ->will($this->returnValue(30));
+
+ $handle->expects($this->any())
+ ->method('getInfo')
+ ->will($this->returnValue(2));
+
+ return $handle;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php
new file mode 100644
index 0000000..496e49e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Http\Client;
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Curl\CurlMulti;
+use Guzzle\Http\Curl\CurlMultiInterface;
+use Guzzle\Plugin\Backoff\ConstantBackoffStrategy;
+use Guzzle\Plugin\Backoff\CurlBackoffStrategy;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @group server
+ * @covers Guzzle\Plugin\Backoff\BackoffPlugin
+ */
+class BackoffPluginTest extends \Guzzle\Tests\GuzzleTestCase implements EventSubscriberInterface
+{
+ protected $retried;
+
+ public function setUp()
+ {
+ $this->retried = false;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry');
+ }
+
+ public function onRequestRetry(Event $event)
+ {
+ $this->retried = $event;
+ }
+
+ public function testHasEventList()
+ {
+ $this->assertEquals(1, count(BackoffPlugin::getAllEvents()));
+ }
+
+ public function testCreatesDefaultExponentialBackoffPlugin()
+ {
+ $plugin = BackoffPlugin::getExponentialBackoff(3, array(204), array(10));
+ $this->assertInstanceOf('Guzzle\Plugin\Backoff\BackoffPlugin', $plugin);
+ $strategy = $this->readAttribute($plugin, 'strategy');
+ $this->assertInstanceOf('Guzzle\Plugin\Backoff\TruncatedBackoffStrategy', $strategy);
+ $this->assertEquals(3, $this->readAttribute($strategy, 'max'));
+ $strategy = $this->readAttribute($strategy, 'next');
+ $this->assertInstanceOf('Guzzle\Plugin\Backoff\HttpBackoffStrategy', $strategy);
+ $this->assertEquals(array(204 => true), $this->readAttribute($strategy, 'errorCodes'));
+ $strategy = $this->readAttribute($strategy, 'next');
+ $this->assertInstanceOf('Guzzle\Plugin\Backoff\CurlBackoffStrategy', $strategy);
+ $this->assertEquals(array(10 => true), $this->readAttribute($strategy, 'errorCodes'));
+ $strategy = $this->readAttribute($strategy, 'next');
+ $this->assertInstanceOf('Guzzle\Plugin\Backoff\ExponentialBackoffStrategy', $strategy);
+ }
+
+ public function testDoesNotRetryUnlessStrategyReturnsNumber()
+ {
+ $request = new Request('GET', 'http://www.example.com');
+ $request->setState('transfer');
+
+ $mock = $this->getMockBuilder('Guzzle\Plugin\Backoff\BackoffStrategyInterface')
+ ->setMethods(array('getBackoffPeriod'))
+ ->getMockForAbstractClass();
+
+ $mock->expects($this->once())
+ ->method('getBackoffPeriod')
+ ->will($this->returnValue(false));
+
+ $plugin = new BackoffPlugin($mock);
+ $plugin->addSubscriber($this);
+ $plugin->onRequestSent(new Event(array('request' => $request)));
+ $this->assertFalse($this->retried);
+ }
+
+ public function testUpdatesRequestForRetry()
+ {
+ $request = new Request('GET', 'http://www.example.com');
+ $request->setState('transfer');
+ $response = new Response(500);
+ $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle')->disableOriginalConstructor()->getMock();
+ $e = new CurlException();
+ $e->setCurlHandle($handle);
+
+ $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10));
+ $plugin->addSubscriber($this);
+
+ $event = new Event(array(
+ 'request' => $request,
+ 'response' => $response,
+ 'exception' => $e
+ ));
+
+ $plugin->onRequestSent($event);
+ $this->assertEquals(array(
+ 'request' => $request,
+ 'response' => $response,
+ 'handle' => $handle,
+ 'retries' => 1,
+ 'delay' => 10
+ ), $this->readAttribute($this->retried, 'context'));
+
+ $plugin->onRequestSent($event);
+ $this->assertEquals(array(
+ 'request' => $request,
+ 'response' => $response,
+ 'handle' => $handle,
+ 'retries' => 2,
+ 'delay' => 10
+ ), $this->readAttribute($this->retried, 'context'));
+ }
+
+ public function testDoesNothingWhenNotRetryingAndPollingRequest()
+ {
+ $request = new Request('GET', 'http://www.foo.com');
+ $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10));
+ $plugin->onRequestPoll(new Event(array('request' => $request)));
+ }
+
+ public function testRetriesRequests()
+ {
+ // Create a script to return several 500 and 503 response codes
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"
+ ));
+
+ $plugin = new BackoffPlugin(
+ new TruncatedBackoffStrategy(3,
+ new HttpBackoffStrategy(null,
+ new CurlBackoffStrategy(null,
+ new ConstantBackoffStrategy(0.05)
+ )
+ )
+ )
+ );
+
+ $client = new Client($this->getServer()->getUrl());
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $request = $client->get();
+ $request->send();
+
+ // Make sure it eventually completed successfully
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ $this->assertEquals('data', $request->getResponse()->getBody(true));
+
+ // Check that three requests were made to retry this request
+ $this->assertEquals(3, count($this->getServer()->getReceivedRequests(false)));
+ $this->assertEquals(2, $request->getParams()->get(BackoffPlugin::RETRY_PARAM));
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException
+ */
+ public function testFailsOnTruncation()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n"
+ ));
+
+ $plugin = new BackoffPlugin(
+ new TruncatedBackoffStrategy(2,
+ new HttpBackoffStrategy(null,
+ new ConstantBackoffStrategy(0.05)
+ )
+ )
+ );
+
+ $client = new Client($this->getServer()->getUrl());
+ $client->addSubscriber($plugin);
+ $client->get()->send();
+ }
+
+ public function testRetriesRequestsWhenInParallel()
+ {
+ // Create a script to return several 500 and 503 response codes
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"
+ ));
+
+ $plugin = new BackoffPlugin(
+ new HttpBackoffStrategy(null,
+ new TruncatedBackoffStrategy(3,
+ new CurlBackoffStrategy(null,
+ new ConstantBackoffStrategy(0.1)
+ )
+ )
+ )
+ );
+ $client = new Client($this->getServer()->getUrl());
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $requests = array();
+ for ($i = 0; $i < 5; $i++) {
+ $requests[] = $client->get();
+ }
+ $client->send($requests);
+
+ $this->assertEquals(15, count($this->getServer()->getReceivedRequests(false)));
+ }
+
+ /**
+ * @covers Guzzle\Plugin\Backoff\BackoffPlugin
+ * @covers Guzzle\Http\Curl\CurlMulti
+ */
+ public function testRetriesPooledRequestsUsingDelayAndPollingEvent()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"
+ ));
+ // Need to sleep for some time ensure that the polling works correctly in the observer
+ $plugin = new BackoffPlugin(new HttpBackoffStrategy(null,
+ new TruncatedBackoffStrategy(1,
+ new ConstantBackoffStrategy(0.5))));
+
+ $client = new Client($this->getServer()->getUrl());
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $request = $client->get();
+ $request->send();
+ // Make sure it eventually completed successfully
+ $this->assertEquals('data', $request->getResponse()->getBody(true));
+ // Check that two requests were made to retry this request
+ $this->assertEquals(2, count($this->getServer()->getReceivedRequests(false)));
+ }
+
+ public function testSeeksToBeginningOfRequestBodyWhenRetrying()
+ {
+ // Create a request with a body
+ $request = new EntityEnclosingRequest('PUT', 'http://www.example.com');
+ $request->setBody('abc');
+ // Set the retry time to be something that will be retried always
+ $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2);
+ // Seek to the end of the stream
+ $request->getBody()->seek(3);
+ $this->assertEquals('', $request->getBody()->read(1));
+ // Create a plugin that does not delay when retrying
+ $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0));
+ $plugin->onRequestPoll($this->getMockEvent($request));
+ // Ensure that the stream was seeked to 0
+ $this->assertEquals('a', $request->getBody()->read(1));
+ }
+
+ public function testDoesNotSeekOnRequestsWithNoBodyWhenRetrying()
+ {
+ // Create a request with a body
+ $request = new EntityEnclosingRequest('PUT', 'http://www.example.com');
+ $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2);
+ $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0));
+ $plugin->onRequestPoll($this->getMockEvent($request));
+ }
+
+ protected function getMockEvent(RequestInterface $request)
+ {
+ // Create a mock curl multi object
+ $multi = $this->getMockBuilder('Guzzle\Http\Curl\CurlMulti')
+ ->setMethods(array('remove', 'add'))
+ ->getMock();
+
+ // Create an event that is expected for the Poll event
+ $event = new Event(array(
+ 'request' => $request,
+ 'curl_multi' => $multi
+ ));
+ $event->setName(CurlMultiInterface::POLLING_REQUEST);
+
+ return $event;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php
new file mode 100644
index 0000000..c0ce10d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\CallbackBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\CallbackBackoffStrategy
+ */
+class CallbackBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testEnsuresIsCallable()
+ {
+ $strategy = new CallbackBackoffStrategy(new \stdClass(), true);
+ }
+
+ public function testRetriesWithCallable()
+ {
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $strategy = new CallbackBackoffStrategy(function () { return 10; }, true);
+ $this->assertTrue($strategy->makesDecision());
+ $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request));
+ // Ensure it chains correctly when null is returned
+ $strategy = new CallbackBackoffStrategy(function () { return null; }, false);
+ $this->assertFalse($strategy->makesDecision());
+ $this->assertFalse($strategy->getBackoffPeriod(0, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php
new file mode 100644
index 0000000..703eb4a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\ConstantBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\ConstantBackoffStrategy
+ */
+class ConstantBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWithConstantDelay()
+ {
+ $strategy = new ConstantBackoffStrategy(3.5);
+ $this->assertFalse($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(3.5, $strategy->getBackoffPeriod(0, $request));
+ $this->assertEquals(3.5, $strategy->getBackoffPeriod(1, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php
new file mode 100644
index 0000000..0a5c3e2
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Backoff\CurlBackoffStrategy;
+use Guzzle\Http\Exception\CurlException;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\CurlBackoffStrategy
+ * @covers Guzzle\Plugin\Backoff\AbstractErrorCodeBackoffStrategy
+ */
+class CurlBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWithExponentialDelay()
+ {
+ $this->assertNotEmpty(CurlBackoffStrategy::getDefaultFailureCodes());
+ $strategy = new CurlBackoffStrategy();
+ $this->assertTrue($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $e = new CurlException();
+ $e->setError('foo', CURLE_BAD_CALLING_ORDER);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, null, $e));
+
+ foreach (CurlBackoffStrategy::getDefaultFailureCodes() as $code) {
+ $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, null, $e->setError('foo', $code)));
+ }
+ }
+
+ public function testIgnoresNonErrors()
+ {
+ $strategy = new CurlBackoffStrategy();
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, new Response(200)));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php
new file mode 100644
index 0000000..09965bc
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\ExponentialBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\ExponentialBackoffStrategy
+ */
+class ExponentialBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWithExponentialDelay()
+ {
+ $strategy = new ExponentialBackoffStrategy();
+ $this->assertFalse($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(1, $strategy->getBackoffPeriod(0, $request));
+ $this->assertEquals(2, $strategy->getBackoffPeriod(1, $request));
+ $this->assertEquals(4, $strategy->getBackoffPeriod(2, $request));
+ $this->assertEquals(8, $strategy->getBackoffPeriod(3, $request));
+ $this->assertEquals(16, $strategy->getBackoffPeriod(4, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php
new file mode 100644
index 0000000..ae68a4e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+use Guzzle\Http\Message\Response;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\HttpBackoffStrategy
+ * @covers Guzzle\Plugin\Backoff\AbstractErrorCodeBackoffStrategy
+ */
+class HttpBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWhenCodeMatches()
+ {
+ $this->assertNotEmpty(HttpBackoffStrategy::getDefaultFailureCodes());
+ $strategy = new HttpBackoffStrategy();
+ $this->assertTrue($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+
+ $response = new Response(200);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response));
+ $response->setStatus(400);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response));
+
+ foreach (HttpBackoffStrategy::getDefaultFailureCodes() as $code) {
+ $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response->setStatus($code)));
+ }
+ }
+
+ public function testAllowsCustomCodes()
+ {
+ $strategy = new HttpBackoffStrategy(array(204));
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $response = new Response(204);
+ $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response));
+ $response->setStatus(500);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response));
+ }
+
+ public function testIgnoresNonErrors()
+ {
+ $strategy = new HttpBackoffStrategy();
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php
new file mode 100644
index 0000000..b4ce8e4
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\LinearBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\LinearBackoffStrategy
+ */
+class LinearBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWithLinearDelay()
+ {
+ $strategy = new LinearBackoffStrategy(5);
+ $this->assertFalse($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request));
+ $this->assertEquals(5, $strategy->getBackoffPeriod(1, $request));
+ $this->assertEquals(10, $strategy->getBackoffPeriod(2, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php
new file mode 100644
index 0000000..dea5a68
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Plugin\Backoff\ReasonPhraseBackoffStrategy;
+use Guzzle\Http\Message\Response;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\ReasonPhraseBackoffStrategy
+ * @covers Guzzle\Plugin\Backoff\AbstractErrorCodeBackoffStrategy
+ */
+class ReasonPhraseBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWhenCodeMatches()
+ {
+ $this->assertEmpty(ReasonPhraseBackoffStrategy::getDefaultFailureCodes());
+ $strategy = new ReasonPhraseBackoffStrategy(array('Foo', 'Internal Server Error'));
+ $this->assertTrue($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $response = new Response(200);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response));
+ $response->setStatus(200, 'Foo');
+ $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response));
+ }
+
+ public function testIgnoresNonErrors()
+ {
+ $strategy = new ReasonPhraseBackoffStrategy();
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php
new file mode 100644
index 0000000..5590dfb
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Backoff;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+use Guzzle\Plugin\Backoff\ConstantBackoffStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Backoff\TruncatedBackoffStrategy
+ */
+class TruncatedBackoffStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testRetriesWhenLessThanMax()
+ {
+ $strategy = new TruncatedBackoffStrategy(2);
+ $this->assertTrue($strategy->makesDecision());
+ $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false);
+ $this->assertFalse($strategy->getBackoffPeriod(0, $request));
+ $this->assertFalse($strategy->getBackoffPeriod(1, $request));
+ $this->assertFalse($strategy->getBackoffPeriod(2, $request));
+
+ $response = new Response(500);
+ $strategy->setNext(new HttpBackoffStrategy(null, new ConstantBackoffStrategy(10)));
+ $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request, $response));
+ $this->assertEquals(10, $strategy->getBackoffPeriod(1, $request, $response));
+ $this->assertFalse($strategy->getBackoffPeriod(2, $request, $response));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php
new file mode 100644
index 0000000..69da60a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php
@@ -0,0 +1,441 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Common\Event;
+use Guzzle\Common\Version;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\CachePlugin;
+use Guzzle\Plugin\Cache\DefaultCacheStorage;
+use Guzzle\Plugin\Cache\CallbackCanCacheStrategy;
+use Doctrine\Common\Cache\ArrayCache;
+
+/**
+ * @group server
+ * @covers Guzzle\Plugin\Cache\CachePlugin
+ * @covers Guzzle\Plugin\Cache\DefaultRevalidation
+ */
+class CachePluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testAddsDefaultStorage()
+ {
+ $plugin = new CachePlugin();
+ $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage'));
+ }
+
+ public function testAddsDefaultCollaborators()
+ {
+ $this->assertNotEmpty(CachePlugin::getSubscribedEvents());
+ $plugin = new CachePlugin(array(
+ 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass()
+ ));
+ $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage'));
+ $this->assertInstanceOf(
+ 'Guzzle\Plugin\Cache\CanCacheStrategyInterface',
+ $this->readAttribute($plugin, 'canCache')
+ );
+ $this->assertInstanceOf(
+ 'Guzzle\Plugin\Cache\RevalidationInterface',
+ $this->readAttribute($plugin, 'revalidation')
+ );
+ }
+
+ public function testAddsCallbackCollaborators()
+ {
+ $this->assertNotEmpty(CachePlugin::getSubscribedEvents());
+ $plugin = new CachePlugin(array('can_cache' => function () {}));
+ $this->assertInstanceOf(
+ 'Guzzle\Plugin\Cache\CallbackCanCacheStrategy',
+ $this->readAttribute($plugin, 'canCache')
+ );
+ }
+
+ public function testCanPassCacheAsOnlyArgumentToConstructor()
+ {
+ $p = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache()));
+ $p = new CachePlugin(new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache())));
+ }
+
+ public function testUsesCreatedCacheStorage()
+ {
+ $plugin = new CachePlugin(array(
+ 'adapter' => $this->getMockBuilder('Guzzle\Cache\CacheAdapterInterface')->getMockForAbstractClass()
+ ));
+ $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage'));
+ }
+
+ public function testUsesProvidedOptions()
+ {
+ $can = $this->getMockBuilder('Guzzle\Plugin\Cache\CanCacheStrategyInterface')->getMockForAbstractClass();
+ $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\RevalidationInterface')->getMockForAbstractClass();
+ $plugin = new CachePlugin(array(
+ 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass(),
+ 'can_cache' => $can,
+ 'revalidation' => $revalidate
+ ));
+ $this->assertSame($can, $this->readAttribute($plugin, 'canCache'));
+ $this->assertSame($revalidate, $this->readAttribute($plugin, 'revalidation'));
+ }
+
+ public function satisfyProvider()
+ {
+ $req1 = new Request('GET', 'http://foo.com', array('Cache-Control' => 'no-cache'));
+
+ return array(
+ // The response is too old to satisfy the request
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-age=20')), new Response(200, array('Age' => 100)), false, false),
+ // The response cannot satisfy the request because it is stale
+ array(new Request('GET', 'http://foo.com'), new Response(200, array('Cache-Control' => 'max-age=10', 'Age' => 100)), false, false),
+ // Allows the expired response to satisfy the request because of the max-stale
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=15')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), true, false),
+ // Max stale is > than the allowed staleness
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=5')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), false, false),
+ // Performs cache revalidation
+ array($req1, new Response(200), true, true),
+ // Performs revalidation due to ETag on the response and no cache-control on the request
+ array(new Request('GET', 'http://foo.com'), new Response(200, array(
+ 'ETag' => 'ABC',
+ 'Expires' => date('c', strtotime('+1 year'))
+ )), true, true),
+ );
+ }
+
+ /**
+ * @dataProvider satisfyProvider
+ */
+ public function testChecksIfResponseCanSatisfyRequest($request, $response, $can, $revalidates)
+ {
+ $didRevalidate = false;
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass();
+ $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation')
+ ->setMethods(array('revalidate'))
+ ->setConstructorArgs(array($storage))
+ ->getMockForAbstractClass();
+
+ $revalidate->expects($this->any())
+ ->method('revalidate')
+ ->will($this->returnCallback(function () use (&$didRevalidate) {
+ $didRevalidate = true;
+ return true;
+ }));
+
+ $plugin = new CachePlugin(array(
+ 'storage' => $storage,
+ 'revalidation' => $revalidate
+ ));
+
+ $this->assertEquals($can, $plugin->canResponseSatisfyRequest($request, $response));
+ $this->assertEquals($didRevalidate, $revalidates);
+ }
+
+ public function satisfyFailedProvider()
+ {
+ return array(
+ // Neither has stale-if-error
+ array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100)), false),
+ // Request has stale-if-error
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true),
+ // Request has valid stale-if-error
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true),
+ // Request has expired stale-if-error
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), false),
+ // Response has permanent stale-if-error
+ array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error', )), true),
+ // Response has valid stale-if-error
+ array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), true),
+ // Response has expired stale-if-error
+ array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false),
+ // Request has valid stale-if-error but response does not
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false),
+ // Response has valid stale-if-error but request does not
+ array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), false),
+ );
+ }
+
+ /**
+ * @dataProvider satisfyFailedProvider
+ */
+ public function testChecksIfResponseCanSatisfyFailedRequest($request, $response, $can)
+ {
+ $plugin = new CachePlugin();
+
+ $this->assertEquals($can, $plugin->canResponseSatisfyFailedRequest($request, $response));
+ }
+
+ public function testDoesNothingWhenRequestIsNotCacheable()
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->never())->method('fetch');
+
+ $plugin = new CachePlugin(array(
+ 'storage' => $storage,
+ 'can_cache' => new CallbackCanCacheStrategy(function () { return false; })
+ ));
+
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => new Request('GET', 'http://foo.com')
+ )));
+ }
+
+ public function satisfiableProvider()
+ {
+ $date = new \DateTime('-10 seconds');
+
+ return array(
+ // Fresh response
+ array(new Response(200, array(), 'foo')),
+ // Stale response
+ array(new Response(200, array('Date' => $date->format('c'), 'Cache-Control' => 'max-age=5'), 'foo'))
+ );
+ }
+
+ /**
+ * @dataProvider satisfiableProvider
+ */
+ public function testInjectsSatisfiableResponses($response)
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+
+ $storage->expects($this->once())->method('fetch')->will($this->returnValue($response));
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale'));
+ $plugin->onRequestBeforeSend(new Event(array('request' => $request)));
+ $plugin->onRequestSent(new Event(array('request' => $request, 'response' => $request->getResponse())));
+ $this->assertEquals($response->getStatusCode(), $request->getResponse()->getStatusCode());
+ $this->assertEquals((string) $response->getBody(), (string) $request->getResponse()->getBody());
+ $this->assertTrue($request->getResponse()->hasHeader('Age'));
+ if ($request->getResponse()->isFresh() === false) {
+ $this->assertContains('110', (string) $request->getResponse()->getHeader('Warning'));
+ }
+ $this->assertSame(
+ sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION),
+ (string) $request->getHeader('Via')
+ );
+ $this->assertSame(
+ sprintf('%s GuzzleCache/%s',$request->getProtocolVersion(), Version::VERSION),
+ (string) $request->getResponse()->getHeader('Via')
+ );
+ $this->assertTrue($request->getParams()->get('cache.lookup'));
+ $this->assertTrue($request->getParams()->get('cache.hit'));
+ $this->assertTrue($request->getResponse()->hasHeader('X-Cache-Lookup'));
+ $this->assertTrue($request->getResponse()->hasHeader('X-Cache'));
+ $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache'));
+ $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache-Lookup'));
+ }
+
+ public function satisfiableOnErrorProvider()
+ {
+ $date = new \DateTime('-10 seconds');
+ return array(
+ array(
+ new Response(200, array(
+ 'Date' => $date->format('c'),
+ 'Cache-Control' => 'max-age=5, stale-if-error'
+ ), 'foo'),
+ )
+ );
+ }
+
+ /**
+ * @dataProvider satisfiableOnErrorProvider
+ */
+ public function testInjectsSatisfiableResponsesOnError($cacheResponse)
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse));
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale'));
+ $plugin->onRequestBeforeSend(new Event(array('request' => $request)));
+ $plugin->onRequestError(
+ $event = new Event(array(
+ 'request' => $request,
+ 'response' => $request->getResponse(),
+ ))
+ );
+ $response = $event['response'];
+ $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode());
+ $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody());
+ $this->assertTrue($response->hasHeader('Age'));
+ if ($response->isFresh() === false) {
+ $this->assertContains('110', (string) $response->getHeader('Warning'));
+ }
+ $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via'));
+ $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via'));
+ $this->assertTrue($request->getParams()->get('cache.lookup'));
+ $this->assertSame('error', $request->getParams()->get('cache.hit'));
+ $this->assertTrue($response->hasHeader('X-Cache-Lookup'));
+ $this->assertTrue($response->hasHeader('X-Cache'));
+ $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup'));
+ $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache'));
+ }
+
+ /**
+ * @dataProvider satisfiableOnErrorProvider
+ */
+ public function testInjectsSatisfiableResponsesOnException($cacheResponse)
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse));
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale'));
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => $request
+ )));
+ $plugin->onRequestException(
+ new Event(array(
+ 'request' => $request,
+ 'response' => $request->getResponse(),
+ 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'),
+ ))
+ );
+ $plugin->onRequestSent(
+ new Event(array(
+ 'request' => $request,
+ 'response' => $response = $request->getResponse(),
+ ))
+ );
+ $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode());
+ $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody());
+ $this->assertTrue($response->hasHeader('Age'));
+ if ($response->isFresh() === false) {
+ $this->assertContains('110', (string) $response->getHeader('Warning'));
+ }
+ $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via'));
+ $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via'));
+ $this->assertTrue($request->getParams()->get('cache.lookup'));
+ $this->assertSame('error', $request->getParams()->get('cache.hit'));
+ $this->assertTrue($response->hasHeader('X-Cache-Lookup'));
+ $this->assertTrue($response->hasHeader('X-Cache'));
+ $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup'));
+ $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache'));
+ }
+
+ public function unsatisfiableOnErrorProvider()
+ {
+ $date = new \DateTime('-10 seconds');
+
+ return array(
+ // no-store on request
+ array(
+ false,
+ array('Cache-Control' => 'no-store'),
+ new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'),
+ ),
+ // request expired
+ array(
+ true,
+ array('Cache-Control' => 'stale-if-error=4'),
+ new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'),
+ ),
+ // response expired
+ array(
+ true,
+ array('Cache-Control' => 'stale-if-error'),
+ new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error=4'), 'foo'),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider unsatisfiableOnErrorProvider
+ */
+ public function testDoesNotInjectUnsatisfiableResponsesOnError($requestCanCache, $requestHeaders, $cacheResponse)
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($cacheResponse));
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', $requestHeaders);
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => $request
+ )));
+ $plugin->onRequestError(
+ $event = new Event(array(
+ 'request' => $request,
+ 'response' => $response = $request->getResponse(),
+ ))
+ );
+
+ $this->assertSame($response, $event['response']);
+ }
+
+ /**
+ * @dataProvider unsatisfiableOnErrorProvider
+ */
+ public function testDoesNotInjectUnsatisfiableResponsesOnException($requestCanCache, $requestHeaders, $responseParts)
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($responseParts));
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', $requestHeaders);
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => $request
+ )));
+ $plugin->onRequestException(
+ $event = new Event(array(
+ 'request' => $request,
+ 'response' => $response = $request->getResponse(),
+ 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'),
+ ))
+ );
+
+ $this->assertSame($response, $request->getResponse());
+ }
+
+ public function testCachesResponsesWhenCacheable()
+ {
+ $cache = new ArrayCache();
+ $plugin = new CachePlugin($cache);
+
+ $request = new Request('GET', 'http://foo.com');
+ $response = new Response(200, array(), 'Foo');
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => $request
+ )));
+ $plugin->onRequestSent(new Event(array(
+ 'request' => $request,
+ 'response' => $response
+ )));
+ $data = $this->readAttribute($cache, 'data');
+ $this->assertNotEmpty($data);
+ }
+
+ public function testPurgesRequests()
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('purge'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->atLeastOnce())->method('purge');
+ $plugin = new CachePlugin(array('storage' => $storage));
+ $request = new Request('GET', 'http://foo.com', array('X-Foo' => 'Bar'));
+ $plugin->purge($request);
+ }
+
+ public function testAutoPurgesRequests()
+ {
+ $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')
+ ->setMethods(array('purge'))
+ ->getMockForAbstractClass();
+ $storage->expects($this->atLeastOnce())->method('purge');
+ $plugin = new CachePlugin(array('storage' => $storage, 'auto_purge' => true));
+ $client = new Client();
+ $request = $client->put('http://foo.com', array('X-Foo' => 'Bar'));
+ $request->addSubscriber($plugin);
+ $request->setResponse(new Response(200), true);
+ $request->send();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php
new file mode 100644
index 0000000..f3d9baf
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Doctrine\Common\Cache\ArrayCache;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\CachePlugin;
+use Guzzle\Plugin\Cache\CallbackCanCacheStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Cache\CallbackCanCacheStrategy
+ */
+class CallbackCanCacheStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testConstructorEnsuresCallbackIsCallable()
+ {
+ $p = new CallbackCanCacheStrategy(new \stdClass());
+ }
+
+ public function testUsesCallback()
+ {
+ $c = new CallbackCanCacheStrategy(function ($request) { return true; });
+ $this->assertTrue($c->canCacheRequest(new Request('DELETE', 'http://www.foo.com')));
+ }
+
+ /**
+ * The following is a bit of an integration test to ensure that the CachePlugin honors a
+ * custom can cache strategy.
+ */
+ public function testIntegrationWithCachePlugin()
+ {
+ $c = new CallbackCanCacheStrategy(
+ function ($request) { return true; },
+ function ($response) { return true; }
+ );
+
+ // Make a request and response that have no business being cached
+ $request = new Request('DELETE', 'http://www.foo.com');
+ $response = Response::fromMessage(
+ "HTTP/1.1 200 OK\r\n"
+ . "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ . "Last-Modified: Wed, 09 Jan 2013 08:48:53 GMT\r\n"
+ . "Content-Length: 2\r\n"
+ . "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n\r\n"
+ . "hi"
+ );
+
+ $this->assertTrue($c->canCacheRequest($request));
+ $this->assertTrue($c->canCacheResponse($response));
+
+ $s = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultCacheStorage')
+ ->setConstructorArgs(array(new DoctrineCacheAdapter(new ArrayCache())))
+ ->setMethods(array('fetch'))
+ ->getMockForAbstractClass();
+
+ $s->expects($this->once())
+ ->method('fetch')
+ ->will($this->returnValue($response));
+
+ $plugin = new CachePlugin(array('can_cache' => $c, 'storage' => $s));
+ $plugin->onRequestBeforeSend(new Event(array('request' => $request)));
+
+ $this->assertEquals(200, $request->getResponse()->getStatusCode());
+ $this->assertEquals('hi', $request->getResponse()->getBody(true));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php
new file mode 100644
index 0000000..701a015
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\DefaultCacheStorage;
+use Doctrine\Common\Cache\ArrayCache;
+
+/**
+ * @covers Guzzle\Plugin\Cache\DefaultCacheStorage
+ */
+class DefaultCacheStorageTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected function getCache()
+ {
+ $a = new ArrayCache();
+ $c = new DoctrineCacheAdapter($a);
+ $s = new DefaultCacheStorage($c);
+ $request = new Request('GET', 'http://foo.com', array('Accept' => 'application/json'));
+ $response = new Response(200, array(
+ 'Content-Type' => 'application/json',
+ 'Connection' => 'close',
+ 'X-Foo' => 'Bar',
+ 'Vary' => 'Accept'
+ ), 'test');
+ $s->cache($request, $response);
+ $data = $this->readAttribute($a, 'data');
+
+ return array(
+ 'cache' => $a,
+ 'adapter' => $c,
+ 'storage' => $s,
+ 'request' => $request,
+ 'response' => $response,
+ 'serialized' => end($data)
+ );
+ }
+
+ public function testReturnsNullForCacheMiss()
+ {
+ $cache = $this->getCache();
+ $this->assertNull($cache['storage']->fetch(new Request('GET', 'http://test.com')));
+ }
+
+ public function testCachesRequests()
+ {
+ $cache = $this->getCache();
+ $foundRequest = $foundBody = $bodyKey = false;
+ foreach ($this->readAttribute($cache['cache'], 'data') as $key => $v) {
+ if (strpos($v, 'foo.com')) {
+ $foundRequest = true;
+ $data = unserialize($v);
+ $bodyKey = $data[0][3];
+ $this->assertInternalType('integer', $data[0][4]);
+ $this->assertFalse(isset($data[0][0]['connection']));
+ $this->assertEquals('foo.com', $data[0][0]['host']);
+ } elseif ($v == 'test') {
+ $foundBody = $key;
+ }
+ }
+ $this->assertContains($bodyKey, $foundBody);
+ $this->assertTrue($foundRequest);
+ }
+
+ public function testFetchesResponse()
+ {
+ $cache = $this->getCache();
+ $response = $cache['storage']->fetch($cache['request']);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertFalse($response->hasHeader('Connection'));
+ $this->assertEquals('Bar', (string) $response->getHeader('X-Foo'));
+ $this->assertEquals('test', (string) $response->getBody());
+ $this->assertTrue(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data')));
+ }
+
+ public function testDeletesRequestItemsAndBody()
+ {
+ $cache = $this->getCache();
+ $cache['storage']->delete($cache['request']);
+ $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data')));
+ $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data')));
+ }
+
+ public function testCachesMultipleRequestsWithVary()
+ {
+ $cache = $this->getCache();
+ $cache['request']->setHeader('Accept', 'application/xml');
+ $response = $cache['response']->setHeader('Content-Type', 'application/xml');
+ $response->setBody('123');
+ $cache['storage']->cache($cache['request'], $response);
+ $data = $this->readAttribute($cache['cache'], 'data');
+ foreach ($data as $v) {
+ if (strpos($v, 'foo.com')) {
+ $u = unserialize($v);
+ $this->assertEquals(2, count($u));
+ $this->assertEquals($u[0][0]['accept'], 'application/xml');
+ $this->assertEquals($u[0][1]['content-type'], 'application/xml');
+ $this->assertEquals($u[1][0]['accept'], 'application/json');
+ $this->assertEquals($u[1][1]['content-type'], 'application/json');
+ $this->assertNotSame($u[0][3], $u[1][3]);
+ break;
+ }
+ }
+ }
+
+ public function testPurgeRemovesAllMethodCaches()
+ {
+ $cache = $this->getCache();
+ foreach (array('HEAD', 'POST', 'PUT', 'DELETE') as $method) {
+ $request = RequestFactory::getInstance()->cloneRequestWithMethod($cache['request'], $method);
+ $cache['storage']->cache($request, $cache['response']);
+ }
+ $cache['storage']->purge('http://foo.com');
+ $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data')));
+ $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data')));
+ $this->assertEquals(
+ array('DoctrineNamespaceCacheKey[]'),
+ array_keys($this->readAttribute($cache['cache'], 'data'))
+ );
+ }
+
+ public function testRemovesExpiredResponses()
+ {
+ $cache = $this->getCache();
+ $request = new Request('GET', 'http://xyz.com');
+ $response = new Response(200, array('Age' => 1000, 'Cache-Control' => 'max-age=-10000'));
+ $cache['storage']->cache($request, $response);
+ $this->assertNull($cache['storage']->fetch($request));
+ $data = $this->readAttribute($cache['cache'], 'data');
+ $this->assertFalse(in_array('xyz.com', $data));
+ $this->assertTrue(in_array($cache['serialized'], $data));
+ }
+
+ public function testUsesVaryToDetermineResult()
+ {
+ $cache = $this->getCache();
+ $this->assertInstanceOf('Guzzle\Http\Message\Response', $cache['storage']->fetch($cache['request']));
+ $request = new Request('GET', 'http://foo.com', array('Accept' => 'application/xml'));
+ $this->assertNull($cache['storage']->fetch($request));
+ }
+
+ public function testEnsuresResponseIsStillPresent()
+ {
+ $cache = $this->getCache();
+ $data = $this->readAttribute($cache['cache'], 'data');
+ $key = array_search('test', $data);
+ $cache['cache']->delete(substr($key, 1, -4));
+ $this->assertNull($cache['storage']->fetch($cache['request']));
+ }
+
+ public function staleProvider()
+ {
+ return array(
+ array(
+ new Request('GET', 'http://foo.com', array('Accept' => 'foo')),
+ new Response(200, array('Cache-Control' => 'stale-if-error=100', 'Vary' => 'Accept'))
+ ),
+ array(
+ new Request('GET', 'http://foo.com', array('Accept' => 'foo')),
+ new Response(200, array('Cache-Control' => 'stale-if-error', 'Vary' => 'Accept'))
+ )
+ );
+ }
+
+ /**
+ * @dataProvider staleProvider
+ */
+ public function testUsesStaleTimeDirectiveForTtd($request, $response)
+ {
+ $cache = $this->getCache();
+ $cache['storage']->cache($request, $response);
+ $data = $this->readAttribute($cache['cache'], 'data');
+ foreach ($data as $v) {
+ if (strpos($v, 'foo.com')) {
+ $u = unserialize($v);
+ $this->assertGreaterThan($u[1][4], $u[0][4]);
+ break;
+ }
+ }
+ }
+
+ public function testCanFilterCacheKeys()
+ {
+ $cache = $this->getCache();
+ $cache['request']->getQuery()->set('auth', 'foo');
+ $this->assertNull($cache['storage']->fetch($cache['request']));
+ $cache['request']->getParams()->set('cache.key_filter', 'auth');
+ $this->assertNotNull($cache['storage']->fetch($cache['request']));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php
new file mode 100644
index 0000000..de4d182
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\DefaultCanCacheStrategy;
+
+/**
+ * @covers Guzzle\Plugin\Cache\DefaultCanCacheStrategy
+ */
+class DefaultCanCacheStrategyTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testReturnsRequestcanCacheRequest()
+ {
+ $strategy = new DefaultCanCacheStrategy();
+ $request = new Request('GET', 'http://foo.com');
+ $this->assertTrue($strategy->canCacheRequest($request));
+ }
+
+ public function testDoesNotCacheNoStore()
+ {
+ $strategy = new DefaultCanCacheStrategy();
+ $request = new Request('GET', 'http://foo.com', array('cache-control' => 'no-store'));
+ $this->assertFalse($strategy->canCacheRequest($request));
+ }
+
+ public function testCanCacheResponse()
+ {
+ $response = $this->getMockBuilder('Guzzle\Http\Message\Response')
+ ->setMethods(array('canCache'))
+ ->setConstructorArgs(array(200))
+ ->getMock();
+ $response->expects($this->once())
+ ->method('canCache')
+ ->will($this->returnValue(true));
+ $strategy = new DefaultCanCacheStrategy();
+ $this->assertTrue($strategy->canCacheResponse($response));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php
new file mode 100644
index 0000000..0699cb2
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php
@@ -0,0 +1,248 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\ClientInterface;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Http\Exception\CurlException;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Plugin\Cache\CachePlugin;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Doctrine\Common\Cache\ArrayCache;
+use Guzzle\Plugin\Cache\DefaultCacheStorage;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Tests\Http\Server;
+
+/**
+ * @covers Guzzle\Plugin\Cache\DefaultRevalidation
+ * @group server
+ */
+class DefaultRevalidationTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected function getHttpDate($time)
+ {
+ return gmdate(ClientInterface::HTTP_DATE, strtotime($time));
+ }
+
+ /**
+ * Data provider to test cache revalidation
+ *
+ * @return array
+ */
+ public function cacheRevalidationDataProvider()
+ {
+ return array(
+ // Forces revalidation that passes
+ array(
+ true,
+ "Pragma: no-cache\r\n\r\n",
+ "HTTP/1.1 200 OK\r\nDate: " . $this->getHttpDate('-100 hours') . "\r\nContent-Length: 4\r\n\r\nData",
+ "HTTP/1.1 304 NOT MODIFIED\r\nCache-Control: max-age=2000000\r\nContent-Length: 0\r\n\r\n",
+ ),
+ // Forces revalidation that overwrites what is in cache
+ array(
+ false,
+ "\r\n",
+ "HTTP/1.1 200 OK\r\nCache-Control: must-revalidate, no-cache\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\nContent-Length: 4\r\n\r\nData",
+ "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nDatas",
+ "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nDate: " . $this->getHttpDate('now') . "\r\n\r\nDatas"
+ ),
+ // Throws an exception during revalidation
+ array(
+ false,
+ "\r\n",
+ "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nDate: " . $this->getHttpDate('-3 hours') . "\r\n\r\nData",
+ "HTTP/1.1 500 INTERNAL SERVER ERROR\r\nContent-Length: 0\r\n\r\n"
+ ),
+ // ETag mismatch
+ array(
+ false,
+ "\r\n",
+ "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nETag: \"123\"\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\n\r\nData",
+ "HTTP/1.1 304 NOT MODIFIED\r\nETag: \"123456\"\r\n\r\n",
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider cacheRevalidationDataProvider
+ */
+ public function testRevalidatesResponsesAgainstOriginServer($can, $request, $response, $validate = null, $result = null)
+ {
+ // Send some responses to the test server for cache validation
+ $server = $this->getServer();
+ $server->flush();
+
+ if ($validate) {
+ $server->enqueue($validate);
+ }
+
+ $request = RequestFactory::getInstance()->fromMessage("GET / HTTP/1.1\r\nHost: 127.0.0.1:" . $server->getPort() . "\r\n" . $request);
+ $response = Response::fromMessage($response);
+ $request->setClient(new Client());
+
+ $plugin = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache()));
+ $this->assertEquals(
+ $can,
+ $plugin->canResponseSatisfyRequest($request, $response),
+ '-> ' . $request . "\n" . $response
+ );
+
+ if ($result) {
+ $result = Response::fromMessage($result);
+ $result->removeHeader('Date');
+ $request->getResponse()->removeHeader('Date');
+ $request->getResponse()->removeHeader('Connection');
+ // Get rid of dates
+ $this->assertEquals((string) $result, (string) $request->getResponse());
+ }
+
+ if ($validate) {
+ $this->assertEquals(1, count($server->getReceivedRequests()));
+ }
+ }
+
+ public function testHandles404RevalidationResponses()
+ {
+ $request = new Request('GET', 'http://foo.com');
+ $request->setClient(new Client());
+ $badResponse = new Response(404, array(), 'Oh no!');
+ $badRequest = clone $request;
+ $badRequest->setResponse($badResponse, true);
+ $response = new Response(200, array(), 'foo');
+
+ // Seed the cache
+ $s = new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));
+ $s->cache($request, $response);
+ $this->assertNotNull($s->fetch($request));
+
+ $rev = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation')
+ ->setConstructorArgs(array($s))
+ ->setMethods(array('createRevalidationRequest'))
+ ->getMock();
+
+ $rev->expects($this->once())
+ ->method('createRevalidationRequest')
+ ->will($this->returnValue($badRequest));
+
+ try {
+ $rev->revalidate($request, $response);
+ $this->fail('Should have thrown an exception');
+ } catch (BadResponseException $e) {
+ $this->assertSame($badResponse, $e->getResponse());
+ $this->assertNull($s->fetch($request));
+ }
+ }
+
+ public function testCanRevalidateWithPlugin()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" .
+ "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Content-Length: 2\r\n\r\nhi",
+ "HTTP/1.0 304 Not Modified\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" .
+ "Content-Type: text/html; charset=UTF-8\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Age: 6302\r\n\r\n",
+ "HTTP/1.0 304 Not Modified\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" .
+ "Content-Type: text/html; charset=UTF-8\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Age: 6302\r\n\r\n",
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $client->addSubscriber(new CachePlugin());
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $this->assertEquals(3, count($this->getServer()->getReceivedRequests()));
+ }
+
+ public function testCanHandleRevalidationFailures()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $lm = gmdate('c', time() - 60);
+ $mock = new MockPlugin(array(
+ new Response(200, array(
+ 'Date' => $lm,
+ 'Cache-Control' => 'max-age=100, must-revalidate, stale-if-error=9999',
+ 'Last-Modified' => $lm,
+ 'Content-Length' => 2
+ ), 'hi'),
+ new CurlException('Bleh'),
+ new CurlException('Bleh')
+ ));
+ $client->addSubscriber(new CachePlugin());
+ $client->addSubscriber($mock);
+ $client->get()->send();
+ $response = $client->get()->send();
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('hi', $response->getBody(true));
+ $this->assertEquals(3, count($mock->getReceivedRequests()));
+ $this->assertEquals(0, count($mock->getQueue()));
+ }
+
+ public function testCanHandleStaleIfErrorWhenRevalidating()
+ {
+ $lm = gmdate('c', time() - 60);
+ $mock = new MockPlugin(array(
+ new Response(200, array(
+ 'Date' => $lm,
+ 'Cache-Control' => 'must-revalidate, max-age=0, stale-if-error=1200',
+ 'Last-Modified' => $lm,
+ 'Content-Length' => 2
+ ), 'hi'),
+ new CurlException('Oh no!'),
+ new CurlException('Oh no!')
+ ));
+ $cache = new CachePlugin();
+ $client = new Client('http://www.example.com');
+ $client->addSubscriber($cache);
+ $client->addSubscriber($mock);
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $response = $client->get()->send();
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertCount(0, $mock);
+ $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup'));
+ $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache'));
+ }
+
+ /**
+ * @group issue-437
+ */
+ public function testDoesNotTouchClosureListeners()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" .
+ "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Content-Length: 2\r\n\r\nhi",
+ "HTTP/1.0 304 Not Modified\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" .
+ "Content-Type: text/html; charset=UTF-8\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Age: 6302\r\n\r\n",
+ "HTTP/1.0 304 Not Modified\r\n" .
+ "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" .
+ "Content-Type: text/html; charset=UTF-8\r\n" .
+ "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" .
+ "Age: 6302\r\n\r\n",
+ ));
+ $client = new Client($this->getServer()->getUrl());
+ $client->addSubscriber(new CachePlugin());
+ $client->getEventDispatcher()->addListener('command.after_send', function(){});
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ $this->assertEquals(200, $client->get()->send()->getStatusCode());
+ }
+
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php
new file mode 100644
index 0000000..9af80f2
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\DenyRevalidation;
+
+/**
+ * @covers Guzzle\Plugin\Cache\DenyRevalidation
+ */
+class DenyRevalidationTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDeniesRequestRevalidation()
+ {
+ $deny = new DenyRevalidation();
+ $this->assertFalse($deny->revalidate(new Request('GET', 'http://foo.com'), new Response(200)));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php
new file mode 100644
index 0000000..4bcc04b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cache;
+
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cache\SkipRevalidation;
+
+/**
+ * @covers Guzzle\Plugin\Cache\SkipRevalidation
+ */
+class SkipRevalidationTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testSkipsRequestRevalidation()
+ {
+ $skip = new SkipRevalidation();
+ $this->assertTrue($skip->revalidate(new Request('GET', 'http://foo.com'), new Response(200)));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php
new file mode 100644
index 0000000..5d0f668
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php
@@ -0,0 +1,385 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cookie\CookieJar;
+
+use Guzzle\Plugin\Cookie\Cookie;
+use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Message\Request;
+
+/**
+ * @covers Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar
+ */
+class ArrayCookieJarTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @var ArrayCookieJar
+ */
+ private $jar;
+
+ public function setUp()
+ {
+ $this->jar = new ArrayCookieJar();
+ }
+
+ protected function getTestCookies()
+ {
+ return array(
+ new Cookie(array('name' => 'foo', 'value' => 'bar', 'domain' => 'foo.com', 'path' => '/', 'discard' => true)),
+ new Cookie(array('name' => 'test', 'value' => '123', 'domain' => 'baz.com', 'path' => '/foo', 'expires' => 2)),
+ new Cookie(array('name' => 'you', 'value' => '123', 'domain' => 'bar.com', 'path' => '/boo', 'expires' => time() + 1000))
+ );
+ }
+
+ /**
+ * Provides test data for cookie cookieJar retrieval
+ */
+ public function getCookiesDataProvider()
+ {
+ return array(
+ array(array('foo', 'baz', 'test', 'muppet', 'googoo'), '', '', '', false),
+ array(array('foo', 'baz', 'muppet', 'googoo'), '', '', '', true),
+ array(array('googoo'), 'www.example.com', '', '', false),
+ array(array('muppet', 'googoo'), 'test.y.example.com', '', '', false),
+ array(array('foo', 'baz'), 'example.com', '', '', false),
+ array(array('muppet'), 'x.y.example.com', '/acme/', '', false),
+ array(array('muppet'), 'x.y.example.com', '/acme/test/', '', false),
+ array(array('googoo'), 'x.y.example.com', '/test/acme/test/', '', false),
+ array(array('foo', 'baz'), 'example.com', '', '', false),
+ array(array('baz'), 'example.com', '', 'baz', false),
+ );
+ }
+
+ public function testStoresAndRetrievesCookies()
+ {
+ $cookies = $this->getTestCookies();
+ foreach ($cookies as $cookie) {
+ $this->assertTrue($this->jar->add($cookie));
+ }
+
+ $this->assertEquals(3, count($this->jar));
+ $this->assertEquals(3, count($this->jar->getIterator()));
+ $this->assertEquals($cookies, $this->jar->all(null, null, null, false, false));
+ }
+
+ public function testRemovesExpiredCookies()
+ {
+ $cookies = $this->getTestCookies();
+ foreach ($this->getTestCookies() as $cookie) {
+ $this->jar->add($cookie);
+ }
+ $this->jar->removeExpired();
+ $this->assertEquals(array($cookies[0], $cookies[2]), $this->jar->all());
+ }
+
+ public function testRemovesTemporaryCookies()
+ {
+ $cookies = $this->getTestCookies();
+ foreach ($this->getTestCookies() as $cookie) {
+ $this->jar->add($cookie);
+ }
+ $this->jar->removeTemporary();
+ $this->assertEquals(array($cookies[2]), $this->jar->all());
+ }
+
+ public function testIsSerializable()
+ {
+ $this->assertEquals('[]', $this->jar->serialize());
+ $this->jar->unserialize('[]');
+ $this->assertEquals(array(), $this->jar->all());
+
+ $cookies = $this->getTestCookies();
+ foreach ($this->getTestCookies() as $cookie) {
+ $this->jar->add($cookie);
+ }
+
+ // Remove discard and expired cookies
+ $serialized = $this->jar->serialize();
+ $data = json_decode($serialized, true);
+ $this->assertEquals(1, count($data));
+
+ $a = new ArrayCookieJar();
+ $a->unserialize($serialized);
+ $this->assertEquals(1, count($a));
+ }
+
+ public function testRemovesSelectively()
+ {
+ $cookies = $this->getTestCookies();
+ foreach ($this->getTestCookies() as $cookie) {
+ $this->jar->add($cookie);
+ }
+
+ // Remove foo.com cookies
+ $this->jar->remove('foo.com');
+ $this->assertEquals(2, count($this->jar));
+ // Try again, removing no further cookies
+ $this->jar->remove('foo.com');
+ $this->assertEquals(2, count($this->jar));
+
+ // Remove bar.com cookies with path of /boo
+ $this->jar->remove('bar.com', '/boo');
+ $this->assertEquals(1, count($this->jar));
+
+ // Remove cookie by name
+ $this->jar->remove(null, null, 'test');
+ $this->assertEquals(0, count($this->jar));
+ }
+
+ public function testDoesNotAddIncompleteCookies()
+ {
+ $this->assertEquals(false, $this->jar->add(new Cookie()));
+ $this->assertFalse($this->jar->add(new Cookie(array(
+ 'name' => 'foo'
+ ))));
+ $this->assertFalse($this->jar->add(new Cookie(array(
+ 'name' => false
+ ))));
+ $this->assertFalse($this->jar->add(new Cookie(array(
+ 'name' => true
+ ))));
+ $this->assertFalse($this->jar->add(new Cookie(array(
+ 'name' => 'foo',
+ 'domain' => 'foo.com'
+ ))));
+ }
+
+ public function testDoesAddValidCookies()
+ {
+ $this->assertTrue($this->jar->add(new Cookie(array(
+ 'name' => 'foo',
+ 'domain' => 'foo.com',
+ 'value' => 0
+ ))));
+ $this->assertTrue($this->jar->add(new Cookie(array(
+ 'name' => 'foo',
+ 'domain' => 'foo.com',
+ 'value' => 0.0
+ ))));
+ $this->assertTrue($this->jar->add(new Cookie(array(
+ 'name' => 'foo',
+ 'domain' => 'foo.com',
+ 'value' => '0'
+ ))));
+ }
+
+ public function testOverwritesCookiesThatAreOlderOrDiscardable()
+ {
+ $t = time() + 1000;
+ $data = array(
+ 'name' => 'foo',
+ 'value' => 'bar',
+ 'domain' => '.example.com',
+ 'path' => '/',
+ 'max_age' => '86400',
+ 'port' => array(80, 8080),
+ 'version' => '1',
+ 'secure' => true,
+ 'discard' => true,
+ 'expires' => $t
+ );
+
+ // Make sure that the discard cookie is overridden with the non-discard
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+
+ unset($data['discard']);
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+ $this->assertEquals(1, count($this->jar));
+
+ $c = $this->jar->all();
+ $this->assertEquals(false, $c[0]->getDiscard());
+
+ // Make sure it doesn't duplicate the cookie
+ $this->jar->add(new Cookie($data));
+ $this->assertEquals(1, count($this->jar));
+
+ // Make sure the more future-ful expiration date supersede the other
+ $data['expires'] = time() + 2000;
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+ $this->assertEquals(1, count($this->jar));
+ $c = $this->jar->all();
+ $this->assertNotEquals($t, $c[0]->getExpires());
+ }
+
+ public function testOverwritesCookiesThatHaveChanged()
+ {
+ $t = time() + 1000;
+ $data = array(
+ 'name' => 'foo',
+ 'value' => 'bar',
+ 'domain' => '.example.com',
+ 'path' => '/',
+ 'max_age' => '86400',
+ 'port' => array(80, 8080),
+ 'version' => '1',
+ 'secure' => true,
+ 'discard' => true,
+ 'expires' => $t
+ );
+
+ // Make sure that the discard cookie is overridden with the non-discard
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+
+ $data['value'] = 'boo';
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+ $this->assertEquals(1, count($this->jar));
+
+ // Changing the value plus a parameter also must overwrite the existing one
+ $data['value'] = 'zoo';
+ $data['secure'] = false;
+ $this->assertTrue($this->jar->add(new Cookie($data)));
+ $this->assertEquals(1, count($this->jar));
+
+ $c = $this->jar->all();
+ $this->assertEquals('zoo', $c[0]->getValue());
+ }
+
+ public function testAddsCookiesFromResponseWithNoRequest()
+ {
+ $response = new Response(200, array(
+ 'Set-Cookie' => array(
+ "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT; path=/; domain=127.0.0.1",
+ "FPCK3=AgBNbvoQAGpGEABZLRAAbFsQAF1tEABkDhAAeO0=; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1",
+ "CH=deleted; expires=Wed, 03-Mar-2010 02:17:39 GMT; path=/; domain=127.0.0.1",
+ "CH=AgBNbvoQAAEcEAApuhAAMJcQADQvEAAvGxAALe0QAD6uEAATwhAAC1AQAC8t; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1"
+ )
+ ));
+
+ $this->jar->addCookiesFromResponse($response);
+ $this->assertEquals(3, count($this->jar));
+ $this->assertEquals(1, count($this->jar->all(null, null, 'fpc')));
+ $this->assertEquals(1, count($this->jar->all(null, null, 'FPCK3')));
+ $this->assertEquals(1, count($this->jar->all(null, null, 'CH')));
+ }
+
+ public function testAddsCookiesFromResponseWithRequest()
+ {
+ $response = new Response(200, array(
+ 'Set-Cookie' => "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT;"
+ ));
+ $request = new Request('GET', 'http://www.example.com');
+ $this->jar->addCookiesFromResponse($response, $request);
+ $this->assertEquals(1, count($this->jar));
+ }
+
+ public function getMatchingCookiesDataProvider()
+ {
+ return array(
+ array('https://example.com', array(0)),
+ array('http://example.com', array()),
+ array('https://example.com:8912', array()),
+ array('https://foo.example.com', array(0)),
+ array('http://foo.example.com/test/acme/', array(4))
+ );
+ }
+
+ /**
+ * @dataProvider getMatchingCookiesDataProvider
+ */
+ public function testReturnsCookiesMatchingRequests($url, $cookies)
+ {
+ $bag = array(
+ new Cookie(array(
+ 'name' => 'foo',
+ 'value' => 'bar',
+ 'domain' => 'example.com',
+ 'path' => '/',
+ 'max_age' => '86400',
+ 'port' => array(443, 8080),
+ 'version' => '1',
+ 'secure' => true
+ )),
+ new Cookie(array(
+ 'name' => 'baz',
+ 'value' => 'foobar',
+ 'domain' => 'example.com',
+ 'path' => '/',
+ 'max_age' => '86400',
+ 'port' => array(80, 8080),
+ 'version' => '1',
+ 'secure' => true
+ )),
+ new Cookie(array(
+ 'name' => 'test',
+ 'value' => '123',
+ 'domain' => 'www.foobar.com',
+ 'path' => '/path/',
+ 'discard' => true
+ )),
+ new Cookie(array(
+ 'name' => 'muppet',
+ 'value' => 'cookie_monster',
+ 'domain' => '.y.example.com',
+ 'path' => '/acme/',
+ 'comment' => 'Comment goes here...',
+ 'expires' => time() + 86400
+ )),
+ new Cookie(array(
+ 'name' => 'googoo',
+ 'value' => 'gaga',
+ 'domain' => '.example.com',
+ 'path' => '/test/acme/',
+ 'max_age' => 1500,
+ 'version' => 2
+ ))
+ );
+
+ foreach ($bag as $cookie) {
+ $this->jar->add($cookie);
+ }
+
+ $request = new Request('GET', $url);
+ $results = $this->jar->getMatchingCookies($request);
+ $this->assertEquals(count($cookies), count($results));
+ foreach ($cookies as $i) {
+ $this->assertContains($bag[$i], $results);
+ }
+ }
+
+ /**
+ * @expectedException \Guzzle\Plugin\Cookie\Exception\InvalidCookieException
+ * @expectedExceptionMessage The cookie name must not contain invalid characters: abc:@123
+ */
+ public function testThrowsExceptionWithStrictMode()
+ {
+ $a = new ArrayCookieJar();
+ $a->setStrictMode(true);
+ $a->add(new Cookie(array(
+ 'name' => 'abc:@123',
+ 'value' => 'foo',
+ 'domain' => 'bar'
+ )));
+ }
+
+ public function testRemoveExistingCookieIfEmpty()
+ {
+ // Add a cookie that should not be affected
+ $a = new Cookie(array(
+ 'name' => 'foo',
+ 'value' => 'nope',
+ 'domain' => 'foo.com',
+ 'path' => '/abc'
+ ));
+ $this->jar->add($a);
+
+ $data = array(
+ 'name' => 'foo',
+ 'value' => 'bar',
+ 'domain' => 'foo.com',
+ 'path' => '/'
+ );
+
+ $b = new Cookie($data);
+ $this->assertTrue($this->jar->add($b));
+ $this->assertEquals(2, count($this->jar));
+
+ // Try to re-set the same cookie with no value: assert that cookie is not added
+ $data['value'] = null;
+ $this->assertFalse($this->jar->add(new Cookie($data)));
+ // assert that original cookie has been deleted
+ $cookies = $this->jar->all('foo.com');
+ $this->assertTrue(in_array($a, $cookies, true));
+ $this->assertFalse(in_array($b, $cookies, true));
+ $this->assertEquals(1, count($this->jar));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php
new file mode 100644
index 0000000..ac9471f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cookie\CookieJar;
+
+use Guzzle\Plugin\Cookie\Cookie;
+use Guzzle\Plugin\Cookie\CookieJar\FileCookieJar;
+
+/**
+ * @covers Guzzle\Plugin\Cookie\CookieJar\FileCookieJar
+ */
+class FileCookieJarTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private $file;
+
+ public function setUp()
+ {
+ $this->file = tempnam('/tmp', 'file-cookies');
+ }
+
+ public function testLoadsFromFileFile()
+ {
+ $jar = new FileCookieJar($this->file);
+ $this->assertEquals(array(), $jar->all());
+ unlink($this->file);
+ }
+
+ public function testPersistsToFileFile()
+ {
+ $jar = new FileCookieJar($this->file);
+ $jar->add(new Cookie(array(
+ 'name' => 'foo',
+ 'value' => 'bar',
+ 'domain' => 'foo.com',
+ 'expires' => time() + 1000
+ )));
+ $jar->add(new Cookie(array(
+ 'name' => 'baz',
+ 'value' => 'bar',
+ 'domain' => 'foo.com',
+ 'expires' => time() + 1000
+ )));
+ $jar->add(new Cookie(array(
+ 'name' => 'boo',
+ 'value' => 'bar',
+ 'domain' => 'foo.com',
+ )));
+
+ $this->assertEquals(3, count($jar));
+ unset($jar);
+
+ // Make sure it wrote to the file
+ $contents = file_get_contents($this->file);
+ $this->assertNotEmpty($contents);
+
+ // Load the cookieJar from the file
+ $jar = new FileCookieJar($this->file);
+
+ // Weeds out temporary and session cookies
+ $this->assertEquals(2, count($jar));
+ unset($jar);
+ unlink($this->file);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php
new file mode 100644
index 0000000..f8c175c
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cookie;
+
+use Guzzle\Common\Event;
+use Guzzle\Plugin\Cookie\Cookie;
+use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Cookie\CookiePlugin;
+
+/**
+ * @group server
+ * @covers Guzzle\Plugin\Cookie\CookiePlugin
+ */
+class CookiePluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testExtractsAndStoresCookies()
+ {
+ $response = new Response(200);
+ $mock = $this->getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar')
+ ->setMethods(array('addCookiesFromResponse'))
+ ->getMock();
+
+ $mock->expects($this->exactly(1))
+ ->method('addCookiesFromResponse')
+ ->with($response);
+
+ $plugin = new CookiePlugin($mock);
+ $plugin->onRequestSent(new Event(array(
+ 'response' => $response
+ )));
+ }
+
+ public function testAddsCookiesToRequests()
+ {
+ $cookie = new Cookie(array(
+ 'name' => 'foo',
+ 'value' => 'bar'
+ ));
+
+ $mock = $this->getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar')
+ ->setMethods(array('getMatchingCookies'))
+ ->getMock();
+
+ $mock->expects($this->once())
+ ->method('getMatchingCookies')
+ ->will($this->returnValue(array($cookie)));
+
+ $plugin = new CookiePlugin($mock);
+
+ $client = new Client();
+ $client->getEventDispatcher()->addSubscriber($plugin);
+
+ $request = $client->get('http://www.example.com');
+ $plugin->onRequestBeforeSend(new Event(array(
+ 'request' => $request
+ )));
+
+ $this->assertEquals('bar', $request->getCookie('foo'));
+ }
+
+ public function testCookiesAreExtractedFromRedirectResponses()
+ {
+ $plugin = new CookiePlugin(new ArrayCookieJar());
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 302 Moved Temporarily\r\n" .
+ "Set-Cookie: test=583551; expires=Wednesday, 23-Mar-2050 19:49:45 GMT; path=/\r\n" .
+ "Location: /redirect\r\n\r\n",
+ "HTTP/1.1 200 OK\r\n" .
+ "Content-Length: 0\r\n\r\n",
+ "HTTP/1.1 200 OK\r\n" .
+ "Content-Length: 0\r\n\r\n"
+ ));
+
+ $client = new Client($this->getServer()->getUrl());
+ $client->getEventDispatcher()->addSubscriber($plugin);
+
+ $client->get()->send();
+ $request = $client->get();
+ $request->send();
+ $this->assertEquals('test=583551', $request->getHeader('Cookie'));
+
+ $requests = $this->getServer()->getReceivedRequests(true);
+ // Confirm subsequent requests have the cookie.
+ $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie'));
+ // Confirm the redirected request has the cookie.
+ $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie'));
+ }
+
+ public function testCookiesAreNotAddedWhenParamIsSet()
+ {
+ $jar = new ArrayCookieJar();
+ $plugin = new CookiePlugin($jar);
+
+ $jar->add(new Cookie(array(
+ 'domain' => 'example.com',
+ 'path' => '/',
+ 'name' => 'test',
+ 'value' => 'hi',
+ 'expires' => time() + 3600
+ )));
+
+ $client = new Client('http://example.com');
+ $client->getEventDispatcher()->addSubscriber($plugin);
+
+ // Ensure that it is normally added
+ $request = $client->get();
+ $request->setResponse(new Response(200), true);
+ $request->send();
+ $this->assertEquals('hi', $request->getCookie('test'));
+
+ // Now ensure that it is not added
+ $request = $client->get();
+ $request->getParams()->set('cookies.disable', true);
+ $request->setResponse(new Response(200), true);
+ $request->send();
+ $this->assertNull($request->getCookie('test'));
+ }
+
+ public function testProvidesCookieJar()
+ {
+ $jar = new ArrayCookieJar();
+ $plugin = new CookiePlugin($jar);
+ $this->assertSame($jar, $plugin->getCookieJar());
+ }
+
+ public function testEscapesCookieDomains()
+ {
+ $cookie = new Cookie(array('domain' => '/foo/^$[A-Z]+/'));
+ $this->assertFalse($cookie->matchesDomain('foo'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php
new file mode 100644
index 0000000..9fb0b43
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Cookie;
+
+use Guzzle\Plugin\Cookie\Cookie;
+
+/**
+ * @covers Guzzle\Plugin\Cookie\Cookie
+ */
+class CookieTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testInitializesDefaultValues()
+ {
+ $cookie = new Cookie();
+ $this->assertEquals('/', $cookie->getPath());
+ $this->assertEquals(array(), $cookie->getPorts());
+ }
+
+ public function testConvertsDateTimeMaxAgeToUnixTimestamp()
+ {
+ $cookie = new Cookie(array(
+ 'expires' => 'November 20, 1984'
+ ));
+ $this->assertTrue(is_numeric($cookie->getExpires()));
+ }
+
+ public function testAddsExpiresBasedOnMaxAge()
+ {
+ $t = time();
+ $cookie = new Cookie(array(
+ 'max_age' => 100
+ ));
+ $this->assertEquals($t + 100, $cookie->getExpires());
+ }
+
+ public function testHoldsValues()
+ {
+ $t = time();
+ $data = array(
+ 'name' => 'foo',
+ 'value' => 'baz',
+ 'path' => '/bar',
+ 'domain' => 'baz.com',
+ 'expires' => $t,
+ 'max_age' => 100,
+ 'comment' => 'Hi',
+ 'comment_url' => 'foo.com',
+ 'port' => array(1, 2),
+ 'version' => 2,
+ 'secure' => true,
+ 'discard' => true,
+ 'http_only' => true,
+ 'data' => array(
+ 'foo' => 'baz',
+ 'bar' => 'bam'
+ )
+ );
+
+ $cookie = new Cookie($data);
+ $this->assertEquals($data, $cookie->toArray());
+
+ $this->assertEquals('foo', $cookie->getName());
+ $this->assertEquals('baz', $cookie->getValue());
+ $this->assertEquals('baz.com', $cookie->getDomain());
+ $this->assertEquals('/bar', $cookie->getPath());
+ $this->assertEquals($t, $cookie->getExpires());
+ $this->assertEquals(100, $cookie->getMaxAge());
+ $this->assertEquals('Hi', $cookie->getComment());
+ $this->assertEquals('foo.com', $cookie->getCommentUrl());
+ $this->assertEquals(array(1, 2), $cookie->getPorts());
+ $this->assertEquals(2, $cookie->getVersion());
+ $this->assertTrue($cookie->getSecure());
+ $this->assertTrue($cookie->getDiscard());
+ $this->assertTrue($cookie->getHttpOnly());
+ $this->assertEquals('baz', $cookie->getAttribute('foo'));
+ $this->assertEquals('bam', $cookie->getAttribute('bar'));
+ $this->assertEquals(array(
+ 'foo' => 'baz',
+ 'bar' => 'bam'
+ ), $cookie->getAttributes());
+
+ $cookie->setName('a')
+ ->setValue('b')
+ ->setPath('c')
+ ->setDomain('bar.com')
+ ->setExpires(10)
+ ->setMaxAge(200)
+ ->setComment('e')
+ ->setCommentUrl('f')
+ ->setPorts(array(80))
+ ->setVersion(3)
+ ->setSecure(false)
+ ->setHttpOnly(false)
+ ->setDiscard(false)
+ ->setAttribute('snoop', 'dog');
+
+ $this->assertEquals('a', $cookie->getName());
+ $this->assertEquals('b', $cookie->getValue());
+ $this->assertEquals('c', $cookie->getPath());
+ $this->assertEquals('bar.com', $cookie->getDomain());
+ $this->assertEquals(10, $cookie->getExpires());
+ $this->assertEquals(200, $cookie->getMaxAge());
+ $this->assertEquals('e', $cookie->getComment());
+ $this->assertEquals('f', $cookie->getCommentUrl());
+ $this->assertEquals(array(80), $cookie->getPorts());
+ $this->assertEquals(3, $cookie->getVersion());
+ $this->assertFalse($cookie->getSecure());
+ $this->assertFalse($cookie->getDiscard());
+ $this->assertFalse($cookie->getHttpOnly());
+ $this->assertEquals('dog', $cookie->getAttribute('snoop'));
+ }
+
+ public function testDeterminesIfExpired()
+ {
+ $c = new Cookie();
+ $c->setExpires(10);
+ $this->assertTrue($c->isExpired());
+ $c->setExpires(time() + 10000);
+ $this->assertFalse($c->isExpired());
+ }
+
+ public function testMatchesPorts()
+ {
+ $cookie = new Cookie();
+ // Always matches when nothing is set
+ $this->assertTrue($cookie->matchesPort(2));
+
+ $cookie->setPorts(array(1, 2));
+ $this->assertTrue($cookie->matchesPort(2));
+ $this->assertFalse($cookie->matchesPort(100));
+ }
+
+ public function testMatchesDomain()
+ {
+ $cookie = new Cookie();
+ $this->assertTrue($cookie->matchesDomain('baz.com'));
+
+ $cookie->setDomain('baz.com');
+ $this->assertTrue($cookie->matchesDomain('baz.com'));
+ $this->assertFalse($cookie->matchesDomain('bar.com'));
+
+ $cookie->setDomain('.baz.com');
+ $this->assertTrue($cookie->matchesDomain('.baz.com'));
+ $this->assertTrue($cookie->matchesDomain('foo.baz.com'));
+ $this->assertFalse($cookie->matchesDomain('baz.bar.com'));
+ $this->assertTrue($cookie->matchesDomain('baz.com'));
+
+ $cookie->setDomain('.127.0.0.1');
+ $this->assertTrue($cookie->matchesDomain('127.0.0.1'));
+
+ $cookie->setDomain('127.0.0.1');
+ $this->assertTrue($cookie->matchesDomain('127.0.0.1'));
+
+ $cookie->setDomain('.com.');
+ $this->assertFalse($cookie->matchesDomain('baz.com'));
+
+ $cookie->setDomain('.local');
+ $this->assertTrue($cookie->matchesDomain('example.local'));
+ }
+
+ public function testMatchesPath()
+ {
+ $cookie = new Cookie();
+ $this->assertTrue($cookie->matchesPath('/foo'));
+
+ $cookie->setPath('/foo');
+
+ // o The cookie-path and the request-path are identical.
+ $this->assertTrue($cookie->matchesPath('/foo'));
+ $this->assertFalse($cookie->matchesPath('/bar'));
+
+ // o The cookie-path is a prefix of the request-path, and the first
+ // character of the request-path that is not included in the cookie-
+ // path is a %x2F ("/") character.
+ $this->assertTrue($cookie->matchesPath('/foo/bar'));
+ $this->assertFalse($cookie->matchesPath('/fooBar'));
+
+ // o The cookie-path is a prefix of the request-path, and the last
+ // character of the cookie-path is %x2F ("/").
+ $cookie->setPath('/foo/');
+ $this->assertTrue($cookie->matchesPath('/foo/bar'));
+ $this->assertFalse($cookie->matchesPath('/fooBaz'));
+ $this->assertFalse($cookie->matchesPath('/foo'));
+
+ }
+
+ public function cookieValidateProvider()
+ {
+ return array(
+ array('foo', 'baz', 'bar', true),
+ array('0', '0', '0', true),
+ array('', 'baz', 'bar', 'The cookie name must not be empty'),
+ array('foo', '', 'bar', 'The cookie value must not be empty'),
+ array('foo', 'baz', '', 'The cookie domain must not be empty'),
+ array('foo\\', 'baz', '0', 'The cookie name must not contain invalid characters: foo\\'),
+ );
+ }
+
+ /**
+ * @dataProvider cookieValidateProvider
+ */
+ public function testValidatesCookies($name, $value, $domain, $result)
+ {
+ $cookie = new Cookie(array(
+ 'name' => $name,
+ 'value' => $value,
+ 'domain' => $domain
+ ));
+ $this->assertSame($result, $cookie->validate());
+ }
+
+ public function testCreatesInvalidCharacterString()
+ {
+ $m = new \ReflectionMethod('Guzzle\Plugin\Cookie\Cookie', 'getInvalidCharacters');
+ $m->setAccessible(true);
+ $p = new \ReflectionProperty('Guzzle\Plugin\Cookie\Cookie', 'invalidCharString');
+ $p->setAccessible(true);
+ $p->setValue('');
+ // Expects a string containing 51 invalid characters
+ $this->assertEquals(51, strlen($m->invoke($m)));
+ $this->assertContains('@', $m->invoke($m));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php
new file mode 100644
index 0000000..2a4b49e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\CurlAuth;
+
+use Guzzle\Common\Version;
+use Guzzle\Plugin\CurlAuth\CurlAuthPlugin;
+use Guzzle\Http\Client;
+
+/**
+ * @covers Guzzle\Plugin\CurlAuth\CurlAuthPlugin
+ */
+class CurlAuthPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testAddsBasicAuthentication()
+ {
+ Version::$emitWarnings = false;
+ $plugin = new CurlAuthPlugin('michael', 'test');
+ $client = new Client('http://www.test.com/');
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $request = $client->get('/');
+ $this->assertEquals('michael', $request->getUsername());
+ $this->assertEquals('test', $request->getPassword());
+ Version::$emitWarnings = true;
+ }
+
+ public function testAddsDigestAuthentication()
+ {
+ Version::$emitWarnings = false;
+ $plugin = new CurlAuthPlugin('julian', 'test', CURLAUTH_DIGEST);
+ $client = new Client('http://www.test.com/');
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $request = $client->get('/');
+ $this->assertEquals('julian', $request->getUsername());
+ $this->assertEquals('test', $request->getPassword());
+ $this->assertEquals('julian:test', $request->getCurlOptions()->get(CURLOPT_USERPWD));
+ $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH));
+ Version::$emitWarnings = true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php
new file mode 100644
index 0000000..6f94186
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\ErrorResponse;
+
+use Guzzle\Service\Client;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Tests\Mock\ErrorResponseMock;
+
+/**
+ * @covers \Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin
+ */
+class ErrorResponsePluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $client;
+
+ public static function tearDownAfterClass()
+ {
+ self::getServer()->flush();
+ }
+
+ public function setUp()
+ {
+ $mockError = 'Guzzle\Tests\Mock\ErrorResponseMock';
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'works' => array(
+ 'httpMethod' => 'GET',
+ 'errorResponses' => array(
+ array('code' => 500, 'class' => $mockError),
+ array('code' => 503, 'reason' => 'foo', 'class' => $mockError),
+ array('code' => 200, 'reason' => 'Error!', 'class' => $mockError)
+ )
+ ),
+ 'bad_class' => array(
+ 'httpMethod' => 'GET',
+ 'errorResponses' => array(
+ array('code' => 500, 'class' => 'Does\\Not\\Exist')
+ )
+ ),
+ 'does_not_implement' => array(
+ 'httpMethod' => 'GET',
+ 'errorResponses' => array(
+ array('code' => 500, 'class' => __CLASS__)
+ )
+ ),
+ 'no_errors' => array('httpMethod' => 'GET'),
+ 'no_class' => array(
+ 'httpMethod' => 'GET',
+ 'errorResponses' => array(
+ array('code' => 500)
+ )
+ ),
+ )
+ ));
+ $this->client = new Client($this->getServer()->getUrl());
+ $this->client->setDescription($description);
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException
+ */
+ public function testSkipsWhenErrorResponsesIsNotSet()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('no_errors')->execute();
+ }
+
+ public function testSkipsWhenErrorResponsesIsNotSetAndAllowsSuccess()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('no_errors')->execute();
+ }
+
+ /**
+ * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException
+ * @expectedExceptionMessage Does\Not\Exist does not exist
+ */
+ public function testEnsuresErrorResponseExists()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('bad_class')->execute();
+ }
+
+ /**
+ * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException
+ * @expectedExceptionMessage must implement Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface
+ */
+ public function testEnsuresErrorResponseImplementsInterface()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('does_not_implement')->execute();
+ }
+
+ public function testThrowsSpecificErrorResponseOnMatch()
+ {
+ try {
+ $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $command = $this->client->getCommand('works');
+ $command->execute();
+ $this->fail('Exception not thrown');
+ } catch (ErrorResponseMock $e) {
+ $this->assertSame($command, $e->command);
+ $this->assertEquals(500, $e->response->getStatusCode());
+ }
+ }
+
+ /**
+ * @expectedException \Guzzle\Tests\Mock\ErrorResponseMock
+ */
+ public function testThrowsWhenCodeAndPhraseMatch()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 Error!\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('works')->execute();
+ }
+
+ public function testSkipsWhenReasonDoesNotMatch()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('works')->execute();
+ }
+
+ public function testSkipsWhenNoClassIsSet()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $this->client->addSubscriber(new ErrorResponsePlugin());
+ $this->client->getCommand('no_class')->execute();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php
new file mode 100644
index 0000000..41aa673
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\History;
+
+use Guzzle\Http\Client;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\History\HistoryPlugin;
+use Guzzle\Plugin\Mock\MockPlugin;
+
+/**
+ * @covers Guzzle\Plugin\History\HistoryPlugin
+ */
+class HistoryPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * Adds multiple requests to a plugin
+ *
+ * @param HistoryPlugin $h Plugin
+ * @param int $num Number of requests to add
+ *
+ * @return array
+ */
+ protected function addRequests(HistoryPlugin $h, $num)
+ {
+ $requests = array();
+ $client = new Client('http://127.0.0.1/');
+ for ($i = 0; $i < $num; $i++) {
+ $requests[$i] = $client->get();
+ $requests[$i]->setResponse(new Response(200), true);
+ $requests[$i]->send();
+ $h->add($requests[$i]);
+ }
+
+ return $requests;
+ }
+
+ public function testDescribesSubscribedEvents()
+ {
+ $this->assertInternalType('array', HistoryPlugin::getSubscribedEvents());
+ }
+
+ public function testMaintainsLimitValue()
+ {
+ $h = new HistoryPlugin();
+ $this->assertSame($h, $h->setLimit(10));
+ $this->assertEquals(10, $h->getLimit());
+ }
+
+ public function testAddsRequests()
+ {
+ $h = new HistoryPlugin();
+ $requests = $this->addRequests($h, 1);
+ $this->assertEquals(1, count($h));
+ $i = $h->getIterator();
+ $this->assertEquals(1, count($i));
+ $this->assertEquals($requests[0], $i[0]);
+ }
+
+ /**
+ * @depends testAddsRequests
+ */
+ public function testMaintainsLimit()
+ {
+ $h = new HistoryPlugin();
+ $h->setLimit(2);
+ $requests = $this->addRequests($h, 3);
+ $this->assertEquals(2, count($h));
+ $i = 0;
+ foreach ($h as $request) {
+ if ($i > 0) {
+ $this->assertSame($requests[$i], $request);
+ }
+ }
+ }
+
+ public function testReturnsLastRequest()
+ {
+ $h = new HistoryPlugin();
+ $requests = $this->addRequests($h, 5);
+ $this->assertSame(end($requests), $h->getLastRequest());
+ }
+
+ public function testReturnsLastResponse()
+ {
+ $h = new HistoryPlugin();
+ $requests = $this->addRequests($h, 5);
+ $this->assertSame(end($requests)->getResponse(), $h->getLastResponse());
+ }
+
+ public function testClearsHistory()
+ {
+ $h = new HistoryPlugin();
+ $requests = $this->addRequests($h, 5);
+ $this->assertEquals(5, count($h));
+ $h->clear();
+ $this->assertEquals(0, count($h));
+ }
+
+ /**
+ * @depends testAddsRequests
+ */
+ public function testUpdatesAddRequests()
+ {
+ $h = new HistoryPlugin();
+ $client = new Client('http://127.0.0.1/');
+ $client->getEventDispatcher()->addSubscriber($h);
+
+ $request = $client->get();
+ $request->setResponse(new Response(200), true);
+ $request->send();
+
+ $this->assertSame($request, $h->getLastRequest());
+ }
+
+ public function testCanCastToString()
+ {
+ $client = new Client('http://127.0.0.1/');
+ $h = new HistoryPlugin();
+ $client->getEventDispatcher()->addSubscriber($h);
+
+ $mock = new MockPlugin(array(
+ new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)),
+ new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)),
+ new Response(200, array('Content-Length' => '2'), 'HI')
+ ));
+
+ $client->getEventDispatcher()->addSubscriber($mock);
+ $request = $client->get();
+ $request->send();
+ $this->assertEquals(3, count($h));
+ $this->assertEquals(3, count($mock->getReceivedRequests()));
+
+ $h = str_replace("\r", '', $h);
+ $this->assertContains("> GET / HTTP/1.1\nHost: 127.0.0.1\nUser-Agent:", $h);
+ $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h);
+ $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h);
+ $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php
new file mode 100644
index 0000000..ad663a5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Log;
+
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Http\Client;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Log\LogPlugin;
+use Guzzle\Common\Event;
+
+/**
+ * @group server
+ * @covers Guzzle\Plugin\Log\LogPlugin
+ */
+class LogPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $adapter;
+
+ public function setUp()
+ {
+ $this->adapter = new ClosureLogAdapter(function ($message) {
+ echo $message;
+ });
+ }
+
+ public function testIgnoresCurlEventsWhenNotWiringBodies()
+ {
+ $p = new LogPlugin($this->adapter);
+ $this->assertNotEmpty($p->getSubscribedEvents());
+ $event = new Event(array('request' => new Request('GET', 'http://foo.com')));
+ $p->onCurlRead($event);
+ $p->onCurlWrite($event);
+ $p->onRequestBeforeSend($event);
+ }
+
+ public function testLogsWhenComplete()
+ {
+ $output = '';
+ $p = new LogPlugin(new ClosureLogAdapter(function ($message) use (&$output) {
+ $output = $message;
+ }), '{method} {resource} | {code} {res_body}');
+
+ $p->onRequestSent(new Event(array(
+ 'request' => new Request('GET', 'http://foo.com'),
+ 'response' => new Response(200, array(), 'Foo')
+ )));
+
+ $this->assertEquals('GET / | 200 Foo', $output);
+ }
+
+ public function testWiresBodiesWhenNeeded()
+ {
+ $client = new Client($this->getServer()->getUrl());
+ $plugin = new LogPlugin($this->adapter, '{req_body} | {res_body}', true);
+ $client->getEventDispatcher()->addSubscriber($plugin);
+ $request = $client->put();
+
+ // Send the response from the dummy server as the request body
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nsend");
+ $stream = fopen($this->getServer()->getUrl(), 'r');
+ $request->setBody(EntityBody::factory($stream, 4));
+
+ $tmpFile = tempnam(sys_get_temp_dir(), 'non_repeatable');
+ $request->setResponseBody(EntityBody::factory(fopen($tmpFile, 'w')));
+
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nresponse");
+
+ ob_start();
+ $request->send();
+ $message = ob_get_clean();
+
+ unlink($tmpFile);
+ $this->assertContains("send", $message);
+ $this->assertContains("response", $message);
+ }
+
+ public function testHasHelpfulStaticFactoryMethod()
+ {
+ $s = fopen('php://temp', 'r+');
+ $client = new Client();
+ $client->addSubscriber(LogPlugin::getDebugPlugin(true, $s));
+ $request = $client->put('http://foo.com', array('Content-Type' => 'Foo'), 'Bar');
+ $request->setresponse(new Response(200), true);
+ $request->send();
+ rewind($s);
+ $contents = stream_get_contents($s);
+ $this->assertContains('# Request:', $contents);
+ $this->assertContainsIns('PUT / HTTP/1.1', $contents);
+ $this->assertContains('# Response:', $contents);
+ $this->assertContainsIns('HTTP/1.1 200 OK', $contents);
+ $this->assertContains('# Errors:', $contents);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php
new file mode 100644
index 0000000..4bd4111
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Md5;
+
+use Guzzle\Common\Event;
+use Guzzle\Plugin\Md5\CommandContentMd5Plugin;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Client;
+
+/**
+ * @covers Guzzle\Plugin\Md5\CommandContentMd5Plugin
+ */
+class CommandContentMd5PluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected function getClient()
+ {
+ $description = new ServiceDescription(array(
+ 'operations' => array(
+ 'test' => array(
+ 'httpMethod' => 'PUT',
+ 'parameters' => array(
+ 'ContentMD5' => array(),
+ 'Body' => array(
+ 'location' => 'body'
+ )
+ )
+ )
+ )
+ ));
+
+ $client = new Client();
+ $client->setDescription($description);
+
+ return $client;
+ }
+
+ public function testHasEvents()
+ {
+ $this->assertNotEmpty(CommandContentMd5Plugin::getSubscribedEvents());
+ }
+
+ public function testValidatesMd5WhenParamExists()
+ {
+ $client = $this->getClient();
+ $command = $client->getCommand('test', array(
+ 'Body' => 'Foo',
+ 'ContentMD5' => true
+ ));
+ $event = new Event(array('command' => $command));
+ $request = $command->prepare();
+ $plugin = new CommandContentMd5Plugin();
+ $plugin->onCommandBeforeSend($event);
+ $this->assertEquals('E1bGfXrRY42Ba/uCLdLCXQ==', (string) $request->getHeader('Content-MD5'));
+ }
+
+ public function testDoesNothingWhenNoPayloadExists()
+ {
+ $client = $this->getClient();
+ $client->getDescription()->getOperation('test')->setHttpMethod('GET');
+ $command = $client->getCommand('test');
+ $event = new Event(array('command' => $command));
+ $request = $command->prepare();
+ $plugin = new CommandContentMd5Plugin();
+ $plugin->onCommandBeforeSend($event);
+ $this->assertNull($request->getHeader('Content-MD5'));
+ }
+
+ public function testAddsValidationToResponsesOfContentMd5()
+ {
+ $client = $this->getClient();
+ $client->getDescription()->getOperation('test')->setHttpMethod('GET');
+ $command = $client->getCommand('test', array(
+ 'ValidateMD5' => true
+ ));
+ $event = new Event(array('command' => $command));
+ $request = $command->prepare();
+ $plugin = new CommandContentMd5Plugin();
+ $plugin->onCommandBeforeSend($event);
+ $listeners = $request->getEventDispatcher()->getListeners('request.complete');
+ $this->assertNotEmpty($listeners);
+ }
+
+ public function testIgnoresValidationWhenDisabled()
+ {
+ $client = $this->getClient();
+ $client->getDescription()->getOperation('test')->setHttpMethod('GET');
+ $command = $client->getCommand('test', array(
+ 'ValidateMD5' => false
+ ));
+ $event = new Event(array('command' => $command));
+ $request = $command->prepare();
+ $plugin = new CommandContentMd5Plugin();
+ $plugin->onCommandBeforeSend($event);
+ $listeners = $request->getEventDispatcher()->getListeners('request.complete');
+ $this->assertEmpty($listeners);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php
new file mode 100644
index 0000000..482e92b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Md5;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Md5\Md5ValidatorPlugin;
+
+/**
+ * @covers Guzzle\Plugin\Md5\Md5ValidatorPlugin
+ */
+class Md5ValidatorPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testValidatesMd5()
+ {
+ $plugin = new Md5ValidatorPlugin();
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/');
+ $request->getEventDispatcher()->addSubscriber($plugin);
+
+ $body = 'abc';
+ $hash = md5($body);
+ $response = new Response(200, array(
+ 'Content-MD5' => $hash,
+ 'Content-Length' => 3
+ ), 'abc');
+
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+
+ // Try again with no Content-MD5
+ $response->removeHeader('Content-MD5');
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testThrowsExceptionOnInvalidMd5()
+ {
+ $plugin = new Md5ValidatorPlugin();
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/');
+ $request->getEventDispatcher()->addSubscriber($plugin);
+
+ $request->dispatch('request.complete', array(
+ 'response' => new Response(200, array(
+ 'Content-MD5' => 'foobar',
+ 'Content-Length' => 3
+ ), 'abc')
+ ));
+ }
+
+ public function testSkipsWhenContentLengthIsTooLarge()
+ {
+ $plugin = new Md5ValidatorPlugin(false, 1);
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/');
+ $request->getEventDispatcher()->addSubscriber($plugin);
+
+ $request->dispatch('request.complete', array(
+ 'response' => new Response(200, array(
+ 'Content-MD5' => 'foobar',
+ 'Content-Length' => 3
+ ), 'abc')
+ ));
+ }
+
+ public function testProperlyValidatesWhenUsingContentEncoding()
+ {
+ $plugin = new Md5ValidatorPlugin(true);
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/');
+ $request->getEventDispatcher()->addSubscriber($plugin);
+
+ // Content-MD5 is the MD5 hash of the canonical content after all
+ // content-encoding has been applied. Because cURL will automatically
+ // decompress entity bodies, we need to re-compress it to calculate.
+ $body = EntityBody::factory('abc');
+ $body->compress();
+ $hash = $body->getContentMd5();
+ $body->uncompress();
+
+ $response = new Response(200, array(
+ 'Content-MD5' => $hash,
+ 'Content-Encoding' => 'gzip'
+ ), 'abc');
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+ $this->assertEquals('abc', $response->getBody(true));
+
+ // Try again with an unknown encoding
+ $response = new Response(200, array(
+ 'Content-MD5' => $hash,
+ 'Content-Encoding' => 'foobar'
+ ), 'abc');
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+
+ // Try again with compress
+ $body->compress('bzip2.compress');
+ $response = new Response(200, array(
+ 'Content-MD5' => $body->getContentMd5(),
+ 'Content-Encoding' => 'compress'
+ ), 'abc');
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+
+ // Try again with encoding and disabled content-encoding checks
+ $request->getEventDispatcher()->removeSubscriber($plugin);
+ $plugin = new Md5ValidatorPlugin(false);
+ $request->getEventDispatcher()->addSubscriber($plugin);
+ $request->dispatch('request.complete', array(
+ 'response' => $response
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php
new file mode 100644
index 0000000..3af8fef
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Mock;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Http\Client;
+use Guzzle\Http\Exception\CurlException;
+
+/**
+ * @covers Guzzle\Plugin\Mock\MockPlugin
+ */
+class MockPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDescribesSubscribedEvents()
+ {
+ $this->assertInternalType('array', MockPlugin::getSubscribedEvents());
+ }
+
+ public function testDescribesEvents()
+ {
+ $this->assertInternalType('array', MockPlugin::getAllEvents());
+ }
+
+ public function testCanBeTemporary()
+ {
+ $plugin = new MockPlugin();
+ $this->assertFalse($plugin->isTemporary());
+ $plugin = new MockPlugin(null, true);
+ $this->assertTrue($plugin->isTemporary());
+ }
+
+ public function testIsCountable()
+ {
+ $plugin = new MockPlugin();
+ $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
+ $this->assertEquals(1, count($plugin));
+ }
+
+ /**
+ * @depends testIsCountable
+ */
+ public function testCanClearQueue()
+ {
+ $plugin = new MockPlugin();
+ $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
+ $plugin->clearQueue();
+ $this->assertEquals(0, count($plugin));
+ }
+
+ public function testCanInspectQueue()
+ {
+ $plugin = new MockPlugin();
+ $this->assertInternalType('array', $plugin->getQueue());
+ $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
+ $queue = $plugin->getQueue();
+ $this->assertInternalType('array', $queue);
+ $this->assertEquals(1, count($queue));
+ }
+
+ public function testRetrievesResponsesFromFiles()
+ {
+ $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response');
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response);
+ $this->assertEquals(200, $response->getStatusCode());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowsExceptionWhenResponseFileIsNotFound()
+ {
+ MockPlugin::getMockFile('missing/filename');
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidResponsesThrowAnException()
+ {
+ $p = new MockPlugin();
+ $p->addResponse($this);
+ }
+
+ public function testAddsResponseObjectsToQueue()
+ {
+ $p = new MockPlugin();
+ $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $p->addResponse($response);
+ $this->assertEquals(array($response), $p->getQueue());
+ }
+
+ public function testAddsResponseFilesToQueue()
+ {
+ $p = new MockPlugin();
+ $p->addResponse(__DIR__ . '/../../TestData/mock_response');
+ $this->assertEquals(1, count($p));
+ }
+
+ /**
+ * @depends testAddsResponseFilesToQueue
+ */
+ public function testAddsMockResponseToRequestFromClient()
+ {
+ $p = new MockPlugin();
+ $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response');
+ $p->addResponse($response);
+
+ $client = new Client('http://127.0.0.1:123/');
+ $client->getEventDispatcher()->addSubscriber($p, 9999);
+ $request = $client->get();
+ $request->send();
+
+ $this->assertSame($response, $request->getResponse());
+ $this->assertEquals(0, count($p));
+ }
+
+ /**
+ * @depends testAddsResponseFilesToQueue
+ * @expectedException \OutOfBoundsException
+ */
+ public function testUpdateThrowsExceptionWhenEmpty()
+ {
+ $p = new MockPlugin();
+ $p->onRequestBeforeSend(new Event());
+ }
+
+ /**
+ * @depends testAddsMockResponseToRequestFromClient
+ */
+ public function testDetachesTemporaryWhenEmpty()
+ {
+ $p = new MockPlugin(null, true);
+ $p->addResponse(MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response'));
+ $client = new Client('http://127.0.0.1:123/');
+ $client->getEventDispatcher()->addSubscriber($p, 9999);
+ $request = $client->get();
+ $request->send();
+
+ $this->assertFalse($this->hasSubscriber($client, $p));
+ }
+
+ public function testLoadsResponsesFromConstructor()
+ {
+ $p = new MockPlugin(array(new Response(200)));
+ $this->assertEquals(1, $p->count());
+ }
+
+ public function testStoresMockedRequests()
+ {
+ $p = new MockPlugin(array(new Response(200), new Response(200)));
+ $client = new Client('http://127.0.0.1:123/');
+ $client->getEventDispatcher()->addSubscriber($p, 9999);
+
+ $request1 = $client->get();
+ $request1->send();
+ $this->assertEquals(array($request1), $p->getReceivedRequests());
+
+ $request2 = $client->get();
+ $request2->send();
+ $this->assertEquals(array($request1, $request2), $p->getReceivedRequests());
+
+ $p->flush();
+ $this->assertEquals(array(), $p->getReceivedRequests());
+ }
+
+ public function testReadsBodiesFromMockedRequests()
+ {
+ $p = new MockPlugin(array(new Response(200)));
+ $p->readBodies(true);
+ $client = new Client('http://127.0.0.1:123/');
+ $client->getEventDispatcher()->addSubscriber($p, 9999);
+
+ $body = EntityBody::factory('foo');
+ $request = $client->put();
+ $request->setBody($body);
+ $request->send();
+ $this->assertEquals(3, $body->ftell());
+ }
+
+ public function testCanMockBadRequestExceptions()
+ {
+ $client = new Client('http://127.0.0.1:123/');
+ $ex = new CurlException('Foo');
+ $mock = new MockPlugin(array($ex));
+ $client->addSubscriber($mock);
+ $request = $client->get('foo');
+
+ try {
+ $request->send();
+ $this->fail('Did not dequeue an exception');
+ } catch (CurlException $e) {
+ $this->assertSame($e, $ex);
+ $this->assertSame($request, $ex->getRequest());
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php
new file mode 100644
index 0000000..3892fb6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php
@@ -0,0 +1,345 @@
+<?php
+
+namespace Guzzle\Tests\Plugin\Oauth;
+
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Http\QueryAggregator\CommaAggregator;
+use Guzzle\Plugin\Oauth\OauthPlugin;
+use Guzzle\Common\Event;
+
+/**
+ * @covers Guzzle\Plugin\Oauth\OauthPlugin
+ */
+class OauthPluginTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ const TIMESTAMP = '1327274290';
+ const NONCE = 'e7aa11195ca58349bec8b5ebe351d3497eb9e603';
+
+ protected $config = array(
+ 'consumer_key' => 'foo',
+ 'consumer_secret' => 'bar',
+ 'token' => 'count',
+ 'token_secret' => 'dracula'
+ );
+
+ protected function getRequest()
+ {
+ return RequestFactory::getInstance()->create('POST', 'http://www.test.com/path?a=b&c=d', null, array(
+ 'e' => 'f'
+ ));
+ }
+
+ public function testSubscribesToEvents()
+ {
+ $events = OauthPlugin::getSubscribedEvents();
+ $this->assertArrayHasKey('request.before_send', $events);
+ }
+
+ public function testAcceptsConfigurationData()
+ {
+ $p = new OauthPlugin($this->config);
+
+ // Access the config object
+ $class = new \ReflectionClass($p);
+ $property = $class->getProperty('config');
+ $property->setAccessible(true);
+ $config = $property->getValue($p);
+
+ $this->assertEquals('foo', $config['consumer_key']);
+ $this->assertEquals('bar', $config['consumer_secret']);
+ $this->assertEquals('count', $config['token']);
+ $this->assertEquals('dracula', $config['token_secret']);
+ $this->assertEquals('1.0', $config['version']);
+ $this->assertEquals('HMAC-SHA1', $config['signature_method']);
+ $this->assertEquals('header', $config['request_method']);
+ }
+
+ public function testCreatesStringToSignFromPostRequest()
+ {
+ $p = new OauthPlugin($this->config);
+ $request = $this->getRequest();
+ $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE);
+
+ $this->assertContains('&e=f', rawurldecode($signString));
+
+ $expectedSignString =
+ // Method and URL
+ 'POST&http%3A%2F%2Fwww.test.com%2Fpath' .
+ // Sorted parameters from query string and body
+ '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' .
+ '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' .
+ 'oauth_signature_method%3DHMAC-SHA1' .
+ '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0';
+
+ $this->assertEquals($expectedSignString, $signString);
+ }
+
+ public function testCreatesStringToSignIgnoringPostFields()
+ {
+ $config = $this->config;
+ $config['disable_post_params'] = true;
+ $p = new OauthPlugin($config);
+ $request = $this->getRequest();
+ $sts = rawurldecode($p->getStringToSign($request, self::TIMESTAMP, self::NONCE));
+ $this->assertNotContains('&e=f', $sts);
+ }
+
+ public function testCreatesStringToSignFromPostRequestWithCustomContentType()
+ {
+ $p = new OauthPlugin($this->config);
+ $request = $this->getRequest();
+ $request->setHeader('Content-Type', 'Foo');
+ $this->assertEquals(
+ // Method and URL
+ 'POST&http%3A%2F%2Fwww.test.com%2Fpath' .
+ // Sorted parameters from query string and body
+ '&a%3Db%26c%3Dd%26oauth_consumer_key%3Dfoo' .
+ '%26oauth_nonce%3D'. self::NONCE .'%26' .
+ 'oauth_signature_method%3DHMAC-SHA1' .
+ '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0',
+ $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)
+ );
+ }
+
+ /**
+ * @depends testCreatesStringToSignFromPostRequest
+ */
+ public function testConvertsBooleansToStrings()
+ {
+ $p = new OauthPlugin($this->config);
+ $request = $this->getRequest();
+ $request->getQuery()->set('a', true);
+ $request->getQuery()->set('c', false);
+ $this->assertContains('&a%3Dtrue%26c%3Dfalse', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE));
+ }
+
+ public function testCreatesStringToSignFromPostRequestWithNullValues()
+ {
+ $config = array(
+ 'consumer_key' => 'foo',
+ 'consumer_secret' => 'bar',
+ 'token' => null,
+ 'token_secret' => 'dracula'
+ );
+
+ $p = new OauthPlugin($config);
+ $request = $this->getRequest();
+ $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE);
+
+ $this->assertContains('&e=f', rawurldecode($signString));
+
+ $expectedSignString = // Method and URL
+ 'POST&http%3A%2F%2Fwww.test.com%2Fpath' .
+ // Sorted parameters from query string and body
+ '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' .
+ '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' .
+ 'oauth_signature_method%3DHMAC-SHA1' .
+ '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_version%3D1.0';
+
+ $this->assertEquals($expectedSignString, $signString);
+ }
+
+ /**
+ * @depends testCreatesStringToSignFromPostRequest
+ */
+ public function testMultiDimensionalArray()
+ {
+ $p = new OauthPlugin($this->config);
+ $request = $this->getRequest();
+ $request->getQuery()->set('a', array('b' => array('e' => 'f', 'c' => 'd')));
+ $this->assertContains('a%255Bb%255D%255Bc%255D%3Dd%26a%255Bb%255D%255Be%255D%3Df%26c%3Dd%26e%3Df%26', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE));
+ }
+
+ /**
+ * @depends testMultiDimensionalArray
+ */
+ public function testMultiDimensionalArrayWithNonDefaultQueryAggregator()
+ {
+ $p = new OauthPlugin($this->config);
+ $request = $this->getRequest();
+ $aggregator = new CommaAggregator();
+ $query = $request->getQuery()->setAggregator($aggregator)
+ ->set('g', array('h', 'i', 'j'))
+ ->set('k', array('l'))
+ ->set('m', array('n', 'o'));
+ $this->assertContains('a%3Db%26c%3Dd%26e%3Df%26g%3Dh%2Ci%2Cj%26k%3Dl%26m%3Dn%2Co', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE));
+ }
+
+ /**
+ * @depends testCreatesStringToSignFromPostRequest
+ */
+ public function testSignsStrings()
+ {
+ $p = new OauthPlugin(array_merge($this->config, array(
+ 'signature_callback' => function($string, $key) {
+ return "_{$string}|{$key}_";
+ }
+ )));
+ $request = $this->getRequest();
+ $sig = $p->getSignature($request, self::TIMESTAMP, self::NONCE);
+ $this->assertEquals(
+ '_POST&http%3A%2F%2Fwww.test.com%2Fpath&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' .
+ '%26oauth_nonce%3D'. self::NONCE .'%26oauth_signature_method%3DHMAC-SHA1' .
+ '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0|' .
+ 'bar&dracula_',
+ base64_decode($sig)
+ );
+ }
+
+ /**
+ * Test that the Oauth is signed correctly and that extra strings haven't been added
+ * to the authorization header.
+ */
+ public function testSignsOauthRequests()
+ {
+ $p = new OauthPlugin($this->config);
+ $event = new Event(array(
+ 'request' => $this->getRequest(),
+ 'timestamp' => self::TIMESTAMP
+ ));
+ $params = $p->onRequestBeforeSend($event);
+
+ $this->assertTrue($event['request']->hasHeader('Authorization'));
+
+ $authorizationHeader = (string)$event['request']->getHeader('Authorization');
+
+ $this->assertStringStartsWith('OAuth ', $authorizationHeader);
+
+ $stringsToCheck = array(
+ 'oauth_consumer_key="foo"',
+ 'oauth_nonce="'.urlencode($params['oauth_nonce']).'"',
+ 'oauth_signature="'.urlencode($params['oauth_signature']).'"',
+ 'oauth_signature_method="HMAC-SHA1"',
+ 'oauth_timestamp="' . self::TIMESTAMP . '"',
+ 'oauth_token="count"',
+ 'oauth_version="1.0"',
+ );
+
+ $totalLength = strlen('OAuth ');
+
+ //Separator is not used before first parameter.
+ $separator = '';
+
+ foreach ($stringsToCheck as $stringToCheck) {
+ $this->assertContains($stringToCheck, $authorizationHeader);
+ $totalLength += strlen($separator);
+ $totalLength += strlen($stringToCheck);
+ $separator = ', ';
+ }
+
+ // Technically this test is not universally valid. It would be allowable to have extra \n characters
+ // in the Authorization header. However Guzzle does not do this, so we just perform a simple check
+ // on length to validate the Authorization header is composed of only the strings above.
+ $this->assertEquals($totalLength, strlen($authorizationHeader), 'Authorization has extra characters i.e. contains extra elements compared to stringsToCheck.');
+ }
+
+ public function testSignsOauthQueryStringRequest()
+ {
+ $config = array_merge(
+ $this->config,
+ array('request_method' => OauthPlugin::REQUEST_METHOD_QUERY)
+ );
+
+ $p = new OauthPlugin($config);
+ $event = new Event(array(
+ 'request' => $this->getRequest(),
+ 'timestamp' => self::TIMESTAMP
+ ));
+ $params = $p->onRequestBeforeSend($event);
+
+ $this->assertFalse($event['request']->hasHeader('Authorization'));
+
+ $stringsToCheck = array(
+ 'a=b',
+ 'c=d',
+ 'oauth_consumer_key=foo',
+ 'oauth_nonce='.urlencode($params['oauth_nonce']),
+ 'oauth_signature='.urlencode($params['oauth_signature']),
+ 'oauth_signature_method=HMAC-SHA1',
+ 'oauth_timestamp='.self::TIMESTAMP,
+ 'oauth_token=count',
+ 'oauth_version=1.0',
+ );
+
+ $queryString = (string) $event['request']->getQuery();
+
+ $totalLength = strlen('?');
+
+ //Separator is not used before first parameter.
+ $separator = '';
+
+ foreach ($stringsToCheck as $stringToCheck) {
+ $this->assertContains($stringToCheck, $queryString);
+ $totalLength += strlen($separator);
+ $totalLength += strlen($stringToCheck);
+ $separator = '&';
+ }
+
+ // Removes the last query string separator '&'
+ $totalLength -= 1;
+
+ $this->assertEquals($totalLength, strlen($queryString), 'Query string has extra characters i.e. contains extra elements compared to stringsToCheck.');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInvalidArgumentExceptionOnMethodError()
+ {
+ $config = array_merge(
+ $this->config,
+ array('request_method' => 'FakeMethod')
+ );
+
+ $p = new OauthPlugin($config);
+ $event = new Event(array(
+ 'request' => $this->getRequest(),
+ 'timestamp' => self::TIMESTAMP
+ ));
+
+ $p->onRequestBeforeSend($event);
+ }
+
+ public function testDoesNotAddFalseyValuesToAuthorization()
+ {
+ unset($this->config['token']);
+ $p = new OauthPlugin($this->config);
+ $event = new Event(array('request' => $this->getRequest(), 'timestamp' => self::TIMESTAMP));
+ $p->onRequestBeforeSend($event);
+ $this->assertTrue($event['request']->hasHeader('Authorization'));
+ $this->assertNotContains('oauth_token=', (string) $event['request']->getHeader('Authorization'));
+ }
+
+ public function testOptionalOauthParametersAreNotAutomaticallyAdded()
+ {
+ // The only required Oauth parameters are the consumer key and secret. That is enough credentials
+ // for signing oauth requests.
+ $config = array(
+ 'consumer_key' => 'foo',
+ 'consumer_secret' => 'bar',
+ );
+
+ $plugin = new OauthPlugin($config);
+ $event = new Event(array(
+ 'request' => $this->getRequest(),
+ 'timestamp' => self::TIMESTAMP
+ ));
+
+ $timestamp = $plugin->getTimestamp($event);
+ $request = $event['request'];
+ $nonce = $plugin->generateNonce($request);
+
+ $paramsToSign = $plugin->getParamsToSign($request, $timestamp, $nonce);
+
+ $optionalParams = array(
+ 'callback' => 'oauth_callback',
+ 'token' => 'oauth_token',
+ 'verifier' => 'oauth_verifier',
+ 'token_secret' => 'token_secret'
+ );
+
+ foreach ($optionalParams as $optionName => $oauthName) {
+ $this->assertArrayNotHasKey($oauthName, $paramsToSign, "Optional Oauth param '$oauthName' was not set via config variable '$optionName', but it is listed in getParamsToSign().");
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php
new file mode 100644
index 0000000..8b42fb8
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Guzzle\Tests\Service;
+
+/**
+ * @covers Guzzle\Service\AbstractConfigLoader
+ */
+class AbstractConfigLoaderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var \Guzzle\Service\AbstractConfigLoader */
+ protected $loader;
+
+ /** @var array Any files that need to be deleted on tear down */
+ protected $cleanup = array();
+
+ public function setUp()
+ {
+ $this->loader = $this->getMockBuilder('Guzzle\Service\AbstractConfigLoader')
+ ->setMethods(array('build'))
+ ->getMockForAbstractClass();
+ }
+
+ public function tearDown()
+ {
+ foreach ($this->cleanup as $file) {
+ unlink($file);
+ }
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testOnlyLoadsSupportedTypes()
+ {
+ $this->loader->load(new \stdClass());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Unable to open fooooooo.json
+ */
+ public function testFileMustBeReadable()
+ {
+ $this->loader->load('fooooooo.json');
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Unknown file extension
+ */
+ public function testMustBeSupportedExtension()
+ {
+ $this->loader->load(dirname(__DIR__) . '/TestData/FileBody.txt');
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ * @expectedExceptionMessage Error loading JSON data from
+ */
+ public function testJsonMustBeValue()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'json') . '.json';
+ file_put_contents($filename, '{/{./{}foo');
+ $this->cleanup[] = $filename;
+ $this->loader->load($filename);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ * @expectedExceptionMessage PHP files must return an array
+ */
+ public function testPhpFilesMustReturnAnArray()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'php') . '.php';
+ file_put_contents($filename, '<?php $fdr = false;');
+ $this->cleanup[] = $filename;
+ $this->loader->load($filename);
+ }
+
+ public function testLoadsPhpFileIncludes()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'php') . '.php';
+ file_put_contents($filename, '<?php return array("foo" => "bar");');
+ $this->cleanup[] = $filename;
+ $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0));
+ $config = $this->loader->load($filename);
+ $this->assertEquals(array('foo' => 'bar'), $config);
+ }
+
+ public function testCanCreateFromJson()
+ {
+ $file = dirname(__DIR__) . '/TestData/services/json1.json';
+ // The build method will just return the config data
+ $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0));
+ $data = $this->loader->load($file);
+ // Ensure that the config files were merged using the includes directives
+ $this->assertArrayHasKey('includes', $data);
+ $this->assertArrayHasKey('services', $data);
+ $this->assertInternalType('array', $data['services']['foo']);
+ $this->assertInternalType('array', $data['services']['abstract']);
+ $this->assertInternalType('array', $data['services']['mock']);
+ $this->assertEquals('bar', $data['services']['foo']['params']['baz']);
+ }
+
+ public function testUsesAliases()
+ {
+ $file = dirname(__DIR__) . '/TestData/services/json1.json';
+ $this->loader->addAlias('foo', $file);
+ // The build method will just return the config data
+ $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0));
+ $data = $this->loader->load('foo');
+ $this->assertEquals('bar', $data['services']['foo']['params']['baz']);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Unable to open foo.json
+ */
+ public function testCanRemoveAliases()
+ {
+ $file = dirname(__DIR__) . '/TestData/services/json1.json';
+ $this->loader->addAlias('foo.json', $file);
+ $this->loader->removeAlias('foo.json');
+ $this->loader->load('foo.json');
+ }
+
+ public function testCanLoadArraysWithIncludes()
+ {
+ $file = dirname(__DIR__) . '/TestData/services/json1.json';
+ $config = array('includes' => array($file));
+ // The build method will just return the config data
+ $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0));
+ $data = $this->loader->load($config);
+ $this->assertEquals('bar', $data['services']['foo']['params']['baz']);
+ }
+
+ public function testDoesNotEnterInfiniteLoop()
+ {
+ $prefix = $file = dirname(__DIR__) . '/TestData/description';
+ $this->loader->load("{$prefix}/baz.json");
+ $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles'));
+ // Ensure that the internal list of loaded files is reset
+ $this->loader->load("{$prefix}/../test_service2.json");
+ $this->assertCount(1, $this->readAttribute($this->loader, 'loadedFiles'));
+ // Ensure that previously loaded files will be reloaded when starting fresh
+ $this->loader->load("{$prefix}/baz.json");
+ $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php
new file mode 100644
index 0000000..f63070e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace Guzzle\Tests\Service\Builder;
+
+use Guzzle\Service\Builder\ServiceBuilderLoader;
+
+/**
+ * @covers Guzzle\Service\Builder\ServiceBuilderLoader
+ */
+class ServiceBuilderLoaderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testBuildsServiceBuilders()
+ {
+ $arrayFactory = new ServiceBuilderLoader();
+
+ $data = array(
+ 'services' => array(
+ 'abstract' => array(
+ 'params' => array(
+ 'access_key' => 'xyz',
+ 'secret' => 'abc',
+ ),
+ ),
+ 'foo' => array(
+ 'extends' => 'abstract',
+ 'params' => array(
+ 'baz' => 'bar',
+ ),
+ ),
+ 'mock' => array(
+ 'extends' => 'abstract',
+ 'params' => array(
+ 'username' => 'foo',
+ 'password' => 'baz',
+ 'subdomain' => 'bar',
+ )
+ )
+ )
+ );
+
+ $builder = $arrayFactory->load($data);
+
+ // Ensure that services were parsed
+ $this->assertTrue(isset($builder['mock']));
+ $this->assertTrue(isset($builder['abstract']));
+ $this->assertTrue(isset($builder['foo']));
+ $this->assertFalse(isset($builder['jimmy']));
+ }
+
+ /**
+ * @expectedException Guzzle\Service\Exception\ServiceNotFoundException
+ * @expectedExceptionMessage foo is trying to extend a non-existent service: abstract
+ */
+ public function testThrowsExceptionWhenExtendingNonExistentService()
+ {
+ $arrayFactory = new ServiceBuilderLoader();
+
+ $data = array(
+ 'services' => array(
+ 'foo' => array(
+ 'extends' => 'abstract'
+ )
+ )
+ );
+
+ $builder = $arrayFactory->load($data);
+ }
+
+ public function testAllowsGlobalParameterOverrides()
+ {
+ $arrayFactory = new ServiceBuilderLoader();
+
+ $data = array(
+ 'services' => array(
+ 'foo' => array(
+ 'params' => array(
+ 'foo' => 'baz',
+ 'bar' => 'boo'
+ )
+ )
+ )
+ );
+
+ $builder = $arrayFactory->load($data, array(
+ 'bar' => 'jar',
+ 'far' => 'car'
+ ));
+
+ $compiled = json_decode($builder->serialize(), true);
+ $this->assertEquals(array(
+ 'foo' => 'baz',
+ 'bar' => 'jar',
+ 'far' => 'car'
+ ), $compiled['foo']['params']);
+ }
+
+ public function tstDoesNotErrorOnCircularReferences()
+ {
+ $arrayFactory = new ServiceBuilderLoader();
+ $arrayFactory->load(array(
+ 'services' => array(
+ 'too' => array('extends' => 'ball'),
+ 'ball' => array('extends' => 'too'),
+ )
+ ));
+ }
+
+ public function configProvider()
+ {
+ $foo = array(
+ 'extends' => 'bar',
+ 'class' => 'stdClass',
+ 'params' => array('a' => 'test', 'b' => '456')
+ );
+
+ return array(
+ array(
+ // Does not extend the existing `foo` service but overwrites it
+ array(
+ 'services' => array(
+ 'foo' => $foo,
+ 'bar' => array('params' => array('baz' => '123'))
+ )
+ ),
+ array(
+ 'services' => array(
+ 'foo' => array('class' => 'Baz')
+ )
+ ),
+ array(
+ 'services' => array(
+ 'foo' => array('class' => 'Baz'),
+ 'bar' => array('params' => array('baz' => '123'))
+ )
+ )
+ ),
+ array(
+ // Extends the existing `foo` service
+ array(
+ 'services' => array(
+ 'foo' => $foo,
+ 'bar' => array('params' => array('baz' => '123'))
+ )
+ ),
+ array(
+ 'services' => array(
+ 'foo' => array(
+ 'extends' => 'foo',
+ 'params' => array('b' => '123', 'c' => 'def')
+ )
+ )
+ ),
+ array(
+ 'services' => array(
+ 'foo' => array(
+ 'extends' => 'bar',
+ 'class' => 'stdClass',
+ 'params' => array('a' => 'test', 'b' => '123', 'c' => 'def')
+ ),
+ 'bar' => array('params' => array('baz' => '123'))
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * @dataProvider configProvider
+ */
+ public function testCombinesConfigs($a, $b, $c)
+ {
+ $l = new ServiceBuilderLoader();
+ $m = new \ReflectionMethod($l, 'mergeData');
+ $m->setAccessible(true);
+ $this->assertEquals($c, $m->invoke($l, $a, $b));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php
new file mode 100644
index 0000000..e1b3a1d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php
@@ -0,0 +1,317 @@
+<?php
+
+namespace Guzzle\Tests\Service;
+
+use Guzzle\Plugin\History\HistoryPlugin;
+use Guzzle\Service\Builder\ServiceBuilder;
+use Guzzle\Service\Client;
+
+/**
+ * @covers Guzzle\Service\Builder\ServiceBuilder
+ */
+class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $arrayData = array(
+ 'michael.mock' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'username' => 'michael',
+ 'password' => 'testing123',
+ 'subdomain' => 'michael',
+ ),
+ ),
+ 'billy.mock' => array(
+ 'alias' => 'Hello!',
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'username' => 'billy',
+ 'password' => 'passw0rd',
+ 'subdomain' => 'billy',
+ ),
+ ),
+ 'billy.testing' => array(
+ 'extends' => 'billy.mock',
+ 'params' => array(
+ 'subdomain' => 'test.billy',
+ ),
+ ),
+ 'missing_params' => array(
+ 'extends' => 'billy.mock'
+ )
+ );
+
+ public function testAllowsSerialization()
+ {
+ $builder = ServiceBuilder::factory($this->arrayData);
+ $cached = unserialize(serialize($builder));
+ $this->assertEquals($cached, $builder);
+ }
+
+ public function testDelegatesFactoryMethodToAbstractFactory()
+ {
+ $builder = ServiceBuilder::factory($this->arrayData);
+ $c = $builder->get('michael.mock');
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c);
+ }
+
+ /**
+ * @expectedException Guzzle\Service\Exception\ServiceNotFoundException
+ * @expectedExceptionMessage No service is registered as foobar
+ */
+ public function testThrowsExceptionWhenGettingInvalidClient()
+ {
+ ServiceBuilder::factory($this->arrayData)->get('foobar');
+ }
+
+ public function testStoresClientCopy()
+ {
+ $builder = ServiceBuilder::factory($this->arrayData);
+ $client = $builder->get('michael.mock');
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client);
+ $this->assertEquals('http://127.0.0.1:8124/v1/michael', $client->getBaseUrl());
+ $this->assertEquals($client, $builder->get('michael.mock'));
+
+ // Get another client but throw this one away
+ $client2 = $builder->get('billy.mock', true);
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client2);
+ $this->assertEquals('http://127.0.0.1:8124/v1/billy', $client2->getBaseUrl());
+
+ // Make sure the original client is still there and set
+ $this->assertTrue($client === $builder->get('michael.mock'));
+
+ // Create a new billy.mock client that is stored
+ $client3 = $builder->get('billy.mock');
+
+ // Make sure that the stored billy.mock client is equal to the other stored client
+ $this->assertTrue($client3 === $builder->get('billy.mock'));
+
+ // Make sure that this client is not equal to the previous throwaway client
+ $this->assertFalse($client2 === $builder->get('billy.mock'));
+ }
+
+ public function testBuildersPassOptionsThroughToClients()
+ {
+ $s = new ServiceBuilder(array(
+ 'michael.mock' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'base_url' => 'http://www.test.com/',
+ 'subdomain' => 'michael',
+ 'password' => 'test',
+ 'username' => 'michael',
+ 'curl.curlopt_proxyport' => 8080
+ )
+ )
+ ));
+
+ $c = $s->get('michael.mock');
+ $this->assertEquals(8080, $c->getConfig('curl.curlopt_proxyport'));
+ }
+
+ public function testUsesTheDefaultBuilderWhenNoBuilderIsSpecified()
+ {
+ $s = new ServiceBuilder(array(
+ 'michael.mock' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'base_url' => 'http://www.test.com/',
+ 'subdomain' => 'michael',
+ 'password' => 'test',
+ 'username' => 'michael',
+ 'curl.curlopt_proxyport' => 8080
+ )
+ )
+ ));
+
+ $c = $s->get('michael.mock');
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c);
+ }
+
+ public function testUsedAsArray()
+ {
+ $b = ServiceBuilder::factory($this->arrayData);
+ $this->assertTrue($b->offsetExists('michael.mock'));
+ $this->assertFalse($b->offsetExists('not_there'));
+ $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']);
+
+ unset($b['michael.mock']);
+ $this->assertFalse($b->offsetExists('michael.mock'));
+
+ $b['michael.mock'] = new Client('http://www.test.com/');
+ $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']);
+ }
+
+ public function testFactoryCanCreateFromJson()
+ {
+ $tmp = sys_get_temp_dir() . '/test.js';
+ file_put_contents($tmp, json_encode($this->arrayData));
+ $b = ServiceBuilder::factory($tmp);
+ unlink($tmp);
+ $s = $b->get('billy.testing');
+ $this->assertEquals('test.billy', $s->getConfig('subdomain'));
+ $this->assertEquals('billy', $s->getConfig('username'));
+ }
+
+ public function testFactoryCanCreateFromArray()
+ {
+ $b = ServiceBuilder::factory($this->arrayData);
+ $s = $b->get('billy.testing');
+ $this->assertEquals('test.billy', $s->getConfig('subdomain'));
+ $this->assertEquals('billy', $s->getConfig('username'));
+ }
+
+ public function testFactoryDoesNotRequireParams()
+ {
+ $b = ServiceBuilder::factory($this->arrayData);
+ $s = $b->get('missing_params');
+ $this->assertEquals('billy', $s->getConfig('username'));
+ }
+
+ public function testBuilderAllowsReferencesBetweenClients()
+ {
+ $builder = ServiceBuilder::factory(array(
+ 'a' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'other_client' => '{b}',
+ 'username' => 'x',
+ 'password' => 'y',
+ 'subdomain' => 'z'
+ )
+ ),
+ 'b' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'username' => '1',
+ 'password' => '2',
+ 'subdomain' => '3'
+ )
+ )
+ ));
+
+ $client = $builder['a'];
+ $this->assertEquals('x', $client->getConfig('username'));
+ $this->assertSame($builder['b'], $client->getConfig('other_client'));
+ $this->assertEquals('1', $builder['b']->getConfig('username'));
+ }
+
+ public function testEmitsEventsWhenClientsAreCreated()
+ {
+ // Ensure that the client signals that it emits an event
+ $this->assertEquals(array('service_builder.create_client'), ServiceBuilder::getAllEvents());
+
+ // Create a test service builder
+ $builder = ServiceBuilder::factory(array(
+ 'a' => array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'username' => 'test',
+ 'password' => '123',
+ 'subdomain' => 'z'
+ )
+ )
+ ));
+
+ // Add an event listener to pick up client creation events
+ $emits = 0;
+ $builder->getEventDispatcher()->addListener('service_builder.create_client', function($event) use (&$emits) {
+ $emits++;
+ });
+
+ // Get the 'a' client by name
+ $client = $builder->get('a');
+
+ // Ensure that the event was emitted once, and that the client was present
+ $this->assertEquals(1, $emits);
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client);
+ }
+
+ public function testCanAddGlobalParametersToServicesOnLoad()
+ {
+ $builder = ServiceBuilder::factory($this->arrayData, array(
+ 'username' => 'fred',
+ 'new_value' => 'test'
+ ));
+
+ $data = json_decode($builder->serialize(), true);
+
+ foreach ($data as $service) {
+ $this->assertEquals('fred', $service['params']['username']);
+ $this->assertEquals('test', $service['params']['new_value']);
+ }
+ }
+
+ public function testAddsGlobalPlugins()
+ {
+ $b = new ServiceBuilder($this->arrayData);
+ $b->addGlobalPlugin(new HistoryPlugin());
+ $s = $b->get('michael.mock');
+ $this->assertTrue($s->getEventDispatcher()->hasListeners('request.sent'));
+ }
+
+ public function testCanGetData()
+ {
+ $b = new ServiceBuilder($this->arrayData);
+ $this->assertEquals($this->arrayData['michael.mock'], $b->getData('michael.mock'));
+ $this->assertNull($b->getData('ewofweoweofe'));
+ }
+
+ public function testCanGetByAlias()
+ {
+ $b = new ServiceBuilder($this->arrayData);
+ $this->assertSame($b->get('billy.mock'), $b->get('Hello!'));
+ }
+
+ public function testCanOverwriteParametersForThrowawayClients()
+ {
+ $b = new ServiceBuilder($this->arrayData);
+
+ $c1 = $b->get('michael.mock');
+ $this->assertEquals('michael', $c1->getConfig('username'));
+
+ $c2 = $b->get('michael.mock', array('username' => 'jeremy'));
+ $this->assertEquals('jeremy', $c2->getConfig('username'));
+ }
+
+ public function testGettingAThrowawayClientWithParametersDoesNotAffectGettingOtherClients()
+ {
+ $b = new ServiceBuilder($this->arrayData);
+
+ $c1 = $b->get('michael.mock', array('username' => 'jeremy'));
+ $this->assertEquals('jeremy', $c1->getConfig('username'));
+
+ $c2 = $b->get('michael.mock');
+ $this->assertEquals('michael', $c2->getConfig('username'));
+ }
+
+ public function testCanUseArbitraryData()
+ {
+ $b = new ServiceBuilder();
+ $b['a'] = 'foo';
+ $this->assertTrue(isset($b['a']));
+ $this->assertEquals('foo', $b['a']);
+ unset($b['a']);
+ $this->assertFalse(isset($b['a']));
+ }
+
+ public function testCanRegisterServiceData()
+ {
+ $b = new ServiceBuilder();
+ $b['a'] = array(
+ 'class' => 'Guzzle\Tests\Service\Mock\MockClient',
+ 'params' => array(
+ 'username' => 'billy',
+ 'password' => 'passw0rd',
+ 'subdomain' => 'billy',
+ )
+ );
+ $this->assertTrue(isset($b['a']));
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $b['a']);
+ $client = $b['a'];
+ unset($b['a']);
+ $this->assertFalse(isset($b['a']));
+ // Ensure that instantiated clients can be registered
+ $b['mock'] = $client;
+ $this->assertSame($client, $b['mock']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php
new file mode 100644
index 0000000..b8245ad
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Guzzle\Tests\Service;
+
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Service\CachingConfigLoader;
+use Doctrine\Common\Cache\ArrayCache;
+
+/**
+ * @covers Guzzle\Service\CachingConfigLoader
+ */
+class CachingConfigLoaderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testLoadsPhpFileIncludes()
+ {
+ $cache = new DoctrineCacheAdapter(new ArrayCache());
+ $loader = $this->getMockBuilder('Guzzle\Service\ConfigLoaderInterface')
+ ->setMethods(array('load'))
+ ->getMockForAbstractClass();
+ $data = array('foo' => 'bar');
+ $loader->expects($this->once())
+ ->method('load')
+ ->will($this->returnValue($data));
+ $cache = new CachingConfigLoader($loader, $cache);
+ $this->assertEquals($data, $cache->load('foo'));
+ $this->assertEquals($data, $cache->load('foo'));
+ }
+
+ public function testDoesNotCacheArrays()
+ {
+ $cache = new DoctrineCacheAdapter(new ArrayCache());
+ $loader = $this->getMockBuilder('Guzzle\Service\ConfigLoaderInterface')
+ ->setMethods(array('load'))
+ ->getMockForAbstractClass();
+ $data = array('foo' => 'bar');
+ $loader->expects($this->exactly(2))
+ ->method('load')
+ ->will($this->returnValue($data));
+ $cache = new CachingConfigLoader($loader, $cache);
+ $this->assertEquals($data, $cache->load(array()));
+ $this->assertEquals($data, $cache->load(array()));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php
new file mode 100644
index 0000000..aee29ed
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php
@@ -0,0 +1,320 @@
+<?php
+
+namespace Guzzle\Tests\Service;
+
+use Guzzle\Inflection\Inflector;
+use Guzzle\Http\Message\Response;
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Client;
+use Guzzle\Service\Exception\CommandTransferException;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+use Guzzle\Service\Resource\ResourceIteratorClassFactory;
+use Guzzle\Service\Command\AbstractCommand;
+
+/**
+ * @group server
+ * @covers Guzzle\Service\Client
+ */
+class ClientTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $service;
+ protected $serviceTest;
+
+ public function setUp()
+ {
+ $this->serviceTest = new ServiceDescription(array(
+ 'test_command' => new Operation(array(
+ 'doc' => 'documentationForCommand',
+ 'method' => 'DELETE',
+ 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand',
+ 'args' => array(
+ 'bucket' => array(
+ 'required' => true
+ ),
+ 'key' => array(
+ 'required' => true
+ )
+ )
+ ))
+ ));
+
+ $this->service = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json');
+ }
+
+ public function testAllowsCustomClientParameters()
+ {
+ $client = new Mock\MockClient(null, array(
+ Client::COMMAND_PARAMS => array(AbstractCommand::RESPONSE_PROCESSING => 'foo')
+ ));
+ $command = $client->getCommand('mock_command');
+ $this->assertEquals('foo', $command->get(AbstractCommand::RESPONSE_PROCESSING));
+ }
+
+ public function testFactoryCreatesClient()
+ {
+ $client = Client::factory(array(
+ 'base_url' => 'http://www.test.com/',
+ 'test' => '123'
+ ));
+
+ $this->assertEquals('http://www.test.com/', $client->getBaseUrl());
+ $this->assertEquals('123', $client->getConfig('test'));
+ }
+
+ public function testFactoryDoesNotRequireBaseUrl()
+ {
+ $client = Client::factory();
+ }
+
+ public function testDescribesEvents()
+ {
+ $this->assertInternalType('array', Client::getAllEvents());
+ }
+
+ public function testExecutesCommands()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+
+ $client = new Client($this->getServer()->getUrl());
+ $cmd = new MockCommand();
+ $client->execute($cmd);
+
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResponse());
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResult());
+ $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false)));
+ }
+
+ public function testExecutesCommandsWithArray()
+ {
+ $client = new Client('http://www.test.com/');
+ $client->getEventDispatcher()->addSubscriber(new MockPlugin(array(
+ new Response(200),
+ new Response(200)
+ )));
+
+ // Create a command set and a command
+ $set = array(new MockCommand(), new MockCommand());
+ $client->execute($set);
+
+ // Make sure it sent
+ $this->assertTrue($set[0]->isExecuted());
+ $this->assertTrue($set[1]->isExecuted());
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testThrowsExceptionWhenInvalidCommandIsExecuted()
+ {
+ $client = new Client();
+ $client->execute(new \stdClass());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowsExceptionWhenMissingCommand()
+ {
+ $client = new Client();
+
+ $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface');
+ $mock->expects($this->any())
+ ->method('factory')
+ ->with($this->equalTo('test'))
+ ->will($this->returnValue(null));
+
+ $client->setCommandFactory($mock);
+ $client->getCommand('test');
+ }
+
+ public function testCreatesCommandsUsingCommandFactory()
+ {
+ $mockCommand = new MockCommand();
+
+ $client = new Mock\MockClient();
+ $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface');
+ $mock->expects($this->any())
+ ->method('factory')
+ ->with($this->equalTo('foo'))
+ ->will($this->returnValue($mockCommand));
+
+ $client->setCommandFactory($mock);
+
+ $command = $client->getCommand('foo', array('acl' => '123'));
+ $this->assertSame($mockCommand, $command);
+ $command = $client->getCommand('foo', array('acl' => '123'));
+ $this->assertSame($mockCommand, $command);
+ $this->assertSame($client, $command->getClient());
+ }
+
+ public function testOwnsServiceDescription()
+ {
+ $client = new Mock\MockClient();
+ $this->assertNull($client->getDescription());
+
+ $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription');
+ $this->assertSame($client, $client->setDescription($description));
+ $this->assertSame($description, $client->getDescription());
+ }
+
+ public function testOwnsResourceIteratorFactory()
+ {
+ $client = new Mock\MockClient();
+
+ $method = new \ReflectionMethod($client, 'getResourceIteratorFactory');
+ $method->setAccessible(TRUE);
+ $rf1 = $method->invoke($client);
+
+ $rf = $this->readAttribute($client, 'resourceIteratorFactory');
+ $this->assertInstanceOf('Guzzle\\Service\\Resource\\ResourceIteratorClassFactory', $rf);
+ $this->assertSame($rf1, $rf);
+
+ $rf = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock');
+ $client->setResourceIteratorFactory($rf);
+ $this->assertNotSame($rf1, $rf);
+ }
+
+ public function testClientResetsRequestsBeforeExecutingCommands()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi",
+ "HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nI"
+ ));
+
+ $client = new Mock\MockClient($this->getServer()->getUrl());
+
+ $command = $client->getCommand('mock_command');
+ $client->execute($command);
+ $client->execute($command);
+ $this->assertEquals('I', $command->getResponse()->getBody(true));
+ }
+
+ public function testClientCreatesIterators()
+ {
+ $client = new Mock\MockClient();
+
+ $iterator = $client->getIterator('mock_command', array(
+ 'foo' => 'bar'
+ ), array(
+ 'limit' => 10
+ ));
+
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ $this->assertEquals(10, $this->readAttribute($iterator, 'limit'));
+
+ $command = $this->readAttribute($iterator, 'originalCommand');
+ $this->assertEquals('bar', $command->get('foo'));
+ }
+
+ public function testClientCreatesIteratorsWithNoOptions()
+ {
+ $client = new Mock\MockClient();
+ $iterator = $client->getIterator('mock_command');
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ }
+
+ public function testClientCreatesIteratorsWithCommands()
+ {
+ $client = new Mock\MockClient();
+ $command = new MockCommand();
+ $iterator = $client->getIterator($command);
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ $iteratorCommand = $this->readAttribute($iterator, 'originalCommand');
+ $this->assertSame($command, $iteratorCommand);
+ }
+
+ public function testClientHoldsInflector()
+ {
+ $client = new Mock\MockClient();
+ $this->assertInstanceOf('Guzzle\Inflection\MemoizingInflector', $client->getInflector());
+
+ $inflector = new Inflector();
+ $client->setInflector($inflector);
+ $this->assertSame($inflector, $client->getInflector());
+ }
+
+ public function testClientAddsGlobalCommandOptions()
+ {
+ $client = new Mock\MockClient('http://www.foo.com', array(
+ Client::COMMAND_PARAMS => array(
+ 'mesa' => 'bar'
+ )
+ ));
+ $command = $client->getCommand('mock_command');
+ $this->assertEquals('bar', $command->get('mesa'));
+ }
+
+ public function testSupportsServiceDescriptionBaseUrls()
+ {
+ $description = new ServiceDescription(array('baseUrl' => 'http://foo.com'));
+ $client = new Client();
+ $client->setDescription($description);
+ $this->assertEquals('http://foo.com', $client->getBaseUrl());
+ }
+
+ public function testMergesDefaultCommandParamsCorrectly()
+ {
+ $client = new Mock\MockClient('http://www.foo.com', array(
+ Client::COMMAND_PARAMS => array(
+ 'mesa' => 'bar',
+ 'jar' => 'jar'
+ )
+ ));
+ $command = $client->getCommand('mock_command', array('jar' => 'test'));
+ $this->assertEquals('bar', $command->get('mesa'));
+ $this->assertEquals('test', $command->get('jar'));
+ }
+
+ /**
+ * @expectedException \Guzzle\Http\Exception\BadResponseException
+ */
+ public function testWrapsSingleCommandExceptions()
+ {
+ $client = new Mock\MockClient('http://foobaz.com');
+ $mock = new MockPlugin(array(new Response(401)));
+ $client->addSubscriber($mock);
+ $client->execute(new MockCommand());
+ }
+
+ public function testWrapsMultipleCommandExceptions()
+ {
+ $client = new Mock\MockClient('http://foobaz.com');
+ $mock = new MockPlugin(array(new Response(200), new Response(200), new Response(404), new Response(500)));
+ $client->addSubscriber($mock);
+
+ $cmds = array(new MockCommand(), new MockCommand(), new MockCommand(), new MockCommand());
+ try {
+ $client->execute($cmds);
+ } catch (CommandTransferException $e) {
+ $this->assertEquals(2, count($e->getFailedRequests()));
+ $this->assertEquals(2, count($e->getSuccessfulRequests()));
+ $this->assertEquals(2, count($e->getFailedCommands()));
+ $this->assertEquals(2, count($e->getSuccessfulCommands()));
+
+ foreach ($e->getSuccessfulCommands() as $c) {
+ $this->assertTrue($c->getResponse()->isSuccessful());
+ }
+
+ foreach ($e->getFailedCommands() as $c) {
+ $this->assertFalse($c->getRequest()->getResponse()->isSuccessful());
+ }
+ }
+ }
+
+ public function testGetCommandAfterTwoSetDescriptions()
+ {
+ $service1 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json');
+ $service2 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service_3.json');
+
+ $client = new Mock\MockClient();
+
+ $client->setDescription($service1);
+ $client->getCommand('foo_bar');
+ $client->setDescription($service2);
+ $client->getCommand('baz_qux');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php
new file mode 100644
index 0000000..1004fae
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Client;
+use Guzzle\Service\Description\ServiceDescription;
+
+abstract class AbstractCommandTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected function getClient()
+ {
+ $client = new Client('http://www.google.com/');
+
+ return $client->setDescription(ServiceDescription::factory(__DIR__ . '/../../TestData/test_service.json'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php
new file mode 100644
index 0000000..d762246
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Http\Message\RequestFactory;
+use Guzzle\Service\Command\ClosureCommand;
+use Guzzle\Service\Client;
+
+/**
+ * @covers Guzzle\Service\Command\ClosureCommand
+ */
+class ClosureCommandTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage A closure must be passed in the parameters array
+ */
+ public function testConstructorValidatesClosure()
+ {
+ $c = new ClosureCommand();
+ }
+
+ public function testExecutesClosure()
+ {
+ $c = new ClosureCommand(array(
+ 'closure' => function($command, $api) {
+ $command->set('testing', '123');
+ $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/');
+ return $request;
+ }
+ ));
+
+ $client = $this->getServiceBuilder()->get('mock');
+ $c->setClient($client)->prepare();
+ $this->assertEquals('123', $c->get('testing'));
+ $this->assertEquals('http://www.test.com/', $c->getRequest()->getUrl());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ * @expectedExceptionMessage Closure command did not return a RequestInterface object
+ */
+ public function testMustReturnRequest()
+ {
+ $c = new ClosureCommand(array(
+ 'closure' => function($command, $api) {
+ return false;
+ }
+ ));
+
+ $client = $this->getServiceBuilder()->get('mock');
+ $c->setClient($client)->prepare();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php
new file mode 100644
index 0000000..b7173d4
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php
@@ -0,0 +1,445 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Plugin\Mock\MockPlugin;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Client;
+use Guzzle\Service\Command\AbstractCommand;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\SchemaValidator;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+use Guzzle\Tests\Service\Mock\Command\Sub\Sub;
+
+/**
+ * @covers Guzzle\Service\Command\AbstractCommand
+ */
+class CommandTest extends AbstractCommandTest
+{
+ public function testConstructorAddsDefaultParams()
+ {
+ $command = new MockCommand();
+ $this->assertEquals('123', $command->get('test'));
+ $this->assertFalse($command->isPrepared());
+ $this->assertFalse($command->isExecuted());
+ }
+
+ public function testDeterminesShortName()
+ {
+ $api = new Operation(array('name' => 'foobar'));
+ $command = new MockCommand(array(), $api);
+ $this->assertEquals('foobar', $command->getName());
+
+ $command = new MockCommand();
+ $this->assertEquals('mock_command', $command->getName());
+
+ $command = new Sub();
+ $this->assertEquals('sub.sub', $command->getName());
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testGetRequestThrowsExceptionBeforePreparation()
+ {
+ $command = new MockCommand();
+ $command->getRequest();
+ }
+
+ public function testGetResponseExecutesCommandsWhenNeeded()
+ {
+ $response = new Response(200);
+ $client = $this->getClient();
+ $this->setMockResponse($client, array($response));
+ $command = new MockCommand();
+ $command->setClient($client);
+ $this->assertSame($response, $command->getResponse());
+ $this->assertSame($response, $command->getResponse());
+ }
+
+ public function testGetResultExecutesCommandsWhenNeeded()
+ {
+ $response = new Response(200);
+ $client = $this->getClient();
+ $this->setMockResponse($client, array($response));
+ $command = new MockCommand();
+ $command->setClient($client);
+ $this->assertSame($response, $command->getResult());
+ $this->assertSame($response, $command->getResult());
+ }
+
+ public function testSetClient()
+ {
+ $command = new MockCommand();
+ $client = $this->getClient();
+
+ $command->setClient($client);
+ $this->assertEquals($client, $command->getClient());
+
+ unset($client);
+ unset($command);
+
+ $command = new MockCommand();
+ $client = $this->getClient();
+
+ $command->setClient($client)->prepare();
+ $this->assertEquals($client, $command->getClient());
+ $this->assertTrue($command->isPrepared());
+ }
+
+ public function testExecute()
+ {
+ $client = $this->getClient();
+ $response = new Response(200, array(
+ 'Content-Type' => 'application/xml'
+ ), '<xml><data>123</data></xml>');
+ $this->setMockResponse($client, array($response));
+ $command = new MockCommand();
+ $this->assertSame($command, $command->setClient($client));
+
+ // Returns the result of the command
+ $this->assertInstanceOf('SimpleXMLElement', $command->execute());
+
+ $this->assertTrue($command->isPrepared());
+ $this->assertTrue($command->isExecuted());
+ $this->assertSame($response, $command->getResponse());
+ $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $command->getRequest());
+ // Make sure that the result was automatically set to a SimpleXMLElement
+ $this->assertInstanceOf('SimpleXMLElement', $command->getResult());
+ $this->assertEquals('123', (string) $command->getResult()->data);
+ }
+
+ public function testConvertsJsonResponsesToArray()
+ {
+ $client = $this->getClient();
+ $this->setMockResponse($client, array(
+ new \Guzzle\Http\Message\Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), '{ "key": "Hi!" }'
+ )
+ ));
+ $command = new MockCommand();
+ $command->setClient($client);
+ $command->execute();
+ $this->assertEquals(array(
+ 'key' => 'Hi!'
+ ), $command->getResult());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ */
+ public function testConvertsInvalidJsonResponsesToArray()
+ {
+ $json = '{ "key": "Hi!" }invalid';
+ // Some implementations of php-json extension are not strict enough
+ // and allow to parse invalid json ignoring invalid parts
+ // See https://github.com/remicollet/pecl-json-c/issues/5
+ if (json_decode($json) && JSON_ERROR_NONE === json_last_error()) {
+ $this->markTestSkipped('php-pecl-json library regression issues');
+ }
+
+ $client = $this->getClient();
+ $this->setMockResponse($client, array(
+ new \Guzzle\Http\Message\Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), $json
+ )
+ ));
+ $command = new MockCommand();
+ $command->setClient($client);
+ $command->execute();
+ }
+
+ public function testProcessResponseIsNotXml()
+ {
+ $client = $this->getClient();
+ $this->setMockResponse($client, array(
+ new Response(200, array(
+ 'Content-Type' => 'application/octet-stream'
+ ), 'abc,def,ghi')
+ ));
+ $command = new MockCommand();
+ $client->execute($command);
+
+ // Make sure that the result was not converted to XML
+ $this->assertFalse($command->getResult() instanceof \SimpleXMLElement);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testExecuteThrowsExceptionWhenNoClientIsSet()
+ {
+ $command = new MockCommand();
+ $command->execute();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testPrepareThrowsExceptionWhenNoClientIsSet()
+ {
+ $command = new MockCommand();
+ $command->prepare();
+ }
+
+ public function testCommandsAllowsCustomRequestHeaders()
+ {
+ $command = new MockCommand();
+ $command->getRequestHeaders()->set('test', '123');
+ $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders());
+ $this->assertEquals('123', $command->getRequestHeaders()->get('test'));
+
+ $command->setClient($this->getClient())->prepare();
+ $this->assertEquals('123', (string) $command->getRequest()->getHeader('test'));
+ }
+
+ public function testCommandsAllowsCustomRequestHeadersAsArray()
+ {
+ $command = new MockCommand(array(AbstractCommand::HEADERS_OPTION => array('Foo' => 'Bar')));
+ $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders());
+ $this->assertEquals('Bar', $command->getRequestHeaders()->get('Foo'));
+ }
+
+ private function getOperation()
+ {
+ return new Operation(array(
+ 'name' => 'foobar',
+ 'httpMethod' => 'POST',
+ 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand',
+ 'parameters' => array(
+ 'test' => array(
+ 'default' => '123',
+ 'type' => 'string'
+ )
+ )));
+ }
+
+ public function testCommandsUsesOperation()
+ {
+ $api = $this->getOperation();
+ $command = new MockCommand(array(), $api);
+ $this->assertSame($api, $command->getOperation());
+ $command->setClient($this->getClient())->prepare();
+ $this->assertEquals('123', $command->get('test'));
+ $this->assertSame($api, $command->getOperation($api));
+ }
+
+ public function testCloneMakesNewRequest()
+ {
+ $client = $this->getClient();
+ $command = new MockCommand(array(), $this->getOperation());
+ $command->setClient($client);
+
+ $command->prepare();
+ $this->assertTrue($command->isPrepared());
+
+ $command2 = clone $command;
+ $this->assertFalse($command2->isPrepared());
+ }
+
+ public function testHasOnCompleteMethod()
+ {
+ $that = $this;
+ $called = 0;
+
+ $testFunction = function($command) use (&$called, $that) {
+ $called++;
+ $that->assertInstanceOf('Guzzle\Service\Command\CommandInterface', $command);
+ };
+
+ $client = $this->getClient();
+ $command = new MockCommand(array(
+ 'command.on_complete' => $testFunction
+ ), $this->getOperation());
+ $command->setClient($client);
+
+ $command->prepare()->setResponse(new Response(200), true);
+ $command->execute();
+ $this->assertEquals(1, $called);
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testOnCompleteMustBeCallable()
+ {
+ $client = $this->getClient();
+ $command = new MockCommand();
+ $command->setOnComplete('foo');
+ }
+
+ public function testCanSetResultManually()
+ {
+ $client = $this->getClient();
+ $client->getEventDispatcher()->addSubscriber(new MockPlugin(array(
+ new Response(200)
+ )));
+ $command = new MockCommand();
+ $client->execute($command);
+ $command->setResult('foo!');
+ $this->assertEquals('foo!', $command->getResult());
+ }
+
+ public function testCanInitConfig()
+ {
+ $command = $this->getMockBuilder('Guzzle\\Service\\Command\\AbstractCommand')
+ ->setConstructorArgs(array(array(
+ 'foo' => 'bar'
+ ), new Operation(array(
+ 'parameters' => array(
+ 'baz' => new Parameter(array(
+ 'default' => 'baaar'
+ ))
+ )
+ ))))
+ ->getMockForAbstractClass();
+
+ $this->assertEquals('bar', $command['foo']);
+ $this->assertEquals('baaar', $command['baz']);
+ }
+
+ public function testAddsCurlOptionsToRequestsWhenPreparing()
+ {
+ $command = new MockCommand(array(
+ 'foo' => 'bar',
+ 'curl.options' => array('CURLOPT_PROXYPORT' => 8080)
+ ));
+ $client = new Client();
+ $command->setClient($client);
+ $request = $command->prepare();
+ $this->assertEquals(8080, $request->getCurlOptions()->get(CURLOPT_PROXYPORT));
+ }
+
+ public function testIsInvokable()
+ {
+ $client = $this->getClient();
+ $response = new Response(200);
+ $this->setMockResponse($client, array($response));
+ $command = new MockCommand();
+ $command->setClient($client);
+ // Returns the result of the command
+ $this->assertSame($response, $command());
+ }
+
+ public function testCreatesDefaultOperation()
+ {
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass();
+ $this->assertInstanceOf('Guzzle\Service\Description\Operation', $command->getOperation());
+ }
+
+ public function testAllowsValidatorToBeInjected()
+ {
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass();
+ $v = new SchemaValidator();
+ $command->setValidator($v);
+ $this->assertSame($v, $this->readAttribute($command, 'validator'));
+ }
+
+ public function testCanDisableValidation()
+ {
+ $command = new MockCommand();
+ $command->setClient(new \Guzzle\Service\Client());
+ $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator')
+ ->setMethods(array('validate'))
+ ->getMock();
+ $v->expects($this->never())->method('validate');
+ $command->setValidator($v);
+ $command->set(AbstractCommand::DISABLE_VALIDATION, true);
+ $command->prepare();
+ }
+
+ public function testValidatorDoesNotUpdateNonDefaultValues()
+ {
+ $command = new MockCommand(array('test' => 123, 'foo' => 'bar'));
+ $command->setClient(new \Guzzle\Service\Client());
+ $command->prepare();
+ $this->assertEquals(123, $command->get('test'));
+ $this->assertEquals('bar', $command->get('foo'));
+ }
+
+ public function testValidatorUpdatesDefaultValues()
+ {
+ $command = new MockCommand();
+ $command->setClient(new \Guzzle\Service\Client());
+ $command->prepare();
+ $this->assertEquals(123, $command->get('test'));
+ $this->assertEquals('abc', $command->get('_internal'));
+ }
+
+ /**
+ * @expectedException \Guzzle\Service\Exception\ValidationException
+ * @expectedExceptionMessage [Foo] Baz
+ */
+ public function testValidatesCommandBeforeSending()
+ {
+ $command = new MockCommand();
+ $command->setClient(new \Guzzle\Service\Client());
+ $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator')
+ ->setMethods(array('validate', 'getErrors'))
+ ->getMock();
+ $v->expects($this->any())->method('validate')->will($this->returnValue(false));
+ $v->expects($this->any())->method('getErrors')->will($this->returnValue(array('[Foo] Baz', '[Bar] Boo')));
+ $command->setValidator($v);
+ $command->prepare();
+ }
+
+ /**
+ * @expectedException \Guzzle\Service\Exception\ValidationException
+ * @expectedExceptionMessage Validation errors: [abc] must be of type string
+ */
+ public function testValidatesAdditionalParameters()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'foo' => array(
+ 'parameters' => array(
+ 'baz' => array('type' => 'integer')
+ ),
+ 'additionalParameters' => array(
+ 'type' => 'string'
+ )
+ )
+ )
+ ));
+
+ $client = new Client();
+ $client->setDescription($description);
+ $command = $client->getCommand('foo', array(
+ 'abc' => false,
+ 'command.headers' => array('foo' => 'bar')
+ ));
+ $command->prepare();
+ }
+
+ public function testCanAccessValidationErrorsFromCommand()
+ {
+ $validationErrors = array('[Foo] Baz', '[Bar] Boo');
+ $command = new MockCommand();
+ $command->setClient(new \Guzzle\Service\Client());
+
+ $this->assertFalse($command->getValidationErrors());
+
+ $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator')
+ ->setMethods(array('validate', 'getErrors'))
+ ->getMock();
+ $v->expects($this->any())->method('getErrors')->will($this->returnValue($validationErrors));
+ $command->setValidator($v);
+
+ $this->assertEquals($validationErrors, $command->getValidationErrors());
+ }
+
+ public function testCanChangeResponseBody()
+ {
+ $body = EntityBody::factory();
+ $command = new MockCommand();
+ $command->setClient(new \Guzzle\Service\Client());
+ $command->set(AbstractCommand::RESPONSE_BODY, $body);
+ $request = $command->prepare();
+ $this->assertSame($body, $this->readAttribute($request, 'responseBody'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php
new file mode 100644
index 0000000..b7a4682
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Command\DefaultRequestSerializer;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Service\Client;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor;
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+
+/**
+ * @covers Guzzle\Service\Command\DefaultRequestSerializer
+ */
+class DefaultRequestSerializerTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var EntityEnclosingRequest */
+ protected $request;
+
+ /** @var \Guzzle\Service\Command\AbstractCommand */
+ protected $command;
+
+ /** @var Client */
+ protected $client;
+
+ /** @var DefaultRequestSerializer */
+ protected $serializer;
+
+ /** @var Operation */
+ protected $operation;
+
+ public function setUp()
+ {
+ $this->serializer = DefaultRequestSerializer::getInstance();
+ $this->client = new Client('http://foo.com/baz');
+ $this->operation = new Operation(array('httpMethod' => 'POST'));
+ $this->command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')
+ ->setConstructorArgs(array(array(), $this->operation))
+ ->getMockForAbstractClass();
+ $this->command->setClient($this->client);
+ }
+
+ public function testAllowsCustomVisitor()
+ {
+ $this->serializer->addVisitor('custom', new HeaderVisitor());
+ $this->command['test'] = '123';
+ $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'custom')));
+ $request = $this->serializer->prepare($this->command);
+ $this->assertEquals('123', (string) $request->getHeader('test'));
+ }
+
+ public function testUsesRelativePath()
+ {
+ $this->operation->setUri('bar');
+ $request = $this->serializer->prepare($this->command);
+ $this->assertEquals('http://foo.com/baz/bar', (string) $request->getUrl());
+ }
+
+ public function testUsesRelativePathWithUriLocations()
+ {
+ $this->command['test'] = '123';
+ $this->operation->setUri('bar/{test}');
+ $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'uri')));
+ $request = $this->serializer->prepare($this->command);
+ $this->assertEquals('http://foo.com/baz/bar/123', (string) $request->getUrl());
+ }
+
+ public function testAllowsCustomFactory()
+ {
+ $f = new VisitorFlyweight();
+ $serializer = new DefaultRequestSerializer($f);
+ $this->assertSame($f, $this->readAttribute($serializer, 'factory'));
+ }
+
+ public function testMixedParams()
+ {
+ $this->operation->setUri('bar{?limit,fields}');
+ $this->operation->addParam(new Parameter(array(
+ 'name' => 'limit',
+ 'location' => 'uri',
+ 'required' => false,
+ )));
+ $this->operation->addParam(new Parameter(array(
+ 'name' => 'fields',
+ 'location' => 'uri',
+ 'required' => true,
+ )));
+
+ $this->command['fields'] = array('id', 'name');
+
+ $request = $this->serializer->prepare($this->command);
+ $this->assertEquals('http://foo.com/baz/bar?fields='.urlencode('id,name'), (string) $request->getUrl());
+ }
+
+ public function testValidatesAdditionalParameters()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'foo' => array(
+ 'httpMethod' => 'PUT',
+ 'parameters' => array(
+ 'bar' => array('location' => 'header')
+ ),
+ 'additionalParameters' => array(
+ 'location' => 'json'
+ )
+ )
+ )
+ ));
+
+ $client = new Client();
+ $client->setDescription($description);
+ $command = $client->getCommand('foo');
+ $command['bar'] = 'test';
+ $command['hello'] = 'abc';
+ $request = $command->prepare();
+ $this->assertEquals('test', (string) $request->getHeader('bar'));
+ $this->assertEquals('{"hello":"abc"}', (string) $request->getBody());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php
new file mode 100644
index 0000000..a6a02f9
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Client;
+use Guzzle\Service\Command\DefaultResponseParser;
+use Guzzle\Service\Command\OperationCommand;
+use Guzzle\Service\Description\Operation;
+
+/**
+ * @covers Guzzle\Service\Command\DefaultResponseParser
+ */
+class DefaultResponseParserTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testParsesXmlResponses()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/xml'
+ ), '<Foo><Baz>Bar</Baz></Foo>'), true);
+ $this->assertInstanceOf('SimpleXMLElement', $op->execute());
+ }
+
+ public function testParsesJsonResponses()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), '{"Baz":"Bar"}'), true);
+ $this->assertEquals(array('Baz' => 'Bar'), $op->execute());
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\RuntimeException
+ */
+ public function testThrowsExceptionWhenParsingJsonFails()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200, array('Content-Type' => 'application/json'), '{"Baz":ddw}'), true);
+ $op->execute();
+ }
+
+ public function testAddsContentTypeWhenExpectsIsSetOnCommand()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $op['command.expects'] = 'application/json';
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200, null, '{"Baz":"Bar"}'), true);
+ $this->assertEquals(array('Baz' => 'Bar'), $op->execute());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php
new file mode 100644
index 0000000..ab1041a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Client;
+use Guzzle\Service\Command\Factory\AliasFactory;
+use Guzzle\Service\Command\Factory\MapFactory;
+use Guzzle\Service\Command\Factory\CompositeFactory;
+
+/**
+ * @covers Guzzle\Service\Command\Factory\AliasFactory
+ */
+class AliasFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private $factory;
+ private $client;
+
+ public function setup()
+ {
+ $this->client = new Client();
+
+ $map = new MapFactory(array(
+ 'test' => 'Guzzle\Tests\Service\Mock\Command\MockCommand',
+ 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand'
+ ));
+
+ $this->factory = new AliasFactory($this->client, array(
+ 'foo' => 'test',
+ 'bar' => 'sub',
+ 'sub' => 'test1',
+ 'krull' => 'test3',
+ 'krull_2' => 'krull',
+ 'sub_2' => 'bar',
+ 'bad_link' => 'jarjar'
+ ));
+
+ $map2 = new MapFactory(array(
+ 'test3' => 'Guzzle\Tests\Service\Mock\Command\Sub\Sub'
+ ));
+
+ $this->client->setCommandFactory(new CompositeFactory(array($map, $this->factory, $map2)));
+ }
+
+ public function aliasProvider()
+ {
+ return array(
+ array('foo', 'Guzzle\Tests\Service\Mock\Command\MockCommand', false),
+ array('bar', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false),
+ array('sub', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false),
+ array('sub_2', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false),
+ array('krull', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false),
+ array('krull_2', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false),
+ array('missing', null, true),
+ array('bad_link', null, true)
+ );
+ }
+
+ /**
+ * @dataProvider aliasProvider
+ */
+ public function testAliasesCommands($key, $result, $exception)
+ {
+ try {
+ $command = $this->client->getCommand($key);
+ if (is_null($result)) {
+ $this->assertNull($command);
+ } else {
+ $this->assertInstanceof($result, $command);
+ }
+ } catch (\Exception $e) {
+ if (!$exception) {
+ $this->fail('Got exception when it was not expected');
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php
new file mode 100644
index 0000000..b896dcf
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Command\Factory\CompositeFactory;
+
+/**
+ * @covers Guzzle\Service\Command\Factory\CompositeFactory
+ */
+class CompositeFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ private function getFactory($class = 'Guzzle\\Service\\Command\\Factory\\MapFactory')
+ {
+ return $mock = $this->getMockBuilder($class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function testIsIterable()
+ {
+ $factory = new CompositeFactory(array($this->getFactory(), $this->getFactory()));
+ $this->assertEquals(2, count($factory));
+ $this->assertEquals(2, count(iterator_to_array($factory->getIterator())));
+ }
+
+ public function testFindsFactories()
+ {
+ $f1 = $this->getFactory();
+ $f2 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory');
+ $factory = new CompositeFactory(array($f1, $f2));
+ $this->assertNull($factory->find('foo'));
+ $this->assertNull($factory->find($this->getFactory()));
+ $this->assertSame($f1, $factory->find('Guzzle\\Service\\Command\\Factory\\MapFactory'));
+ $this->assertSame($f2, $factory->find('Guzzle\\Service\\Command\\Factory\\CompositeFactory'));
+ $this->assertSame($f1, $factory->find($f1));
+ $this->assertSame($f2, $factory->find($f2));
+
+ $this->assertFalse($factory->has('foo'));
+ $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\MapFactory'));
+ $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\CompositeFactory'));
+ }
+
+ public function testCreatesCommands()
+ {
+ $factory = new CompositeFactory();
+ $this->assertNull($factory->factory('foo'));
+
+ $f1 = $this->getFactory();
+ $mockCommand1 = $this->getMockForAbstractClass('Guzzle\\Service\\Command\\AbstractCommand');
+
+ $f1->expects($this->once())
+ ->method('factory')
+ ->with($this->equalTo('foo'))
+ ->will($this->returnValue($mockCommand1));
+
+ $factory = new CompositeFactory(array($f1));
+ $this->assertSame($mockCommand1, $factory->factory('foo'));
+ }
+
+ public function testAllowsRemovalOfFactories()
+ {
+ $f1 = $this->getFactory();
+ $f2 = $this->getFactory();
+ $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory');
+ $factories = array($f1, $f2, $f3);
+ $factory = new CompositeFactory($factories);
+
+ $factory->remove('foo');
+ $this->assertEquals($factories, $factory->getIterator()->getArrayCopy());
+
+ $factory->remove($f1);
+ $this->assertEquals(array($f2, $f3), $factory->getIterator()->getArrayCopy());
+
+ $factory->remove('Guzzle\\Service\\Command\\Factory\\MapFactory');
+ $this->assertEquals(array($f3), $factory->getIterator()->getArrayCopy());
+
+ $factory->remove('Guzzle\\Service\\Command\\Factory\\CompositeFactory');
+ $this->assertEquals(array(), $factory->getIterator()->getArrayCopy());
+
+ $factory->remove('foo');
+ $this->assertEquals(array(), $factory->getIterator()->getArrayCopy());
+ }
+
+ public function testAddsFactoriesBeforeAndAtEnd()
+ {
+ $f1 = $this->getFactory();
+ $f2 = $this->getFactory();
+ $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory');
+ $f4 = $this->getFactory();
+
+ $factory = new CompositeFactory();
+
+ $factory->add($f1);
+ $this->assertEquals(array($f1), $factory->getIterator()->getArrayCopy());
+
+ $factory->add($f2);
+ $this->assertEquals(array($f1, $f2), $factory->getIterator()->getArrayCopy());
+
+ $factory->add($f3, $f2);
+ $this->assertEquals(array($f1, $f3, $f2), $factory->getIterator()->getArrayCopy());
+
+ $factory->add($f4, 'Guzzle\\Service\\Command\\Factory\\CompositeFactory');
+ $this->assertEquals(array($f1, $f4, $f3, $f2), $factory->getIterator()->getArrayCopy());
+ }
+
+ public function testProvidesDefaultChainForClients()
+ {
+ $client = $this->getMock('Guzzle\\Service\\Client');
+ $chain = CompositeFactory::getDefaultChain($client);
+ $a = $chain->getIterator()->getArrayCopy();
+ $this->assertEquals(1, count($a));
+ $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[0]);
+
+ $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription');
+ $client->expects($this->once())
+ ->method('getDescription')
+ ->will($this->returnValue($description));
+ $chain = CompositeFactory::getDefaultChain($client);
+ $a = $chain->getIterator()->getArrayCopy();
+ $this->assertEquals(2, count($a));
+ $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ServiceDescriptionFactory', $a[0]);
+ $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[1]);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php
new file mode 100644
index 0000000..7664718
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Tests\Service\Mock\MockClient;
+use Guzzle\Service\Command\Factory\ConcreteClassFactory;
+
+/**
+ * @covers Guzzle\Service\Command\Factory\ConcreteClassFactory
+ */
+class ConcreteClassFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testProvider()
+ {
+ return array(
+ array('foo', null, 'Guzzle\\Tests\\Service\\Mock\\Command\\'),
+ array('mock_command', 'Guzzle\Tests\Service\Mock\Command\MockCommand', 'Guzzle\\Tests\\Service\\Mock\\Command\\'),
+ array('other_command', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', 'Guzzle\\Tests\\Service\\Mock\\Command\\'),
+ array('sub.sub', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', 'Guzzle\\Tests\\Service\\Mock\\Command\\'),
+ array('sub.sub', null, 'Guzzle\\Foo\\'),
+ array('foo', null, null),
+ array('mock_command', 'Guzzle\Tests\Service\Mock\Command\MockCommand', null),
+ array('other_command', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', null),
+ array('sub.sub', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', null)
+ );
+ }
+
+ /**
+ * @dataProvider testProvider
+ */
+ public function testCreatesConcreteCommands($key, $result, $prefix)
+ {
+ if (!$prefix) {
+ $client = new MockClient();
+ } else {
+ $client = new MockClient('', array(
+ 'command.prefix' => $prefix
+ ));
+ }
+
+ $factory = new ConcreteClassFactory($client);
+
+ if (is_null($result)) {
+ $this->assertNull($factory->factory($key));
+ } else {
+ $this->assertInstanceof($result, $factory->factory($key));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php
new file mode 100644
index 0000000..ee720d1
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Command\Factory\MapFactory;
+
+/**
+ * @covers Guzzle\Service\Command\Factory\MapFactory
+ */
+class MapFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function mapProvider()
+ {
+ return array(
+ array('foo', null),
+ array('test', 'Guzzle\Tests\Service\Mock\Command\MockCommand'),
+ array('test1', 'Guzzle\Tests\Service\Mock\Command\OtherCommand')
+ );
+ }
+
+ /**
+ * @dataProvider mapProvider
+ */
+ public function testCreatesCommandsUsingMappings($key, $result)
+ {
+ $factory = new MapFactory(array(
+ 'test' => 'Guzzle\Tests\Service\Mock\Command\MockCommand',
+ 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand'
+ ));
+
+ if (is_null($result)) {
+ $this->assertNull($factory->factory($key));
+ } else {
+ $this->assertInstanceof($result, $factory->factory($key));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php
new file mode 100644
index 0000000..3372634
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Command\Factory\ServiceDescriptionFactory;
+use Guzzle\Inflection\Inflector;
+
+/**
+ * @covers Guzzle\Service\Command\Factory\ServiceDescriptionFactory
+ */
+class ServiceDescriptionFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testProvider()
+ {
+ return array(
+ array('foo', null),
+ array('jar_jar', 'Guzzle\Tests\Service\Mock\Command\MockCommand'),
+ array('binks', 'Guzzle\Tests\Service\Mock\Command\OtherCommand')
+ );
+ }
+
+ /**
+ * @dataProvider testProvider
+ */
+ public function testCreatesCommandsUsingServiceDescriptions($key, $result)
+ {
+ $d = $this->getDescription();
+
+ $factory = new ServiceDescriptionFactory($d);
+ $this->assertSame($d, $factory->getServiceDescription());
+
+ if (is_null($result)) {
+ $this->assertNull($factory->factory($key));
+ } else {
+ $this->assertInstanceof($result, $factory->factory($key));
+ }
+ }
+
+ public function testUsesUcFirstIfNoExactMatch()
+ {
+ $d = $this->getDescription();
+ $factory = new ServiceDescriptionFactory($d, new Inflector());
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Test'));
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('test'));
+ }
+
+ public function testUsesInflectionIfNoExactMatch()
+ {
+ $d = $this->getDescription();
+ $factory = new ServiceDescriptionFactory($d, new Inflector());
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Binks'));
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('binks'));
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('JarJar'));
+ $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('jar_jar'));
+ }
+
+ protected function getDescription()
+ {
+ return ServiceDescription::factory(array(
+ 'operations' => array(
+ 'jar_jar' => array('class' => 'Guzzle\Tests\Service\Mock\Command\MockCommand'),
+ 'binks' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand'),
+ 'Test' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand')
+ )
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php
new file mode 100644
index 0000000..46b472e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\SchemaValidator;
+use Guzzle\Service\Command\OperationCommand;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+use Guzzle\Tests\Service\Mock\MockClient;
+
+abstract class AbstractVisitorTestCase extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $command;
+ protected $request;
+ protected $param;
+ protected $validator;
+
+ public function setUp()
+ {
+ $this->command = new MockCommand();
+ $this->request = new EntityEnclosingRequest('POST', 'http://www.test.com/some/path.php');
+ $this->validator = new SchemaValidator();
+ }
+
+ protected function getCommand($location)
+ {
+ $command = new OperationCommand(array(), $this->getNestedCommand($location));
+ $command->setClient(new MockClient());
+
+ return $command;
+ }
+
+ protected function getNestedCommand($location)
+ {
+ return new Operation(array(
+ 'httpMethod' => 'POST',
+ 'parameters' => array(
+ 'foo' => new Parameter(array(
+ 'type' => 'object',
+ 'location' => $location,
+ 'sentAs' => 'Foo',
+ 'required' => true,
+ 'properties' => array(
+ 'test' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'baz' => array(
+ 'type' => 'boolean',
+ 'default' => true
+ ),
+ 'jenga' => array(
+ 'type' => 'string',
+ 'default' => 'hello',
+ 'sentAs' => 'Jenga_Yall!',
+ 'filters' => array('strtoupper')
+ )
+ )
+ ),
+ 'bar' => array('default' => 123)
+ ),
+ 'additionalProperties' => array(
+ 'type' => 'string',
+ 'filters' => array('strtoupper'),
+ 'location' => $location
+ )
+ )),
+ 'arr' => new Parameter(array(
+ 'type' => 'array',
+ 'location' => $location,
+ 'items' => array(
+ 'type' => 'string',
+ 'filters' => array('strtoupper')
+ )
+ )),
+ )
+ ));
+ }
+
+ protected function getCommandWithArrayParamAndFilters()
+ {
+ $operation = new Operation(array(
+ 'httpMethod' => 'POST',
+ 'parameters' => array(
+ 'foo' => new Parameter(array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'Foo',
+ 'required' => true,
+ 'default' => 'bar',
+ 'filters' => array('strtoupper')
+ )),
+ 'arr' => new Parameter(array(
+ 'type' => 'array',
+ 'location' => 'query',
+ 'sentAs' => 'Arr',
+ 'required' => true,
+ 'default' => array(123, 456, 789),
+ 'filters' => array(array('method' => 'implode', 'args' => array(',', '@value')))
+ ))
+ )
+ ));
+ $command = new OperationCommand(array(), $operation);
+ $command->setClient(new MockClient());
+
+ return $command;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php
new file mode 100644
index 0000000..2a95c45
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Http\EntityBody;
+use Guzzle\Service\Command\LocationVisitor\Request\BodyVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\BodyVisitor
+ */
+class BodyVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo');
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertEquals('123', (string) $this->request->getBody());
+ $this->assertNull($this->request->getHeader('Expect'));
+ }
+
+ public function testAddsExpectHeaderWhenSetToTrue()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo');
+ $param->setData('expect_header', true);
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertEquals('123', (string) $this->request->getBody());
+ }
+
+ public function testCanDisableExpectHeader()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo');
+ $param->setData('expect_header', false);
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertNull($this->request->getHeader('Expect'));
+ }
+
+ public function testCanSetExpectHeaderBasedOnSize()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo');
+ // The body is less than the cutoff
+ $param->setData('expect_header', 5);
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertNull($this->request->getHeader('Expect'));
+ // Now check when the body is greater than the cutoff
+ $param->setData('expect_header', 2);
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertEquals('100-Continue', (string) $this->request->getHeader('Expect'));
+ }
+
+ public function testAddsContentEncodingWhenSetOnBody()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo');
+ $body = EntityBody::factory('foo');
+ $body->compress();
+ $visitor->visit($this->command, $this->request, $param, $body);
+ $this->assertEquals('gzip', (string) $this->request->getHeader('Content-Encoding'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php
new file mode 100644
index 0000000..7ea1ae9
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor
+ */
+class HeaderVisitorTest extends AbstractVisitorTestCase
+{
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesHeaderMapsAreArrays()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test');
+ $param->setAdditionalProperties(new Parameter(array()));
+ $visitor->visit($this->command, $this->request, $param, 'test');
+ }
+
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test');
+ $param->setAdditionalProperties(false);
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertEquals('123', (string) $this->request->getHeader('test'));
+ }
+
+ public function testVisitsMappedPrefixHeaders()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test');
+ $param->setSentAs('x-foo-');
+ $param->setAdditionalProperties(new Parameter(array(
+ 'type' => 'string'
+ )));
+ $visitor->visit($this->command, $this->request, $param, array(
+ 'bar' => 'test',
+ 'baz' => '123'
+ ));
+ $this->assertEquals('test', (string) $this->request->getHeader('x-foo-bar'));
+ $this->assertEquals('123', (string) $this->request->getHeader('x-foo-baz'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php
new file mode 100644
index 0000000..ea6782f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively
+ */
+class JsonVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ // Test after when no body query values were found
+ $visitor->after($this->command, $this->request);
+
+ $param = $this->getNestedCommand('json')->getParam('foo');
+ $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123');
+ $visitor->visit($this->command, $this->request, $param->setSentAs('test2'), 'abc');
+ $visitor->after($this->command, $this->request);
+ $this->assertEquals('{"test":"123","test2":"abc"}', (string) $this->request->getBody());
+ }
+
+ public function testAddsJsonHeader()
+ {
+ $visitor = new Visitor();
+ $visitor->setContentTypeHeader('application/json-foo');
+ $param = $this->getNestedCommand('json')->getParam('foo');
+ $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123');
+ $visitor->after($this->command, $this->request);
+ $this->assertEquals('application/json-foo', (string) $this->request->getHeader('Content-Type'));
+ }
+
+ public function testRecursivelyBuildsJsonBodies()
+ {
+ $command = $this->getCommand('json');
+ $request = $command->prepare();
+ $this->assertEquals('{"Foo":{"test":{"baz":true,"Jenga_Yall!":"HELLO"},"bar":123}}', (string) $request->getBody());
+ }
+
+ public function testAppliesFiltersToAdditionalProperties()
+ {
+ $command = $this->getCommand('json');
+ $command->set('foo', array('not_set' => 'abc'));
+ $request = $command->prepare();
+ $result = json_decode($request->getBody(), true);
+ $this->assertEquals('ABC', $result['Foo']['not_set']);
+ }
+
+ public function testAppliesFiltersToArrayItemValues()
+ {
+ $command = $this->getCommand('json');
+ $command->set('arr', array('a', 'b'));
+ $request = $command->prepare();
+ $result = json_decode($request->getBody(), true);
+ $this->assertEquals(array('A', 'B'), $result['arr']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php
new file mode 100644
index 0000000..540b410
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\LocationVisitor\Request\PostFieldVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\PostFieldVisitor
+ */
+class PostFieldVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('postField')->getParam('foo');
+ $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123');
+ $this->assertEquals('123', (string) $this->request->getPostField('test'));
+ }
+
+ public function testRecursivelyBuildsPostFields()
+ {
+ $command = $this->getCommand('postField');
+ $request = $command->prepare();
+ $visitor = new Visitor();
+ $param = $command->getOperation()->getParam('foo');
+ $visitor->visit($command, $request, $param, $command['foo']);
+ $visitor->after($command, $request);
+ $this->assertEquals(
+ 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123',
+ rawurldecode((string) $request->getPostFields())
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php
new file mode 100644
index 0000000..21e3cec
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Client;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Http\Message\PostFile;
+use Guzzle\Service\Command\LocationVisitor\Request\PostFileVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\PostFileVisitor
+ */
+class PostFileVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('postFile')->getParam('foo');
+
+ // Test using a path to a file
+ $visitor->visit($this->command, $this->request, $param->setSentAs('test_3'), __FILE__);
+ $this->assertInternalType('array', $this->request->getPostFile('test_3'));
+
+ // Test with a PostFile
+ $visitor->visit($this->command, $this->request, $param->setSentAs(null), new PostFile('baz', __FILE__));
+ $this->assertInternalType('array', $this->request->getPostFile('baz'));
+ }
+
+ public function testVisitsLocationWithMultipleFiles()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'DoPost' => array(
+ 'httpMethod' => 'POST',
+ 'parameters' => array(
+ 'foo' => array(
+ 'location' => 'postFile',
+ 'type' => array('string', 'array')
+ )
+ )
+ )
+ )
+ ));
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length:0\r\n\r\n"));
+ $client = new Client($this->getServer()->getUrl());
+ $client->setDescription($description);
+ $command = $client->getCommand('DoPost', array('foo' => array(__FILE__, __FILE__)));
+ $command->execute();
+ $received = $this->getServer()->getReceivedRequests();
+ $this->assertContains('name="foo[0]";', $received[0]);
+ $this->assertContains('name="foo[1]";', $received[0]);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php
new file mode 100644
index 0000000..607af76
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor
+ */
+class QueryVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('query')->getParam('foo')->setSentAs('test');
+ $visitor->visit($this->command, $this->request, $param, '123');
+ $this->assertEquals('123', $this->request->getQuery()->get('test'));
+ }
+
+ /**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively
+ */
+ public function testRecursivelyBuildsQueryStrings()
+ {
+ $command = $this->getCommand('query');
+ $command->getOperation()->getParam('foo')->setSentAs('Foo');
+ $request = $command->prepare();
+ $this->assertEquals(
+ 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123',
+ rawurldecode($request->getQuery())
+ );
+ }
+
+ /**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively
+ */
+ public function testFiltersAreAppliedToArrayParamType()
+ {
+ $command = $this->getCommandWithArrayParamAndFilters();
+ $request = $command->prepare();
+ $query = $request->getQuery();
+ // param type 'string'
+ $this->assertEquals('BAR', $query->get('Foo'));
+ // param type 'array'
+ $this->assertEquals('123,456,789', $query->get('Arr'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php
new file mode 100644
index 0000000..ff8cec5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor
+ */
+class ResponseBodyVisitorTest extends AbstractVisitorTestCase
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = $this->getNestedCommand('response_body')->getParam('foo');
+ $visitor->visit($this->command, $this->request, $param, sys_get_temp_dir() . '/foo.txt');
+ $body = $this->readAttribute($this->request, 'responseBody');
+ $this->assertContains('/foo.txt', $body->getUri());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php
new file mode 100644
index 0000000..beb58b0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php
@@ -0,0 +1,558 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Request;
+
+use Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor;
+use Guzzle\Service\Client;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor
+ */
+class XmlVisitorTest extends AbstractVisitorTestCase
+{
+ public function xmlProvider()
+ {
+ return array(
+ array(
+ array(
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'test',
+ 'namespaces' => 'http://foo.com'
+ )
+ ),
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array('location' => 'xml', 'type' => 'string')
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => 'bar'),
+ '<test xmlns="http://foo.com"><Foo>test</Foo><Baz>bar</Baz></test>'
+ ),
+ // Ensure that the content-type is not added
+ array(array('parameters' => array('Foo' => array('location' => 'xml', 'type' => 'string'))), array(), ''),
+ // Test with adding attributes and no namespace
+ array(
+ array(
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'test'
+ )
+ ),
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string', 'data' => array('xmlAttribute' => true))
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => 'bar'),
+ '<test Foo="test"/>'
+ ),
+ // Test adding with an array
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'items' => array(
+ 'type' => 'numeric',
+ 'sentAs' => 'Bar'
+ )
+ )
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => array(1, 2)),
+ '<Request><Foo>test</Foo><Baz><Bar>1</Bar><Bar>2</Bar></Baz></Request>'
+ ),
+ // Test adding an object
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Bar' => array('type' => 'string'),
+ 'Bam' => array()
+ )
+ )
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')),
+ '<Request><Foo>test</Foo><Baz><Bar>abc</Bar><Bam>foo</Bam></Baz></Request>'
+ ),
+ // Add an array that contains an object
+ array(
+ array(
+ 'parameters' => array(
+ 'Baz' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'items' => array(
+ 'type' => 'object',
+ 'sentAs' => 'Bar',
+ 'properties' => array('A' => array(), 'B' => array())
+ )
+ )
+ )
+ ),
+ array('Baz' => array(
+ array('A' => '1', 'B' => '2'),
+ array('A' => '3', 'B' => '4')
+ )),
+ '<Request><Baz><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar></Baz></Request>'
+ ),
+ // Add an object of attributes
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)),
+ 'Bam' => array()
+ )
+ )
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')),
+ '<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>'
+ ),
+ // Check order doesn't matter
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)),
+ 'Bam' => array()
+ )
+ )
+ )
+ ),
+ array('Foo' => 'test', 'Baz' => array('Bam' => 'foo', 'Bar' => 'abc')),
+ '<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>'
+ ),
+ // Add values with custom namespaces
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array(
+ 'location' => 'xml',
+ 'type' => 'string',
+ 'data' => array(
+ 'xmlNamespace' => 'http://foo.com'
+ )
+ )
+ )
+ ),
+ array('Foo' => 'test'),
+ '<Request><Foo xmlns="http://foo.com">test</Foo></Request>'
+ ),
+ // Add attributes with custom namespace prefix
+ array(
+ array(
+ 'parameters' => array(
+ 'Wrap' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Foo' => array(
+ 'type' => 'string',
+ 'sentAs' => 'xsi:baz',
+ 'data' => array(
+ 'xmlNamespace' => 'http://foo.com',
+ 'xmlAttribute' => true
+ )
+ )
+ )
+ ),
+ )
+ ),
+ array('Wrap' => array(
+ 'Foo' => 'test'
+ )),
+ '<Request><Wrap xsi:baz="test" xmlns:xsi="http://foo.com"/></Request>'
+ ),
+ // Add nodes with custom namespace prefix
+ array(
+ array(
+ 'parameters' => array(
+ 'Wrap' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Foo' => array(
+ 'type' => 'string',
+ 'sentAs' => 'xsi:Foo',
+ 'data' => array(
+ 'xmlNamespace' => 'http://foobar.com'
+ )
+ )
+ )
+ ),
+ )
+ ),
+ array('Wrap' => array(
+ 'Foo' => 'test'
+ )),
+ '<Request><Wrap><xsi:Foo xmlns:xsi="http://foobar.com">test</xsi:Foo></Wrap></Request>'
+ ),
+ array(
+ array(
+ 'parameters' => array(
+ 'Foo' => array(
+ 'location' => 'xml',
+ 'type' => 'string',
+ 'data' => array(
+ 'xmlNamespace' => 'http://foo.com'
+ )
+ )
+ )
+ ),
+ array('Foo' => '<h1>This is a title</h1>'),
+ '<Request><Foo xmlns="http://foo.com"><![CDATA[<h1>This is a title</h1>]]></Foo></Request>'
+ ),
+ // Flat array at top level
+ array(
+ array(
+ 'parameters' => array(
+ 'Bars' => array(
+ 'type' => 'array',
+ 'data' => array('xmlFlattened' => true),
+ 'location' => 'xml',
+ 'items' => array(
+ 'type' => 'object',
+ 'sentAs' => 'Bar',
+ 'properties' => array(
+ 'A' => array(),
+ 'B' => array()
+ )
+ )
+ ),
+ 'Boos' => array(
+ 'type' => 'array',
+ 'data' => array('xmlFlattened' => true),
+ 'location' => 'xml',
+ 'items' => array(
+ 'sentAs' => 'Boo',
+ 'type' => 'string'
+ )
+ )
+ )
+ ),
+ array(
+ 'Bars' => array(
+ array('A' => '1', 'B' => '2'),
+ array('A' => '3', 'B' => '4')
+ ),
+ 'Boos' => array('test', '123')
+ ),
+ '<Request><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar><Boo>test</Boo><Boo>123</Boo></Request>'
+ ),
+ // Nested flat arrays
+ array(
+ array(
+ 'parameters' => array(
+ 'Delete' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Items' => array(
+ 'type' => 'array',
+ 'data' => array('xmlFlattened' => true),
+ 'items' => array(
+ 'type' => 'object',
+ 'sentAs' => 'Item',
+ 'properties' => array(
+ 'A' => array(),
+ 'B' => array()
+ )
+ )
+ )
+ )
+ )
+ )
+ ),
+ array(
+ 'Delete' => array(
+ 'Items' => array(
+ array('A' => '1', 'B' => '2'),
+ array('A' => '3', 'B' => '4')
+ )
+ )
+ ),
+ '<Request><Delete><Item><A>1</A><B>2</B></Item><Item><A>3</A><B>4</B></Item></Delete></Request>'
+ )
+ );
+ }
+
+ /**
+ * @dataProvider xmlProvider
+ */
+ public function testSerializesXml(array $operation, array $input, $xml)
+ {
+ $operation = new Operation($operation);
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array($input, $operation))
+ ->getMockForAbstractClass();
+ $command->setClient(new Client('http://www.test.com/some/path.php'));
+ $request = $command->prepare();
+ if (!empty($input)) {
+ $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type'));
+ } else {
+ $this->assertNull($request->getHeader('Content-Type'));
+ }
+ $body = str_replace(array("\n", "<?xml version=\"1.0\"?>"), '', (string) $request->getBody());
+ $this->assertEquals($xml, $body);
+ }
+
+ public function testAddsContentTypeAndTopLevelValues()
+ {
+ $operation = new Operation(array(
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'test',
+ 'namespaces' => array(
+ 'xsi' => 'http://foo.com'
+ )
+ )
+ ),
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string'),
+ 'Baz' => array('location' => 'xml', 'type' => 'string')
+ )
+ ));
+
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array(
+ 'Foo' => 'test',
+ 'Baz' => 'bar'
+ ), $operation))
+ ->getMockForAbstractClass();
+
+ $command->setClient(new Client());
+ $request = $command->prepare();
+ $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type'));
+ $this->assertEquals(
+ '<?xml version="1.0"?>' . "\n"
+ . '<test xmlns:xsi="http://foo.com"><Foo>test</Foo><Baz>bar</Baz></test>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testCanChangeContentType()
+ {
+ $visitor = new XmlVisitor();
+ $visitor->setContentTypeHeader('application/foo');
+ $this->assertEquals('application/foo', $this->readAttribute($visitor, 'contentType'));
+ }
+
+ public function testCanAddArrayOfSimpleTypes()
+ {
+ $request = new EntityEnclosingRequest('POST', 'http://foo.com');
+ $visitor = new XmlVisitor();
+ $param = new Parameter(array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'name' => 'Out',
+ 'properties' => array(
+ 'Nodes' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'min' => 1,
+ 'items' => array('type' => 'string', 'sentAs' => 'Node')
+ )
+ )
+ ));
+
+ $param->setParent(new Operation(array(
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'Test',
+ 'namespaces' => array(
+ 'https://foo/'
+ )
+ )
+ )
+ )));
+
+ $value = array('Nodes' => array('foo', 'baz'));
+ $this->assertTrue($this->validator->validate($param, $value));
+ $visitor->visit($this->command, $request, $param, $value);
+ $visitor->after($this->command, $request);
+
+ $this->assertEquals(
+ "<?xml version=\"1.0\"?>\n"
+ . "<Test xmlns=\"https://foo/\"><Out><Nodes><Node>foo</Node><Node>baz</Node></Nodes></Out></Test>\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testCanAddMultipleNamespacesToRoot()
+ {
+ $operation = new Operation(array(
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'Hi',
+ 'namespaces' => array(
+ 'xsi' => 'http://foo.com',
+ 'foo' => 'http://foobar.com'
+ )
+ )
+ ),
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml', 'type' => 'string')
+ )
+ ));
+
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array(
+ 'Foo' => 'test'
+ ), $operation))
+ ->getMockForAbstractClass();
+
+ $command->setClient(new Client());
+ $request = $command->prepare();
+ $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type'));
+ $this->assertEquals(
+ '<?xml version="1.0"?>' . "\n"
+ . '<Hi xmlns:xsi="http://foo.com" xmlns:foo="http://foobar.com"><Foo>test</Foo></Hi>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testValuesAreFiltered()
+ {
+ $operation = new Operation(array(
+ 'parameters' => array(
+ 'Foo' => array(
+ 'location' => 'xml',
+ 'type' => 'string',
+ 'filters' => array('strtoupper')
+ ),
+ 'Bar' => array(
+ 'location' => 'xml',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array(
+ 'filters' => array('strtoupper')
+ )
+ )
+ )
+ )
+ ));
+
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array(
+ 'Foo' => 'test',
+ 'Bar' => array(
+ 'Baz' => 'abc'
+ )
+ ), $operation))
+ ->getMockForAbstractClass();
+
+ $command->setClient(new Client());
+ $request = $command->prepare();
+ $this->assertEquals(
+ '<?xml version="1.0"?>' . "\n"
+ . '<Request><Foo>TEST</Foo><Bar><Baz>ABC</Baz></Bar></Request>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testSkipsNullValues()
+ {
+ $operation = new Operation(array(
+ 'parameters' => array(
+ 'Foo' => array(
+ 'location' => 'xml',
+ 'type' => 'string'
+ ),
+ 'Bar' => array(
+ 'location' => 'xml',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array(),
+ 'Bam' => array(),
+ )
+ ),
+ 'Arr' => array(
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string'
+ )
+ )
+ )
+ ));
+
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array(
+ 'Foo' => null,
+ 'Bar' => array(
+ 'Bar' => null,
+ 'Bam' => 'test'
+ ),
+ 'Arr' => array(null)
+ ), $operation))
+ ->getMockForAbstractClass();
+
+ $command->setClient(new Client());
+ $request = $command->prepare();
+ $this->assertEquals(
+ '<?xml version="1.0"?>' . "\n"
+ . '<Request><Bar><Bam>test</Bam></Bar></Request>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testAllowsXmlEncoding()
+ {
+ $operation = new Operation(array(
+ 'data' => array(
+ 'xmlEncoding' => 'UTF-8'
+ ),
+ 'parameters' => array(
+ 'Foo' => array('location' => 'xml')
+ )
+ ));
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array('Foo' => 'test'), $operation))
+ ->getMockForAbstractClass();
+ $command->setClient(new Client());
+ $request = $command->prepare();
+ $this->assertEquals(
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n"
+ . '<Request><Foo>test</Foo></Request>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+
+ public function testAllowsSendingXmlPayloadIfNoXmlParamsWereSet()
+ {
+ $operation = new Operation(array(
+ 'httpMethod' => 'POST',
+ 'data' => array('xmlAllowEmpty' => true),
+ 'parameters' => array('Foo' => array('location' => 'xml'))
+ ));
+ $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand')
+ ->setConstructorArgs(array(array(), $operation))
+ ->getMockForAbstractClass();
+ $command->setClient(new Client('http://foo.com'));
+ $request = $command->prepare();
+ $this->assertEquals(
+ '<?xml version="1.0"?>' . "\n"
+ . '<Request/>' . "\n",
+ (string) $request->getBody()
+ );
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php
new file mode 100644
index 0000000..7b86003
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+use Guzzle\Http\Message\Response;
+
+abstract class AbstractResponseVisitorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Response */
+ protected $response;
+
+ /** @var MockCommand */
+ protected $command;
+
+ /** @var array */
+ protected $value;
+
+ public function setUp()
+ {
+ $this->value = array();
+ $this->command = new MockCommand();
+ $this->response = new Response(200, array(
+ 'X-Foo' => 'bar',
+ 'Content-Length' => 3,
+ 'Content-Type' => 'text/plain'
+ ), 'Foo');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php
new file mode 100644
index 0000000..932e39b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor
+ */
+class BodyVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array('location' => 'body', 'name' => 'foo'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('Foo', (string) $this->value['foo']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php
new file mode 100644
index 0000000..db54b1a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\HeaderVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\HeaderVisitor
+ */
+class HeaderVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'header',
+ 'name' => 'ContentType',
+ 'sentAs' => 'Content-Type'
+ ));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('text/plain', $this->value['ContentType']);
+ }
+
+ public function testVisitsLocationWithFilters()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'header',
+ 'name' => 'Content-Type',
+ 'filters' => array('strtoupper')
+ ));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('TEXT/PLAIN', $this->value['Content-Type']);
+ }
+
+ public function testVisitsMappedPrefixHeaders()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'header',
+ 'name' => 'Metadata',
+ 'sentAs' => 'X-Baz-',
+ 'type' => 'object',
+ 'additionalProperties' => array(
+ 'type' => 'string'
+ )
+ ));
+ $response = new Response(200, array(
+ 'X-Baz-Test' => 'ABC',
+ 'X-Baz-Bar' => array('123', '456'),
+ 'Content-Length' => 3
+ ), 'Foo');
+ $visitor->visit($this->command, $response, $param, $this->value);
+ $this->assertEquals(array(
+ 'Metadata' => array(
+ 'Test' => 'ABC',
+ 'Bar' => array('123', '456')
+ )
+ ), $this->value);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownHeaders()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'header',
+ 'name' => 'Content-Type',
+ 'additionalParameters' => false
+ ));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('text/plain', $this->value['Content-Type']);
+ $this->assertArrayNotHasKey('X-Foo', $this->value);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownPropertiesWithAliasing()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'header',
+ 'name' => 'ContentType',
+ 'sentAs' => 'Content-Type',
+ 'additionalParameters' => false
+ ));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('text/plain', $this->value['ContentType']);
+ $this->assertArrayNotHasKey('X-Foo', $this->value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php
new file mode 100644
index 0000000..4f8d30b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor
+ */
+class JsonVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testBeforeMethodParsesXml()
+ {
+ $visitor = new Visitor();
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')
+ ->setMethods(array('getResponse'))
+ ->getMockForAbstractClass();
+ $command->expects($this->once())
+ ->method('getResponse')
+ ->will($this->returnValue(new Response(200, null, '{"foo":"bar"}')));
+ $result = array();
+ $visitor->before($command, $result);
+ $this->assertEquals(array('foo' => 'bar'), $result);
+ }
+
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'array',
+ 'items' => array(
+ 'filters' => 'strtoupper',
+ 'type' => 'string'
+ )
+ ));
+ $this->value = array('foo' => array('a', 'b', 'c'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('A', 'B', 'C'), $this->value['foo']);
+ }
+
+ public function testRenamesTopLevelValues()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'sentAs' => 'Baz',
+ 'type' => 'string',
+ ));
+ $this->value = array('Baz' => 'test');
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => 'test'), $this->value);
+ }
+
+ public function testRenamesDoesNotFailForNonExistentKey()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array(
+ 'bar' => array(
+ 'name' => 'bar',
+ 'sentAs' => 'baz',
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('unknown' => 'Unknown'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => array('unknown' => 'Unknown')), $this->value);
+ }
+
+ public function testTraversesObjectsAndAppliesFilters()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array(
+ 'foo' => array('filters' => 'strtoupper'),
+ 'bar' => array('filters' => 'strtolower')
+ )
+ ));
+ $this->value = array('foo' => array('foo' => 'hello', 'bar' => 'THERE'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => 'HELLO', 'bar' => 'there'), $this->value['foo']);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownProperties()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'bar' => array(
+ 'type' => 'string',
+ 'name' => 'bar',
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => array('bar' => 15)), $this->value);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownPropertiesWithAliasing()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'bar' => array(
+ 'name' => 'bar',
+ 'sentAs' => 'baz',
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => array('bar' => 15)), $this->value);
+ }
+
+ public function testWalksAdditionalProperties()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'bar' => array(
+ 'type' => 'string',
+ 'filters' => array('base64_decode')
+ )
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('baz' => array('bar' => 'Zm9v')));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('foo', $this->value['foo']['baz']['bar']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php
new file mode 100644
index 0000000..23cd40f
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor
+ */
+class ReasonPhraseVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array('location' => 'reasonPhrase', 'name' => 'phrase'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals('OK', $this->value['phrase']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php
new file mode 100644
index 0000000..7211a58
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor
+ */
+class StatusCodeVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testVisitsLocation()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array('location' => 'statusCode', 'name' => 'code'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(200, $this->value['code']);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php
new file mode 100644
index 0000000..f87cec7
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php
@@ -0,0 +1,431 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command\LocationVisitor\Response;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor as Visitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor
+ */
+class XmlVisitorTest extends AbstractResponseVisitorTest
+{
+ public function testBeforeMethodParsesXml()
+ {
+ $visitor = new Visitor();
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')
+ ->setMethods(array('getResponse'))
+ ->getMockForAbstractClass();
+ $command->expects($this->once())
+ ->method('getResponse')
+ ->will($this->returnValue(new Response(200, null, '<foo><Bar>test</Bar></foo>')));
+ $result = array();
+ $visitor->before($command, $result);
+ $this->assertEquals(array('Bar' => 'test'), $result);
+ }
+
+ public function testBeforeMethodParsesXmlWithNamespace()
+ {
+ $this->markTestSkipped("Response/XmlVisitor cannot accept 'xmlns' in response, see #368 (http://git.io/USa1mA).");
+
+ $visitor = new Visitor();
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')
+ ->setMethods(array('getResponse'))
+ ->getMockForAbstractClass();
+ $command->expects($this->once())
+ ->method('getResponse')
+ ->will($this->returnValue(new Response(200, null, '<foo xmlns="urn:foo"><bar:Bar xmlns:bar="urn:bar">test</bar:Bar></foo>')));
+ $result = array();
+ $visitor->before($command, $result);
+ $this->assertEquals(array('Bar' => 'test'), $result);
+ }
+
+ public function testBeforeMethodParsesNestedXml()
+ {
+ $visitor = new Visitor();
+ $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')
+ ->setMethods(array('getResponse'))
+ ->getMockForAbstractClass();
+ $command->expects($this->once())
+ ->method('getResponse')
+ ->will($this->returnValue(new Response(200, null, '<foo><Items><Bar>test</Bar></Items></foo>')));
+ $result = array();
+ $visitor->before($command, $result);
+ $this->assertEquals(array('Items' => array('Bar' => 'test')), $result);
+ }
+
+ public function testCanExtractAndRenameTopLevelXmlValues()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'xml',
+ 'name' => 'foo',
+ 'sentAs' => 'Bar'
+ ));
+ $value = array('Bar' => 'test');
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertArrayHasKey('foo', $value);
+ $this->assertEquals('test', $value['foo']);
+ }
+
+ public function testEnsuresRepeatedArraysAreInCorrectLocations()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'xml',
+ 'name' => 'foo',
+ 'sentAs' => 'Foo',
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Bar' => array('type' => 'string'),
+ 'Baz' => array('type' => 'string'),
+ 'Bam' => array('type' => 'string')
+ )
+ )
+ ));
+
+ $xml = new \SimpleXMLElement('<Test><Foo><Bar>1</Bar><Baz>2</Baz></Foo></Test>');
+ $value = json_decode(json_encode($xml), true);
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals(array(
+ 'foo' => array(
+ array (
+ 'Bar' => '1',
+ 'Baz' => '2'
+ )
+ )
+ ), $value);
+ }
+
+ public function testEnsuresFlatArraysAreFlat()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'location' => 'xml',
+ 'name' => 'foo',
+ 'type' => 'array',
+ 'items' => array('type' => 'string')
+ ));
+
+ $value = array('foo' => array('bar', 'baz'));
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals(array('foo' => array('bar', 'baz')), $value);
+
+ $value = array('foo' => 'bar');
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals(array('foo' => array('bar')), $value);
+ }
+
+ public function xmlDataProvider()
+ {
+ $param = new Parameter(array(
+ 'location' => 'xml',
+ 'name' => 'Items',
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'name' => 'Item',
+ 'properties' => array(
+ 'Bar' => array('type' => 'string'),
+ 'Baz' => array('type' => 'string')
+ )
+ )
+ ));
+
+ return array(
+ array($param, '<Test><Items><Item><Bar>1</Bar></Item><Item><Bar>2</Bar></Item></Items></Test>', array(
+ 'Items' => array(
+ array('Bar' => 1),
+ array('Bar' => 2)
+ )
+ )),
+ array($param, '<Test><Items><Item><Bar>1</Bar></Item></Items></Test>', array(
+ 'Items' => array(
+ array('Bar' => 1)
+ )
+ )),
+ array($param, '<Test><Items /></Test>', array(
+ 'Items' => array()
+ ))
+ );
+ }
+
+ /**
+ * @dataProvider xmlDataProvider
+ */
+ public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $result)
+ {
+ $visitor = new Visitor();
+ $xml = new \SimpleXMLElement($xml);
+ $value = json_decode(json_encode($xml), true);
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals($result, $value);
+ }
+
+ public function testCanRenameValues()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'TerminatingInstances',
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'instancesSet',
+ 'items' => array(
+ 'name' => 'item',
+ 'type' => 'object',
+ 'sentAs' => 'item',
+ 'properties' => array(
+ 'InstanceId' => array(
+ 'type' => 'string',
+ 'sentAs' => 'instanceId',
+ ),
+ 'CurrentState' => array(
+ 'type' => 'object',
+ 'sentAs' => 'currentState',
+ 'properties' => array(
+ 'Code' => array(
+ 'type' => 'numeric',
+ 'sentAs' => 'code',
+ ),
+ 'Name' => array(
+ 'type' => 'string',
+ 'sentAs' => 'name',
+ ),
+ ),
+ ),
+ 'PreviousState' => array(
+ 'type' => 'object',
+ 'sentAs' => 'previousState',
+ 'properties' => array(
+ 'Code' => array(
+ 'type' => 'numeric',
+ 'sentAs' => 'code',
+ ),
+ 'Name' => array(
+ 'type' => 'string',
+ 'sentAs' => 'name',
+ ),
+ ),
+ ),
+ ),
+ )
+ ));
+
+ $value = array(
+ 'instancesSet' => array (
+ 'item' => array (
+ 'instanceId' => 'i-3ea74257',
+ 'currentState' => array(
+ 'code' => '32',
+ 'name' => 'shutting-down',
+ ),
+ 'previousState' => array(
+ 'code' => '16',
+ 'name' => 'running',
+ ),
+ ),
+ )
+ );
+
+ $visitor->visit($this->command, $this->response, $param, $value);
+
+ $this->assertEquals(array(
+ 'TerminatingInstances' => array(
+ array(
+ 'InstanceId' => 'i-3ea74257',
+ 'CurrentState' => array(
+ 'Code' => '32',
+ 'Name' => 'shutting-down',
+ ),
+ 'PreviousState' => array(
+ 'Code' => '16',
+ 'Name' => 'running',
+ )
+ )
+ )
+ ), $value);
+ }
+
+ public function testCanRenameAttributes()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'RunningQueues',
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'items' => array(
+ 'type' => 'object',
+ 'sentAs' => 'item',
+ 'properties' => array(
+ 'QueueId' => array(
+ 'type' => 'string',
+ 'sentAs' => 'queue_id',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ ),
+ ),
+ 'CurrentState' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Code' => array(
+ 'type' => 'numeric',
+ 'sentAs' => 'code',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ ),
+ ),
+ 'Name' => array(
+ 'sentAs' => 'name',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ ),
+ ),
+ ),
+ ),
+ 'PreviousState' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Code' => array(
+ 'type' => 'numeric',
+ 'sentAs' => 'code',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ ),
+ ),
+ 'Name' => array(
+ 'sentAs' => 'name',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ ));
+
+ $xml = '<wrap><RunningQueues><item queue_id="q-3ea74257"><CurrentState code="32" name="processing" /><PreviousState code="16" name="wait" /></item></RunningQueues></wrap>';
+ $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true);
+ $visitor->visit($this->command, $this->response, $param, $value);
+
+ $this->assertEquals(array(
+ 'RunningQueues' => array(
+ array(
+ 'QueueId' => 'q-3ea74257',
+ 'CurrentState' => array(
+ 'Code' => '32',
+ 'Name' => 'processing',
+ ),
+ 'PreviousState' => array(
+ 'Code' => '16',
+ 'Name' => 'wait',
+ ),
+ ),
+ )
+ ), $value);
+ }
+
+ public function testAddsEmptyArraysWhenValueIsMissing()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'Foo',
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array('type' => 'array'),
+ 'Bar' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array('type' => 'array'),
+ )
+ )
+ )
+ )
+ ));
+
+ $value = array();
+ $visitor->visit($this->command, $this->response, $param, $value);
+
+ $value = array(
+ 'Foo' => array(
+ 'Bar' => array()
+ )
+ );
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals(array(
+ 'Foo' => array(
+ array(
+ 'Bar' => array()
+ )
+ )
+ ), $value);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownProperties()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'bar' => array(
+ 'type' => 'string',
+ 'name' => 'bar',
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => array('bar' => 15)), $this->value);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testDiscardingUnknownPropertiesWithAliasing()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'bar' => array(
+ 'name' => 'bar',
+ 'sentAs' => 'baz',
+ ),
+ ),
+ ));
+ $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown'));
+ $visitor->visit($this->command, $this->response, $param, $this->value);
+ $this->assertEquals(array('foo' => array('bar' => 15)), $this->value);
+ }
+
+ public function testProperlyHandlesEmptyStringValues()
+ {
+ $visitor = new Visitor();
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array(
+ 'bar' => array('type' => 'string')
+ ),
+ ));
+ $xml = '<wrapper><foo><bar /></foo></wrapper>';
+ $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true);
+ $visitor->visit($this->command, $this->response, $param, $value);
+ $this->assertEquals(array('foo' => array('bar' => '')), $value);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php
new file mode 100644
index 0000000..a252ffe
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+use Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor as JsonRequestVisitor;
+use Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor as JsonResponseVisitor;
+
+/**
+ * @covers Guzzle\Service\Command\LocationVisitor\VisitorFlyweight
+ */
+class VisitorFlyweightTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testUsesDefaultMappingsWithGetInstance()
+ {
+ $f = VisitorFlyweight::getInstance();
+ $this->assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor', $f->getRequestVisitor('json'));
+ $this->assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor', $f->getResponseVisitor('json'));
+ }
+
+ public function testCanUseCustomMappings()
+ {
+ $f = new VisitorFlyweight(array());
+ $this->assertEquals(array(), $this->readAttribute($f, 'mappings'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage No request visitor has been mapped for foo
+ */
+ public function testThrowsExceptionWhenRetrievingUnknownVisitor()
+ {
+ VisitorFlyweight::getInstance()->getRequestVisitor('foo');
+ }
+
+ public function testCachesVisitors()
+ {
+ $f = new VisitorFlyweight();
+ $v1 = $f->getRequestVisitor('json');
+ $this->assertSame($v1, $f->getRequestVisitor('json'));
+ }
+
+ public function testAllowsAddingVisitors()
+ {
+ $f = new VisitorFlyweight();
+ $j1 = new JsonRequestVisitor();
+ $j2 = new JsonResponseVisitor();
+ $f->addRequestVisitor('json', $j1);
+ $f->addResponseVisitor('json', $j2);
+ $this->assertSame($j1, $f->getRequestVisitor('json'));
+ $this->assertSame($j2, $f->getResponseVisitor('json'));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php
new file mode 100644
index 0000000..95fb533
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Client;
+use Guzzle\Service\Command\OperationCommand;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Command\DefaultRequestSerializer;
+use Guzzle\Service\Resource\Model;
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+
+/**
+ * @covers Guzzle\Service\Command\OperationCommand
+ */
+class OperationCommandTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testHasRequestSerializer()
+ {
+ $operation = new OperationCommand();
+ $a = $operation->getRequestSerializer();
+ $b = new DefaultRequestSerializer(VisitorFlyweight::getInstance());
+ $operation->setRequestSerializer($b);
+ $this->assertNotSame($a, $operation->getRequestSerializer());
+ }
+
+ public function testPreparesRequestUsingSerializer()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $op->setClient(new Client());
+ $s = $this->getMockBuilder('Guzzle\Service\Command\RequestSerializerInterface')
+ ->setMethods(array('prepare'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('prepare')
+ ->will($this->returnValue(new EntityEnclosingRequest('POST', 'http://foo.com')));
+ $op->setRequestSerializer($s);
+ $op->prepare();
+ }
+
+ public function testParsesResponsesWithResponseParser()
+ {
+ $op = new OperationCommand(array(), new Operation());
+ $p = $this->getMockBuilder('Guzzle\Service\Command\ResponseParserInterface')
+ ->setMethods(array('parse'))
+ ->getMockForAbstractClass();
+ $p->expects($this->once())
+ ->method('parse')
+ ->will($this->returnValue(array('foo' => 'bar')));
+ $op->setResponseParser($p);
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200), true);
+ $this->assertEquals(array('foo' => 'bar'), $op->execute());
+ }
+
+ public function testParsesResponsesUsingModelParserWhenMatchingModelIsFound()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'foo' => array('responseClass' => 'bar', 'responseType' => 'model')
+ ),
+ 'models' => array(
+ 'bar' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array('type' => 'string', 'location' => 'xml')
+ )
+ )
+ )
+ ));
+
+ $op = new OperationCommand(array(), $description->getOperation('foo'));
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $request->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/xml'
+ ), '<Foo><Baz>Bar</Baz></Foo>'), true);
+ $result = $op->execute();
+ $this->assertEquals(new Model(array('Baz' => 'Bar')), $result);
+ }
+
+ public function testAllowsRawResponses()
+ {
+ $description = new ServiceDescription(array(
+ 'operations' => array('foo' => array('responseClass' => 'bar', 'responseType' => 'model')),
+ 'models' => array('bar' => array())
+ ));
+ $op = new OperationCommand(array(
+ OperationCommand::RESPONSE_PROCESSING => OperationCommand::TYPE_RAW
+ ), $description->getOperation('foo'));
+ $op->setClient(new Client());
+ $request = $op->prepare();
+ $response = new Response(200, array(
+ 'Content-Type' => 'application/xml'
+ ), '<Foo><Baz>Bar</Baz></Foo>');
+ $request->setResponse($response, true);
+ $this->assertSame($response, $op->execute());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php
new file mode 100644
index 0000000..69ba1fc
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php
@@ -0,0 +1,335 @@
+<?php
+
+namespace Guzzle\Tests\Service\Command;
+
+use Guzzle\Http\Message\Response;
+use Guzzle\Service\Client;
+use Guzzle\Service\Command\OperationResponseParser;
+use Guzzle\Service\Command\OperationCommand;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor;
+use Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor;
+use Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor;
+use Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor;
+use Guzzle\Service\Command\LocationVisitor\VisitorFlyweight;
+
+/**
+ * @covers Guzzle\Service\Command\OperationResponseParser
+ * @covers Guzzle\Service\Command\CreateResponseClassEvent
+ */
+class OperationResponseParserTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testHasVisitors()
+ {
+ $p = new OperationResponseParser(new VisitorFlyweight(array()));
+ $visitor = new BodyVisitor();
+ $p->addVisitor('foo', $visitor);
+ $this->assertSame($visitor, $this->readAttribute($p, 'factory')->getResponseVisitor('foo'));
+ }
+
+ public function testUsesParentParser()
+ {
+ $p = new OperationResponseParser(new VisitorFlyweight());
+ $operation = new Operation();
+ $operation->setServiceDescription(new ServiceDescription());
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($p)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/xml'), '<F><B>C</B></F>'), true);
+ $this->assertInstanceOf('SimpleXMLElement', $op->execute());
+ }
+
+ public function testVisitsLocations()
+ {
+ $parser = new OperationResponseParser(new VisitorFlyweight(array()));
+ $parser->addVisitor('statusCode', new StatusCodeVisitor());
+ $parser->addVisitor('reasonPhrase', new ReasonPhraseVisitor());
+ $parser->addVisitor('json', new JsonVisitor());
+ $op = new OperationCommand(array(), $this->getDescription()->getOperation('test'));
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(201), true);
+ $result = $op->execute();
+ $this->assertEquals(201, $result['code']);
+ $this->assertEquals('Created', $result['phrase']);
+ }
+
+ public function testVisitsLocationsForJsonResponse()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $operation = $this->getDescription()->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), '{"baz":"bar","enigma":"123"}'), true);
+ $result = $op->execute();
+ $this->assertEquals(array(
+ 'baz' => 'bar',
+ 'enigma' => '123',
+ 'code' => 200,
+ 'phrase' => 'OK'
+ ), $result->toArray());
+ }
+
+ public function testSkipsUnkownModels()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $operation = $this->getDescription()->getOperation('test');
+ $operation->setResponseClass('Baz')->setResponseType('model');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(201), true);
+ $this->assertInstanceOf('Guzzle\Http\Message\Response', $op->execute());
+ }
+
+ public function testAllowsModelProcessingToBeDisabled()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $operation = $this->getDescription()->getOperation('test');
+ $op = new OperationCommand(array('command.response_processing' => 'native'), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), '{"baz":"bar","enigma":"123"}'), true);
+ $result = $op->execute();
+ $this->assertInstanceOf('Guzzle\Service\Resource\Model', $result);
+ $this->assertEquals(array(
+ 'baz' => 'bar',
+ 'enigma' => '123'
+ ), $result->toArray());
+ }
+
+ public function testCanInjectModelSchemaIntoModels()
+ {
+ $parser = new OperationResponseParser(VisitorFlyweight::getInstance(), true);
+ $desc = $this->getDescription();
+ $operation = $desc->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/json'
+ ), '{"baz":"bar","enigma":"123"}'), true);
+ $result = $op->execute();
+ $this->assertSame($result->getStructure(), $desc->getModel('Foo'));
+ }
+
+ public function testDoesNotParseXmlWhenNotUsingXmlVisitor()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo')),
+ 'models' => array(
+ 'Foo' => array(
+ 'type' => 'object',
+ 'properties' => array('baz' => array('location' => 'body'))
+ )
+ )
+ ));
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $brokenXml = '<broken><><><<xml>>>>>';
+ $op->prepare()->setResponse(new Response(200, array(
+ 'Content-Type' => 'application/xml'
+ ), $brokenXml), true);
+ $result = $op->execute();
+ $this->assertEquals(array('baz'), $result->getKeys());
+ $this->assertEquals($brokenXml, (string) $result['baz']);
+ }
+
+ public function testVisitsAdditionalProperties()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo')),
+ 'models' => array(
+ 'Foo' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'code' => array('location' => 'statusCode')
+ ),
+ 'additionalProperties' => array(
+ 'location' => 'json',
+ 'type' => 'object',
+ 'properties' => array(
+ 'a' => array(
+ 'type' => 'string',
+ 'filters' => 'strtoupper'
+ )
+ )
+ )
+ )
+ )
+ ));
+
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $json = '[{"a":"test"},{"a":"baz"}]';
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true);
+ $result = $op->execute()->toArray();
+ $this->assertEquals(array(
+ 'code' => 200,
+ array('a' => 'TEST'),
+ array('a' => 'BAZ')
+ ), $result);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/399
+ */
+ public function testAdditionalPropertiesDisabledDiscardsData()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo')),
+ 'models' => array(
+ 'Foo' => array(
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'name' => array(
+ 'location' => 'json',
+ 'type' => 'string',
+ ),
+ 'nested' => array(
+ 'location' => 'json',
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'width' => array(
+ 'type' => 'integer'
+ )
+ ),
+ ),
+ 'code' => array('location' => 'statusCode')
+ ),
+
+ )
+ )
+ ));
+
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $json = '{"name":"test", "volume":2.0, "nested":{"width":10,"bogus":1}}';
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true);
+ $result = $op->execute()->toArray();
+ $this->assertEquals(array(
+ 'name' => 'test',
+ 'nested' => array(
+ 'width' => 10,
+ ),
+ 'code' => 200
+ ), $result);
+ }
+
+ public function testCreatesCustomResponseClassInterface()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Guzzle\Tests\Mock\CustomResponseModel'))
+ ));
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true);
+ $result = $op->execute();
+ $this->assertInstanceOf('Guzzle\Tests\Mock\CustomResponseModel', $result);
+ $this->assertSame($op, $result->command);
+ }
+
+ /**
+ * @expectedException \Guzzle\Service\Exception\ResponseClassException
+ * @expectedExceptionMessage must exist
+ */
+ public function testEnsuresResponseClassExists()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo\Baz\Bar'))
+ ));
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true);
+ $op->execute();
+ }
+
+ /**
+ * @expectedException \Guzzle\Service\Exception\ResponseClassException
+ * @expectedExceptionMessage and implement
+ */
+ public function testEnsuresResponseClassImplementsResponseClassInterface()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => __CLASS__))
+ ));
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true);
+ $op->execute();
+ }
+
+ protected function getDescription()
+ {
+ return ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo')),
+ 'models' => array(
+ 'Foo' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'baz' => array('type' => 'string', 'location' => 'json'),
+ 'code' => array('location' => 'statusCode'),
+ 'phrase' => array('location' => 'reasonPhrase'),
+ )
+ )
+ )
+ ));
+ }
+
+ public function testCanAddListenerToParseDomainObjects()
+ {
+ $client = new Client();
+ $client->setDescription(ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'FooBazBar'))
+ )));
+ $foo = new \stdClass();
+ $client->getEventDispatcher()->addListener('command.parse_response', function ($e) use ($foo) {
+ $e['result'] = $foo;
+ });
+ $command = $client->getCommand('test');
+ $command->prepare()->setResponse(new Response(200), true);
+ $result = $command->execute();
+ $this->assertSame($result, $foo);
+ }
+
+ /**
+ * @group issue-399
+ * @link https://github.com/guzzle/guzzle/issues/501
+ */
+ public function testAdditionalPropertiesWithRefAreResolved()
+ {
+ $parser = OperationResponseParser::getInstance();
+ $description = ServiceDescription::factory(array(
+ 'operations' => array('test' => array('responseClass' => 'Foo')),
+ 'models' => array(
+ 'Baz' => array('type' => 'string'),
+ 'Foo' => array(
+ 'type' => 'object',
+ 'additionalProperties' => array('$ref' => 'Baz', 'location' => 'json')
+ )
+ )
+ ));
+ $operation = $description->getOperation('test');
+ $op = new OperationCommand(array(), $operation);
+ $op->setResponseParser($parser)->setClient(new Client());
+ $json = '{"a":"a","b":"b","c":"c"}';
+ $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true);
+ $result = $op->execute()->toArray();
+ $this->assertEquals(array('a' => 'a', 'b' => 'b', 'c' => 'c'), $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php
new file mode 100644
index 0000000..ae33b69
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php
@@ -0,0 +1,308 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\ServiceDescription;
+
+/**
+ * @covers Guzzle\Service\Description\Operation
+ */
+class OperationTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public static function strtoupper($string)
+ {
+ return strtoupper($string);
+ }
+
+ public function testOperationIsDataObject()
+ {
+ $c = new Operation(array(
+ 'name' => 'test',
+ 'summary' => 'doc',
+ 'notes' => 'notes',
+ 'documentationUrl' => 'http://www.example.com',
+ 'httpMethod' => 'POST',
+ 'uri' => '/api/v1',
+ 'responseClass' => 'array',
+ 'responseNotes' => 'returns the json_decoded response',
+ 'deprecated' => true,
+ 'parameters' => array(
+ 'key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'maxLength' => 10
+ ),
+ 'key_2' => array(
+ 'required' => true,
+ 'type' => 'integer',
+ 'default' => 10
+ )
+ )
+ ));
+
+ $this->assertEquals('test', $c->getName());
+ $this->assertEquals('doc', $c->getSummary());
+ $this->assertEquals('http://www.example.com', $c->getDocumentationUrl());
+ $this->assertEquals('POST', $c->getHttpMethod());
+ $this->assertEquals('/api/v1', $c->getUri());
+ $this->assertEquals('array', $c->getResponseClass());
+ $this->assertEquals('returns the json_decoded response', $c->getResponseNotes());
+ $this->assertTrue($c->getDeprecated());
+ $this->assertEquals('Guzzle\\Service\\Command\\OperationCommand', $c->getClass());
+ $this->assertEquals(array(
+ 'key' => new Parameter(array(
+ 'name' => 'key',
+ 'required' => true,
+ 'type' => 'string',
+ 'maxLength' => 10,
+ 'parent' => $c
+ )),
+ 'key_2' => new Parameter(array(
+ 'name' => 'key_2',
+ 'required' => true,
+ 'type' => 'integer',
+ 'default' => 10,
+ 'parent' => $c
+ ))
+ ), $c->getParams());
+
+ $this->assertEquals(new Parameter(array(
+ 'name' => 'key_2',
+ 'required' => true,
+ 'type' => 'integer',
+ 'default' => 10,
+ 'parent' => $c
+ )), $c->getParam('key_2'));
+
+ $this->assertNull($c->getParam('afefwef'));
+ $this->assertArrayNotHasKey('parent', $c->getParam('key_2')->toArray());
+ }
+
+ public function testAllowsConcreteCommands()
+ {
+ $c = new Operation(array(
+ 'name' => 'test',
+ 'class' => 'Guzzle\\Service\\Command\ClosureCommand',
+ 'parameters' => array(
+ 'p' => new Parameter(array(
+ 'name' => 'foo'
+ ))
+ )
+ ));
+ $this->assertEquals('Guzzle\\Service\\Command\ClosureCommand', $c->getClass());
+ }
+
+ public function testConvertsToArray()
+ {
+ $data = array(
+ 'name' => 'test',
+ 'class' => 'Guzzle\\Service\\Command\ClosureCommand',
+ 'summary' => 'test',
+ 'documentationUrl' => 'http://www.example.com',
+ 'httpMethod' => 'PUT',
+ 'uri' => '/',
+ 'parameters' => array('p' => array('name' => 'foo'))
+ );
+ $c = new Operation($data);
+ $toArray = $c->toArray();
+ unset($data['name']);
+ $this->assertArrayHasKey('parameters', $toArray);
+ $this->assertInternalType('array', $toArray['parameters']);
+
+ // Normalize the array
+ unset($data['parameters']);
+ unset($toArray['parameters']);
+
+ $data['responseType'] = 'primitive';
+ $data['responseClass'] = 'array';
+ $this->assertEquals($data, $toArray);
+ }
+
+ public function testDeterminesIfHasParam()
+ {
+ $command = $this->getTestCommand();
+ $this->assertTrue($command->hasParam('data'));
+ $this->assertFalse($command->hasParam('baz'));
+ }
+
+ public function testReturnsParamNames()
+ {
+ $command = $this->getTestCommand();
+ $this->assertEquals(array('data'), $command->getParamNames());
+ }
+
+ protected function getTestCommand()
+ {
+ return new Operation(array(
+ 'parameters' => array(
+ 'data' => new Parameter(array(
+ 'type' => 'string'
+ ))
+ )
+ ));
+ }
+
+ public function testCanBuildUpCommands()
+ {
+ $c = new Operation(array());
+ $c->setName('foo')
+ ->setClass('Baz')
+ ->setDeprecated(false)
+ ->setSummary('summary')
+ ->setDocumentationUrl('http://www.foo.com')
+ ->setHttpMethod('PUT')
+ ->setResponseNotes('oh')
+ ->setResponseClass('string')
+ ->setUri('/foo/bar')
+ ->addParam(new Parameter(array(
+ 'name' => 'test'
+ )));
+
+ $this->assertEquals('foo', $c->getName());
+ $this->assertEquals('Baz', $c->getClass());
+ $this->assertEquals(false, $c->getDeprecated());
+ $this->assertEquals('summary', $c->getSummary());
+ $this->assertEquals('http://www.foo.com', $c->getDocumentationUrl());
+ $this->assertEquals('PUT', $c->getHttpMethod());
+ $this->assertEquals('oh', $c->getResponseNotes());
+ $this->assertEquals('string', $c->getResponseClass());
+ $this->assertEquals('/foo/bar', $c->getUri());
+ $this->assertEquals(array('test'), $c->getParamNames());
+ }
+
+ public function testCanRemoveParams()
+ {
+ $c = new Operation(array());
+ $c->addParam(new Parameter(array('name' => 'foo')));
+ $this->assertTrue($c->hasParam('foo'));
+ $c->removeParam('foo');
+ $this->assertFalse($c->hasParam('foo'));
+ }
+
+ public function testAddsNameToParametersIfNeeded()
+ {
+ $command = new Operation(array('parameters' => array('foo' => new Parameter(array()))));
+ $this->assertEquals('foo', $command->getParam('foo')->getName());
+ }
+
+ public function testContainsApiErrorInformation()
+ {
+ $command = $this->getOperation();
+ $this->assertEquals(1, count($command->getErrorResponses()));
+ $arr = $command->toArray();
+ $this->assertEquals(1, count($arr['errorResponses']));
+ $command->addErrorResponse(400, 'Foo', 'Baz\\Bar');
+ $this->assertEquals(2, count($command->getErrorResponses()));
+ $command->setErrorResponses(array());
+ $this->assertEquals(0, count($command->getErrorResponses()));
+ }
+
+ public function testHasNotes()
+ {
+ $o = new Operation(array('notes' => 'foo'));
+ $this->assertEquals('foo', $o->getNotes());
+ $o->setNotes('bar');
+ $this->assertEquals('bar', $o->getNotes());
+ }
+
+ public function testHasData()
+ {
+ $o = new Operation(array('data' => array('foo' => 'baz', 'bar' => 123)));
+ $o->setData('test', false);
+ $this->assertEquals('baz', $o->getData('foo'));
+ $this->assertEquals(123, $o->getData('bar'));
+ $this->assertNull($o->getData('wfefwe'));
+ $this->assertEquals(array(
+ 'parameters' => array(),
+ 'class' => 'Guzzle\Service\Command\OperationCommand',
+ 'data' => array('foo' => 'baz', 'bar' => 123, 'test' => false),
+ 'responseClass' => 'array',
+ 'responseType' => 'primitive'
+ ), $o->toArray());
+ }
+
+ public function testHasServiceDescription()
+ {
+ $s = new ServiceDescription();
+ $o = new Operation(array(), $s);
+ $this->assertSame($s, $o->getServiceDescription());
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesResponseType()
+ {
+ $o = new Operation(array('responseClass' => 'array', 'responseType' => 'foo'));
+ }
+
+ public function testInfersResponseType()
+ {
+ $o = $this->getOperation();
+ $o->setServiceDescription(new ServiceDescription(array('models' => array('Foo' => array()))));
+ $this->assertEquals('primitive', $o->getResponseType());
+ $this->assertEquals('primitive', $o->setResponseClass('boolean')->getResponseType());
+ $this->assertEquals('primitive', $o->setResponseClass('array')->getResponseType());
+ $this->assertEquals('primitive', $o->setResponseClass('integer')->getResponseType());
+ $this->assertEquals('primitive', $o->setResponseClass('string')->getResponseType());
+ $this->assertEquals('class', $o->setResponseClass('foo')->getResponseType());
+ $this->assertEquals('class', $o->setResponseClass(__CLASS__)->getResponseType());
+ $this->assertEquals('model', $o->setResponseClass('Foo')->getResponseType());
+ }
+
+ public function testHasResponseType()
+ {
+ // infers in the constructor
+ $o = new Operation(array('responseClass' => 'array'));
+ $this->assertEquals('primitive', $o->getResponseType());
+ // Infers when set
+ $o = new Operation();
+ $this->assertEquals('primitive', $o->getResponseType());
+ $this->assertEquals('model', $o->setResponseType('model')->getResponseType());
+ }
+
+ public function testHasAdditionalParameters()
+ {
+ $o = new Operation(array(
+ 'additionalParameters' => array(
+ 'type' => 'string', 'name' => 'binks'
+ ),
+ 'parameters' => array(
+ 'foo' => array('type' => 'integer')
+ )
+ ));
+ $this->assertEquals('string', $o->getAdditionalParameters()->getType());
+ $arr = $o->toArray();
+ $this->assertEquals(array(
+ 'type' => 'string'
+ ), $arr['additionalParameters']);
+ }
+
+ /**
+ * @return Operation
+ */
+ protected function getOperation()
+ {
+ return new Operation(array(
+ 'name' => 'OperationTest',
+ 'class' => get_class($this),
+ 'parameters' => array(
+ 'test' => array('type' => 'object'),
+ 'bool_1' => array('default' => true, 'type' => 'boolean'),
+ 'bool_2' => array('default' => false),
+ 'float' => array('type' => 'numeric'),
+ 'int' => array('type' => 'integer'),
+ 'date' => array('type' => 'string'),
+ 'timestamp' => array('type' => 'string'),
+ 'string' => array('type' => 'string'),
+ 'username' => array('type' => 'string', 'required' => true, 'filters' => 'strtolower'),
+ 'test_function' => array('type' => 'string', 'filters' => __CLASS__ . '::strtoupper')
+ ),
+ 'errorResponses' => array(
+ array('code' => 503, 'reason' => 'InsufficientCapacity', 'class' => 'Guzzle\\Exception\\RuntimeException')
+ )
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php
new file mode 100644
index 0000000..b9c162a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php
@@ -0,0 +1,411 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\ServiceDescription;
+
+/**
+ * @covers Guzzle\Service\Description\Parameter
+ */
+class ParameterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $data = array(
+ 'name' => 'foo',
+ 'type' => 'bar',
+ 'required' => true,
+ 'default' => '123',
+ 'description' => '456',
+ 'minLength' => 2,
+ 'maxLength' => 5,
+ 'location' => 'body',
+ 'static' => 'static!',
+ 'filters' => array('trim', 'json_encode')
+ );
+
+ public function testCreatesParamFromArray()
+ {
+ $p = new Parameter($this->data);
+ $this->assertEquals('foo', $p->getName());
+ $this->assertEquals('bar', $p->getType());
+ $this->assertEquals(true, $p->getRequired());
+ $this->assertEquals('123', $p->getDefault());
+ $this->assertEquals('456', $p->getDescription());
+ $this->assertEquals(2, $p->getMinLength());
+ $this->assertEquals(5, $p->getMaxLength());
+ $this->assertEquals('body', $p->getLocation());
+ $this->assertEquals('static!', $p->getStatic());
+ $this->assertEquals(array('trim', 'json_encode'), $p->getFilters());
+ }
+
+ public function testCanConvertToArray()
+ {
+ $p = new Parameter($this->data);
+ unset($this->data['name']);
+ $this->assertEquals($this->data, $p->toArray());
+ }
+
+ public function testUsesStatic()
+ {
+ $d = $this->data;
+ $d['default'] = 'booboo';
+ $d['static'] = true;
+ $p = new Parameter($d);
+ $this->assertEquals('booboo', $p->getValue('bar'));
+ }
+
+ public function testUsesDefault()
+ {
+ $d = $this->data;
+ $d['default'] = 'foo';
+ $d['static'] = null;
+ $p = new Parameter($d);
+ $this->assertEquals('foo', $p->getValue(null));
+ }
+
+ public function testReturnsYourValue()
+ {
+ $d = $this->data;
+ $d['static'] = null;
+ $p = new Parameter($d);
+ $this->assertEquals('foo', $p->getValue('foo'));
+ }
+
+ public function testZeroValueDoesNotCauseDefaultToBeReturned()
+ {
+ $d = $this->data;
+ $d['default'] = '1';
+ $d['static'] = null;
+ $p = new Parameter($d);
+ $this->assertEquals('0', $p->getValue('0'));
+ }
+
+ public function testFiltersValues()
+ {
+ $d = $this->data;
+ $d['static'] = null;
+ $d['filters'] = 'strtoupper';
+ $p = new Parameter($d);
+ $this->assertEquals('FOO', $p->filter('foo'));
+ }
+
+ public function testConvertsBooleans()
+ {
+ $p = new Parameter(array('type' => 'boolean'));
+ $this->assertEquals(true, $p->filter('true'));
+ $this->assertEquals(false, $p->filter('false'));
+ }
+
+ public function testUsesArrayByDefaultForFilters()
+ {
+ $d = $this->data;
+ $d['filters'] = null;
+ $p = new Parameter($d);
+ $this->assertEquals(array(), $p->getFilters());
+ }
+
+ public function testAllowsSimpleLocationValue()
+ {
+ $p = new Parameter(array('name' => 'myname', 'location' => 'foo', 'sentAs' => 'Hello'));
+ $this->assertEquals('foo', $p->getLocation());
+ $this->assertEquals('Hello', $p->getSentAs());
+ }
+
+ public function testParsesTypeValues()
+ {
+ $p = new Parameter(array('type' => 'foo'));
+ $this->assertEquals('foo', $p->getType());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage A [method] value must be specified for each complex filter
+ */
+ public function testValidatesComplexFilters()
+ {
+ $p = new Parameter(array('filters' => array(array('args' => 'foo'))));
+ }
+
+ public function testCanBuildUpParams()
+ {
+ $p = new Parameter(array());
+ $p->setName('foo')
+ ->setDescription('c')
+ ->setFilters(array('d'))
+ ->setLocation('e')
+ ->setSentAs('f')
+ ->setMaxLength(1)
+ ->setMinLength(1)
+ ->setMinimum(2)
+ ->setMaximum(2)
+ ->setMinItems(3)
+ ->setMaxItems(3)
+ ->setRequired(true)
+ ->setStatic(true)
+ ->setDefault('h')
+ ->setType('i');
+
+ $p->addFilter('foo');
+
+ $this->assertEquals('foo', $p->getName());
+ $this->assertEquals('h', $p->getDefault());
+ $this->assertEquals('c', $p->getDescription());
+ $this->assertEquals(array('d', 'foo'), $p->getFilters());
+ $this->assertEquals('e', $p->getLocation());
+ $this->assertEquals('f', $p->getSentAs());
+ $this->assertEquals(1, $p->getMaxLength());
+ $this->assertEquals(1, $p->getMinLength());
+ $this->assertEquals(2, $p->getMaximum());
+ $this->assertEquals(2, $p->getMinimum());
+ $this->assertEquals(3, $p->getMaxItems());
+ $this->assertEquals(3, $p->getMinItems());
+ $this->assertEquals(true, $p->getRequired());
+ $this->assertEquals(true, $p->getStatic());
+ $this->assertEquals('i', $p->getType());
+ }
+
+ public function testAllowsNestedShape()
+ {
+ $command = $this->getServiceBuilder()->get('mock')->getCommand('mock_command')->getOperation();
+ $param = new Parameter(array(
+ 'parent' => $command,
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'location' => 'query',
+ 'properties' => array(
+ 'foo' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'baz' => array(
+ 'name' => 'baz',
+ 'type' => 'bool',
+ )
+ )
+ ),
+ 'bar' => array(
+ 'name' => 'bar',
+ 'default' => '123'
+ )
+ )
+ ));
+
+ $this->assertSame($command, $param->getParent());
+ $this->assertNotEmpty($param->getProperties());
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('foo'));
+ $this->assertSame($param, $param->getProperty('foo')->getParent());
+ $this->assertSame($param->getProperty('foo'), $param->getProperty('foo')->getProperty('baz')->getParent());
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('bar'));
+ $this->assertSame($param, $param->getProperty('bar')->getParent());
+
+ $array = $param->toArray();
+ $this->assertInternalType('array', $array['properties']);
+ $this->assertArrayHasKey('foo', $array['properties']);
+ $this->assertArrayHasKey('bar', $array['properties']);
+ }
+
+ public function testAllowsComplexFilters()
+ {
+ $that = $this;
+ $param = new Parameter(array());
+ $param->setFilters(array(array('method' => function ($a, $b, $c, $d) use ($that, $param) {
+ $that->assertEquals('test', $a);
+ $that->assertEquals('my_value!', $b);
+ $that->assertEquals('bar', $c);
+ $that->assertSame($param, $d);
+ return 'abc' . $b;
+ }, 'args' => array('test', '@value', 'bar', '@api'))));
+ $this->assertEquals('abcmy_value!', $param->filter('my_value!'));
+ }
+
+ public function testCanChangeParentOfNestedParameter()
+ {
+ $param1 = new Parameter(array('name' => 'parent'));
+ $param2 = new Parameter(array('name' => 'child'));
+ $param2->setParent($param1);
+ $this->assertSame($param1, $param2->getParent());
+ }
+
+ public function testCanRemoveFromNestedStructure()
+ {
+ $param1 = new Parameter(array('name' => 'parent'));
+ $param2 = new Parameter(array('name' => 'child'));
+ $param1->addProperty($param2);
+ $this->assertSame($param1, $param2->getParent());
+ $this->assertSame($param2, $param1->getProperty('child'));
+
+ // Remove a single child from the structure
+ $param1->removeProperty('child');
+ $this->assertNull($param1->getProperty('child'));
+ // Remove the entire structure
+ $param1->addProperty($param2);
+ $param1->removeProperty('child');
+ $this->assertNull($param1->getProperty('child'));
+ }
+
+ public function testAddsAdditionalProperties()
+ {
+ $p = new Parameter(array(
+ 'type' => 'object',
+ 'additionalProperties' => array('type' => 'string')
+ ));
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getAdditionalProperties());
+ $this->assertNull($p->getAdditionalProperties()->getAdditionalProperties());
+ $p = new Parameter(array('type' => 'object'));
+ $this->assertTrue($p->getAdditionalProperties());
+ }
+
+ public function testAddsItems()
+ {
+ $p = new Parameter(array(
+ 'type' => 'array',
+ 'items' => array('type' => 'string')
+ ));
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getItems());
+ $out = $p->toArray();
+ $this->assertEquals('array', $out['type']);
+ $this->assertInternalType('array', $out['items']);
+ }
+
+ public function testHasExtraProperties()
+ {
+ $p = new Parameter();
+ $this->assertEquals(array(), $p->getData());
+ $p->setData(array('foo' => 'bar'));
+ $this->assertEquals('bar', $p->getData('foo'));
+ $p->setData('baz', 'boo');
+ $this->assertEquals(array('foo' => 'bar', 'baz' => 'boo'), $p->getData());
+ }
+
+ public function testCanRetrieveKnownPropertiesUsingDataMethod()
+ {
+ $p = new Parameter();
+ $this->assertEquals(null, $p->getData('foo'));
+ $p->setName('test');
+ $this->assertEquals('test', $p->getData('name'));
+ }
+
+ public function testHasInstanceOf()
+ {
+ $p = new Parameter();
+ $this->assertNull($p->getInstanceOf());
+ $p->setInstanceOf('Foo');
+ $this->assertEquals('Foo', $p->getInstanceOf());
+ }
+
+ public function testHasPattern()
+ {
+ $p = new Parameter();
+ $this->assertNull($p->getPattern());
+ $p->setPattern('/[0-9]+/');
+ $this->assertEquals('/[0-9]+/', $p->getPattern());
+ }
+
+ public function testHasEnum()
+ {
+ $p = new Parameter();
+ $this->assertNull($p->getEnum());
+ $p->setEnum(array('foo', 'bar'));
+ $this->assertEquals(array('foo', 'bar'), $p->getEnum());
+ }
+
+ public function testSerializesItems()
+ {
+ $p = new Parameter(array(
+ 'type' => 'object',
+ 'additionalProperties' => array('type' => 'string')
+ ));
+ $this->assertEquals(array(
+ 'type' => 'object',
+ 'additionalProperties' => array('type' => 'string')
+ ), $p->toArray());
+ }
+
+ public function testResolvesRefKeysRecursively()
+ {
+ $description = new ServiceDescription(array(
+ 'models' => array(
+ 'JarJar' => array('type' => 'string', 'default' => 'Mesa address tha senate!'),
+ 'Anakin' => array('type' => 'array', 'items' => array('$ref' => 'JarJar'))
+ )
+ ));
+ $p = new Parameter(array('$ref' => 'Anakin', 'description' => 'added'), $description);
+ $this->assertEquals(array(
+ 'type' => 'array',
+ 'items' => array('type' => 'string', 'default' => 'Mesa address tha senate!'),
+ 'description' => 'added'
+ ), $p->toArray());
+ }
+
+ public function testResolvesExtendsRecursively()
+ {
+ $jarJar = array('type' => 'string', 'default' => 'Mesa address tha senate!', 'description' => 'a');
+ $anakin = array('type' => 'array', 'items' => array('extends' => 'JarJar', 'description' => 'b'));
+ $description = new ServiceDescription(array(
+ 'models' => array('JarJar' => $jarJar, 'Anakin' => $anakin)
+ ));
+ // Description attribute will be updated, and format added
+ $p = new Parameter(array('extends' => 'Anakin', 'format' => 'date'), $description);
+ $this->assertEquals(array(
+ 'type' => 'array',
+ 'format' => 'date',
+ 'items' => array(
+ 'type' => 'string',
+ 'default' => 'Mesa address tha senate!',
+ 'description' => 'b'
+ )
+ ), $p->toArray());
+ }
+
+ public function testHasKeyMethod()
+ {
+ $p = new Parameter(array('name' => 'foo', 'sentAs' => 'bar'));
+ $this->assertEquals('bar', $p->getWireName());
+ $p->setSentAs(null);
+ $this->assertEquals('foo', $p->getWireName());
+ }
+
+ public function testIncludesNameInToArrayWhenItemsAttributeHasName()
+ {
+ $p = new Parameter(array(
+ 'type' => 'array',
+ 'name' => 'Abc',
+ 'items' => array(
+ 'name' => 'Foo',
+ 'type' => 'object'
+ )
+ ));
+ $result = $p->toArray();
+ $this->assertEquals(array(
+ 'type' => 'array',
+ 'items' => array(
+ 'name' => 'Foo',
+ 'type' => 'object',
+ 'additionalProperties' => true
+ )
+ ), $result);
+ }
+
+ public function dateTimeProvider()
+ {
+ $d = 'October 13, 2012 16:15:46 UTC';
+
+ return array(
+ array($d, 'date-time', '2012-10-13T16:15:46Z'),
+ array($d, 'date', '2012-10-13'),
+ array($d, 'timestamp', strtotime($d)),
+ array(new \DateTime($d), 'timestamp', strtotime($d))
+ );
+ }
+
+ /**
+ * @dataProvider dateTimeProvider
+ */
+ public function testAppliesFormat($d, $format, $result)
+ {
+ $p = new Parameter();
+ $p->setFormat($format);
+ $this->assertEquals($format, $p->getFormat());
+ $this->assertEquals($result, $p->filter($d));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php
new file mode 100644
index 0000000..eb3619b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\SchemaFormatter;
+
+/**
+ * @covers Guzzle\Service\Description\SchemaFormatter
+ */
+class SchemaFormatterTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function dateTimeProvider()
+ {
+ $dateUtc = 'October 13, 2012 16:15:46 UTC';
+ $dateOffset = 'October 13, 2012 10:15:46 -06:00';
+ $expectedDateTime = '2012-10-13T16:15:46Z';
+
+ return array(
+ array('foo', 'does-not-exist', 'foo'),
+ array($dateUtc, 'date-time', $expectedDateTime),
+ array($dateUtc, 'date-time-http', 'Sat, 13 Oct 2012 16:15:46 GMT'),
+ array($dateUtc, 'date', '2012-10-13'),
+ array($dateUtc, 'timestamp', strtotime($dateUtc)),
+ array(new \DateTime($dateUtc), 'timestamp', strtotime($dateUtc)),
+ array($dateUtc, 'time', '16:15:46'),
+ array(strtotime($dateUtc), 'time', '16:15:46'),
+ array(strtotime($dateUtc), 'timestamp', strtotime($dateUtc)),
+ array('true', 'boolean-string', 'true'),
+ array(true, 'boolean-string', 'true'),
+ array('false', 'boolean-string', 'false'),
+ array(false, 'boolean-string', 'false'),
+ array('1350144946', 'date-time', $expectedDateTime),
+ array(1350144946, 'date-time', $expectedDateTime),
+ array($dateOffset, 'date-time', $expectedDateTime)
+ );
+ }
+
+ /**
+ * @dataProvider dateTimeProvider
+ */
+ public function testFilters($value, $format, $result)
+ {
+ $this->assertEquals($result, SchemaFormatter::format($format, $value));
+ }
+
+ /**
+ * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesDateTimeInput()
+ {
+ SchemaFormatter::format('date-time', false);
+ }
+
+ public function testEnsuresTimestampsAreIntegers()
+ {
+ $t = time();
+ $result = SchemaFormatter::format('timestamp', $t);
+ $this->assertSame($t, $result);
+ $this->assertInternalType('int', $result);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php
new file mode 100644
index 0000000..4d6cc87
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php
@@ -0,0 +1,326 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\SchemaValidator;
+
+/**
+ * @covers Guzzle\Service\Description\SchemaValidator
+ */
+class SchemaValidatorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var SchemaValidator */
+ protected $validator;
+
+ public function setUp()
+ {
+ $this->validator = new SchemaValidator();
+ }
+
+ public function testValidatesArrayListsAreNumericallyIndexed()
+ {
+ $value = array(array(1));
+ $this->assertFalse($this->validator->validate($this->getComplexParam(), $value));
+ $this->assertEquals(
+ array('[Foo][0] must be an array of properties. Got a numerically indexed array.'),
+ $this->validator->getErrors()
+ );
+ }
+
+ public function testValidatesArrayListsContainProperItems()
+ {
+ $value = array(true);
+ $this->assertFalse($this->validator->validate($this->getComplexParam(), $value));
+ $this->assertEquals(
+ array('[Foo][0] must be of type object'),
+ $this->validator->getErrors()
+ );
+ }
+
+ public function testAddsDefaultValuesInLists()
+ {
+ $value = array(array());
+ $this->assertTrue($this->validator->validate($this->getComplexParam(), $value));
+ $this->assertEquals(array(array('Bar' => true)), $value);
+ }
+
+ public function testMergesDefaultValuesInLists()
+ {
+ $value = array(
+ array('Baz' => 'hello!'),
+ array('Bar' => false)
+ );
+ $this->assertTrue($this->validator->validate($this->getComplexParam(), $value));
+ $this->assertEquals(array(
+ array(
+ 'Baz' => 'hello!',
+ 'Bar' => true
+ ),
+ array('Bar' => false)
+ ), $value);
+ }
+
+ public function testCorrectlyConvertsParametersToArrayWhenArraysArePresent()
+ {
+ $param = $this->getComplexParam();
+ $result = $param->toArray();
+ $this->assertInternalType('array', $result['items']);
+ $this->assertEquals('array', $result['type']);
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getItems());
+ }
+
+ public function testAllowsInstanceOf()
+ {
+ $p = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'instanceOf' => get_class($this)
+ ));
+ $this->assertTrue($this->validator->validate($p, $this));
+ $this->assertFalse($this->validator->validate($p, $p));
+ $this->assertEquals(array('[foo] must be an instance of ' . __CLASS__), $this->validator->getErrors());
+ }
+
+ public function testEnforcesInstanceOfOnlyWhenObject()
+ {
+ $p = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => array('object', 'string'),
+ 'instanceOf' => get_class($this)
+ ));
+ $this->assertTrue($this->validator->validate($p, $this));
+ $s = 'test';
+ $this->assertTrue($this->validator->validate($p, $s));
+ }
+
+ public function testConvertsObjectsToArraysWhenToArrayInterface()
+ {
+ $o = $this->getMockBuilder('Guzzle\Common\ToArrayInterface')
+ ->setMethods(array('toArray'))
+ ->getMockForAbstractClass();
+ $o->expects($this->once())
+ ->method('toArray')
+ ->will($this->returnValue(array(
+ 'foo' => 'bar'
+ )));
+ $p = new Parameter(array(
+ 'name' => 'test',
+ 'type' => 'object',
+ 'properties' => array(
+ 'foo' => array('required' => 'true')
+ )
+ ));
+ $this->assertTrue($this->validator->validate($p, $o));
+ }
+
+ public function testMergesValidationErrorsInPropertiesWithParent()
+ {
+ $p = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array(
+ 'bar' => array('type' => 'string', 'required' => true, 'description' => 'This is what it does'),
+ 'test' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 5),
+ 'test2' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 2),
+ 'test3' => array('type' => 'integer', 'minimum' => 100),
+ 'test4' => array('type' => 'integer', 'maximum' => 10),
+ 'test5' => array('type' => 'array', 'maxItems' => 2),
+ 'test6' => array('type' => 'string', 'enum' => array('a', 'bc')),
+ 'test7' => array('type' => 'string', 'pattern' => '/[0-9]+/'),
+ 'test8' => array('type' => 'number'),
+ 'baz' => array(
+ 'type' => 'array',
+ 'minItems' => 2,
+ 'required' => true,
+ "items" => array("type" => "string")
+ )
+ )
+ ));
+
+ $value = array(
+ 'test' => 'a',
+ 'test2' => 'abc',
+ 'baz' => array(false),
+ 'test3' => 10,
+ 'test4' => 100,
+ 'test5' => array(1, 3, 4),
+ 'test6' => 'Foo',
+ 'test7' => 'abc',
+ 'test8' => 'abc'
+ );
+
+ $this->assertFalse($this->validator->validate($p, $value));
+ $this->assertEquals(array (
+ '[foo][bar] is a required string: This is what it does',
+ '[foo][baz] must contain 2 or more elements',
+ '[foo][baz][0] must be of type string',
+ '[foo][test2] length must be less than or equal to 2',
+ '[foo][test3] must be greater than or equal to 100',
+ '[foo][test4] must be less than or equal to 10',
+ '[foo][test5] must contain 2 or fewer elements',
+ '[foo][test6] must be one of "a" or "bc"',
+ '[foo][test7] must match the following regular expression: /[0-9]+/',
+ '[foo][test8] must be of type number',
+ '[foo][test] length must be greater than or equal to 2',
+ ), $this->validator->getErrors());
+ }
+
+ public function testHandlesNullValuesInArraysWithDefaults()
+ {
+ $p = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'bar' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'foo' => array('default' => 'hi')
+ )
+ )
+ )
+ ));
+ $value = array();
+ $this->assertTrue($this->validator->validate($p, $value));
+ $this->assertEquals(array('bar' => array('foo' => 'hi')), $value);
+ }
+
+ public function testFailsWhenNullValuesInArraysWithNoDefaults()
+ {
+ $p = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'bar' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array('foo' => array('type' => 'string'))
+ )
+ )
+ ));
+ $value = array();
+ $this->assertFalse($this->validator->validate($p, $value));
+ $this->assertEquals(array('[foo][bar] is a required object'), $this->validator->getErrors());
+ }
+
+ public function testChecksTypes()
+ {
+ $p = new SchemaValidator();
+ $r = new \ReflectionMethod($p, 'determineType');
+ $r->setAccessible(true);
+ $this->assertEquals('any', $r->invoke($p, 'any', 'hello'));
+ $this->assertEquals(false, $r->invoke($p, 'foo', 'foo'));
+ $this->assertEquals('string', $r->invoke($p, 'string', 'hello'));
+ $this->assertEquals(false, $r->invoke($p, 'string', false));
+ $this->assertEquals('integer', $r->invoke($p, 'integer', 1));
+ $this->assertEquals(false, $r->invoke($p, 'integer', 'abc'));
+ $this->assertEquals('numeric', $r->invoke($p, 'numeric', 1));
+ $this->assertEquals('numeric', $r->invoke($p, 'numeric', '1'));
+ $this->assertEquals('number', $r->invoke($p, 'number', 1));
+ $this->assertEquals('number', $r->invoke($p, 'number', '1'));
+ $this->assertEquals(false, $r->invoke($p, 'numeric', 'a'));
+ $this->assertEquals('boolean', $r->invoke($p, 'boolean', true));
+ $this->assertEquals('boolean', $r->invoke($p, 'boolean', false));
+ $this->assertEquals(false, $r->invoke($p, 'boolean', 'false'));
+ $this->assertEquals('null', $r->invoke($p, 'null', null));
+ $this->assertEquals(false, $r->invoke($p, 'null', 'abc'));
+ $this->assertEquals('array', $r->invoke($p, 'array', array()));
+ $this->assertEquals(false, $r->invoke($p, 'array', 'foo'));
+ }
+
+ public function testValidatesFalseAdditionalProperties()
+ {
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array('bar' => array('type' => 'string')),
+ 'additionalProperties' => false
+ ));
+ $value = array('test' => '123');
+ $this->assertFalse($this->validator->validate($param, $value));
+ $this->assertEquals(array('[foo][test] is not an allowed property'), $this->validator->getErrors());
+ $value = array('bar' => '123');
+ $this->assertTrue($this->validator->validate($param, $value));
+ }
+
+ public function testAllowsUndefinedAdditionalProperties()
+ {
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array('bar' => array('type' => 'string'))
+ ));
+ $value = array('test' => '123');
+ $this->assertTrue($this->validator->validate($param, $value));
+ }
+
+ public function testValidatesAdditionalProperties()
+ {
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'properties' => array('bar' => array('type' => 'string')),
+ 'additionalProperties' => array('type' => 'integer')
+ ));
+ $value = array('test' => 'foo');
+ $this->assertFalse($this->validator->validate($param, $value));
+ $this->assertEquals(array('[foo][test] must be of type integer'), $this->validator->getErrors());
+ }
+
+ public function testValidatesAdditionalPropertiesThatArrayArrays()
+ {
+ $param = new Parameter(array(
+ 'name' => 'foo',
+ 'type' => 'object',
+ 'additionalProperties' => array(
+ 'type' => 'array',
+ 'items' => array('type' => 'string')
+ )
+ ));
+ $value = array('test' => array(true));
+ $this->assertFalse($this->validator->validate($param, $value));
+ $this->assertEquals(array('[foo][test][0] must be of type string'), $this->validator->getErrors());
+ }
+
+ public function testIntegersCastToStringWhenTypeMismatch()
+ {
+ $param = new Parameter(array('name' => 'test', 'type' => 'string'));
+ $value = 12;
+ $this->assertTrue($this->validator->validate($param, $value));
+ $this->assertEquals('12', $value);
+ }
+
+ public function testRequiredMessageIncludesType()
+ {
+ $param = new Parameter(array('name' => 'test', 'type' => array('string', 'boolean'), 'required' => true));
+ $value = null;
+ $this->assertFalse($this->validator->validate($param, $value));
+ $this->assertEquals(array('[test] is a required string or boolean'), $this->validator->getErrors());
+ }
+
+ protected function getComplexParam()
+ {
+ return new Parameter(array(
+ 'name' => 'Foo',
+ 'type' => 'array',
+ 'required' => true,
+ 'min' => 1,
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Baz' => array(
+ 'type' => 'string',
+ ),
+ 'Bar' => array(
+ 'required' => true,
+ 'type' => 'boolean',
+ 'default' => true
+ )
+ )
+ )
+ ));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php
new file mode 100644
index 0000000..bbfd1d6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Description\ServiceDescriptionLoader;
+
+/**
+ * @covers Guzzle\Service\Description\ServiceDescriptionLoader
+ */
+class ServiceDescriptionLoaderTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testAllowsExtraData()
+ {
+ $d = ServiceDescription::factory(array(
+ 'foo' => true,
+ 'baz' => array('bar'),
+ 'apiVersion' => '123',
+ 'operations' => array()
+ ));
+
+ $this->assertEquals(true, $d->getData('foo'));
+ $this->assertEquals(array('bar'), $d->getData('baz'));
+ $this->assertEquals('123', $d->getApiVersion());
+ }
+
+ public function testAllowsDeepNestedInheritance()
+ {
+ $d = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'abstract' => array(
+ 'httpMethod' => 'HEAD',
+ 'parameters' => array(
+ 'test' => array('type' => 'string', 'required' => true)
+ )
+ ),
+ 'abstract2' => array('uri' => '/test', 'extends' => 'abstract'),
+ 'concrete' => array('extends' => 'abstract2'),
+ 'override' => array('extends' => 'abstract', 'httpMethod' => 'PUT'),
+ 'override2' => array('extends' => 'override', 'httpMethod' => 'POST', 'uri' => '/')
+ )
+ ));
+
+ $c = $d->getOperation('concrete');
+ $this->assertEquals('/test', $c->getUri());
+ $this->assertEquals('HEAD', $c->getHttpMethod());
+ $params = $c->getParams();
+ $param = $params['test'];
+ $this->assertEquals('string', $param->getType());
+ $this->assertTrue($param->getRequired());
+
+ // Ensure that merging HTTP method does not make an array
+ $this->assertEquals('PUT', $d->getOperation('override')->getHttpMethod());
+ $this->assertEquals('POST', $d->getOperation('override2')->getHttpMethod());
+ $this->assertEquals('/', $d->getOperation('override2')->getUri());
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testThrowsExceptionWhenExtendingMissingCommand()
+ {
+ ServiceDescription::factory(array(
+ 'operations' => array(
+ 'concrete' => array(
+ 'extends' => 'missing'
+ )
+ )
+ ));
+ }
+
+ public function testAllowsMultipleInheritance()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'a' => array(
+ 'httpMethod' => 'GET',
+ 'parameters' => array(
+ 'a1' => array(
+ 'default' => 'foo',
+ 'required' => true,
+ 'prepend' => 'hi'
+ )
+ )
+ ),
+ 'b' => array(
+ 'extends' => 'a',
+ 'parameters' => array(
+ 'b2' => array()
+ )
+ ),
+ 'c' => array(
+ 'parameters' => array(
+ 'a1' => array(
+ 'default' => 'bar',
+ 'required' => true,
+ 'description' => 'test'
+ ),
+ 'c3' => array()
+ )
+ ),
+ 'd' => array(
+ 'httpMethod' => 'DELETE',
+ 'extends' => array('b', 'c'),
+ 'parameters' => array(
+ 'test' => array()
+ )
+ )
+ )
+ ));
+
+ $command = $description->getOperation('d');
+ $this->assertEquals('DELETE', $command->getHttpMethod());
+ $this->assertContains('a1', $command->getParamNames());
+ $this->assertContains('b2', $command->getParamNames());
+ $this->assertContains('c3', $command->getParamNames());
+ $this->assertContains('test', $command->getParamNames());
+
+ $this->assertTrue($command->getParam('a1')->getRequired());
+ $this->assertEquals('bar', $command->getParam('a1')->getDefault());
+ $this->assertEquals('test', $command->getParam('a1')->getDescription());
+ }
+
+ public function testAddsOtherFields()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(),
+ 'description' => 'Foo',
+ 'apiVersion' => 'bar'
+ ));
+ $this->assertEquals('Foo', $description->getDescription());
+ $this->assertEquals('bar', $description->getApiVersion());
+ }
+
+ public function testCanLoadNestedExtends()
+ {
+ $description = ServiceDescription::factory(array(
+ 'operations' => array(
+ 'root' => array(
+ 'class' => 'foo'
+ ),
+ 'foo' => array(
+ 'extends' => 'root',
+ 'parameters' => array(
+ 'baz' => array('type' => 'string')
+ )
+ ),
+ 'foo_2' => array(
+ 'extends' => 'foo',
+ 'parameters' => array(
+ 'bar' => array('type' => 'string')
+ )
+ ),
+ 'foo_3' => array(
+ 'class' => 'bar',
+ 'parameters' => array(
+ 'bar2' => array('type' => 'string')
+ )
+ ),
+ 'foo_4' => array(
+ 'extends' => array('foo_2', 'foo_3'),
+ 'parameters' => array(
+ 'bar3' => array('type' => 'string')
+ )
+ )
+ )
+ ));
+
+ $this->assertTrue($description->hasOperation('foo_4'));
+ $foo4 = $description->getOperation('foo_4');
+ $this->assertTrue($foo4->hasParam('baz'));
+ $this->assertTrue($foo4->hasParam('bar'));
+ $this->assertTrue($foo4->hasParam('bar2'));
+ $this->assertTrue($foo4->hasParam('bar3'));
+ $this->assertEquals('bar', $foo4->getClass());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php
new file mode 100644
index 0000000..ff25452
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace Guzzle\Tests\Service\Description;
+
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Description\Operation;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Client;
+
+/**
+ * @covers Guzzle\Service\Description\ServiceDescription
+ */
+class ServiceDescriptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ protected $serviceData;
+
+ public function setup()
+ {
+ $this->serviceData = array(
+ 'test_command' => new Operation(array(
+ 'name' => 'test_command',
+ 'description' => 'documentationForCommand',
+ 'httpMethod' => 'DELETE',
+ 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand',
+ 'parameters' => array(
+ 'bucket' => array('required' => true),
+ 'key' => array('required' => true)
+ )
+ ))
+ );
+ }
+
+ /**
+ * @covers Guzzle\Service\Description\ServiceDescription::factory
+ * @covers Guzzle\Service\Description\ServiceDescriptionLoader::build
+ */
+ public function testFactoryDelegatesToConcreteFactories()
+ {
+ $jsonFile = __DIR__ . '/../../TestData/test_service.json';
+ $this->assertInstanceOf('Guzzle\Service\Description\ServiceDescription', ServiceDescription::factory($jsonFile));
+ }
+
+ public function testConstructor()
+ {
+ $service = new ServiceDescription(array('operations' => $this->serviceData));
+ $this->assertEquals(1, count($service->getOperations()));
+ $this->assertFalse($service->hasOperation('foobar'));
+ $this->assertTrue($service->hasOperation('test_command'));
+ }
+
+ public function testIsSerializable()
+ {
+ $service = new ServiceDescription(array('operations' => $this->serviceData));
+ $data = serialize($service);
+ $d2 = unserialize($data);
+ $this->assertEquals(serialize($service), serialize($d2));
+ }
+
+ public function testSerializesParameters()
+ {
+ $service = new ServiceDescription(array(
+ 'operations' => array(
+ 'foo' => new Operation(array('parameters' => array('foo' => array('type' => 'string'))))
+ )
+ ));
+ $serialized = serialize($service);
+ $this->assertContains('"parameters":{"foo":', $serialized);
+ $service = unserialize($serialized);
+ $this->assertTrue($service->getOperation('foo')->hasParam('foo'));
+ }
+
+ public function testAllowsForJsonBasedArrayParamsFunctionalTest()
+ {
+ $description = new ServiceDescription(array(
+ 'operations' => array(
+ 'test' => new Operation(array(
+ 'httpMethod' => 'PUT',
+ 'parameters' => array(
+ 'data' => array(
+ 'required' => true,
+ 'filters' => 'json_encode',
+ 'location' => 'body'
+ )
+ )
+ ))
+ )
+ ));
+ $client = new Client();
+ $client->setDescription($description);
+ $command = $client->getCommand('test', array(
+ 'data' => array(
+ 'foo' => 'bar'
+ )
+ ));
+
+ $request = $command->prepare();
+ $this->assertEquals('{"foo":"bar"}', (string) $request->getBody());
+ }
+
+ public function testContainsModels()
+ {
+ $d = new ServiceDescription(array(
+ 'operations' => array('foo' => array()),
+ 'models' => array(
+ 'Tag' => array('type' => 'object'),
+ 'Person' => array('type' => 'object')
+ )
+ ));
+ $this->assertTrue($d->hasModel('Tag'));
+ $this->assertTrue($d->hasModel('Person'));
+ $this->assertFalse($d->hasModel('Foo'));
+ $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $d->getModel('Tag'));
+ $this->assertNull($d->getModel('Foo'));
+ $this->assertContains('"models":{', serialize($d));
+ $this->assertEquals(array('Tag', 'Person'), array_keys($d->getModels()));
+ }
+
+ public function testCanAddModels()
+ {
+ $d = new ServiceDescription(array());
+ $this->assertFalse($d->hasModel('Foo'));
+ $d->addModel(new Parameter(array('name' => 'Foo')));
+ $this->assertTrue($d->hasModel('Foo'));
+ }
+
+ public function testHasAttributes()
+ {
+ $d = new ServiceDescription(array(
+ 'operations' => array(),
+ 'name' => 'Name',
+ 'description' => 'Description',
+ 'apiVersion' => '1.24'
+ ));
+
+ $this->assertEquals('Name', $d->getName());
+ $this->assertEquals('Description', $d->getDescription());
+ $this->assertEquals('1.24', $d->getApiVersion());
+
+ $s = serialize($d);
+ $this->assertContains('"name":"Name"', $s);
+ $this->assertContains('"description":"Description"', $s);
+ $this->assertContains('"apiVersion":"1.24"', $s);
+
+ $d = unserialize($s);
+ $this->assertEquals('Name', $d->getName());
+ $this->assertEquals('Description', $d->getDescription());
+ $this->assertEquals('1.24', $d->getApiVersion());
+ }
+
+ public function testPersistsCustomAttributes()
+ {
+ $data = array(
+ 'operations' => array('foo' => array('class' => 'foo', 'parameters' => array())),
+ 'name' => 'Name',
+ 'description' => 'Test',
+ 'apiVersion' => '1.24',
+ 'auth' => 'foo',
+ 'keyParam' => 'bar'
+ );
+ $d = new ServiceDescription($data);
+ $d->setData('hello', 'baz');
+ $this->assertEquals('foo', $d->getData('auth'));
+ $this->assertEquals('baz', $d->getData('hello'));
+ $this->assertEquals('bar', $d->getData('keyParam'));
+ // responseClass and responseType are added by default
+ $data['operations']['foo']['responseClass'] = 'array';
+ $data['operations']['foo']['responseType'] = 'primitive';
+ $this->assertEquals($data + array('hello' => 'baz'), json_decode($d->serialize(), true));
+ }
+
+ public function testHasToArray()
+ {
+ $data = array(
+ 'operations' => array(),
+ 'name' => 'Name',
+ 'description' => 'Test'
+ );
+ $d = new ServiceDescription($data);
+ $arr = $d->toArray();
+ $this->assertEquals('Name', $arr['name']);
+ $this->assertEquals('Test', $arr['description']);
+ }
+
+ public function testReturnsNullWhenRetrievingMissingOperation()
+ {
+ $s = new ServiceDescription(array());
+ $this->assertNull($s->getOperation('foo'));
+ }
+
+ public function testCanAddOperations()
+ {
+ $s = new ServiceDescription(array());
+ $this->assertFalse($s->hasOperation('Foo'));
+ $s->addOperation(new Operation(array('name' => 'Foo')));
+ $this->assertTrue($s->hasOperation('Foo'));
+ }
+
+ /**
+ * @expectedException Guzzle\Common\Exception\InvalidArgumentException
+ */
+ public function testValidatesOperationTypes()
+ {
+ $s = new ServiceDescription(array(
+ 'operations' => array('foo' => new \stdClass())
+ ));
+ }
+
+ public function testHasBaseUrl()
+ {
+ $description = new ServiceDescription(array('baseUrl' => 'http://foo.com'));
+ $this->assertEquals('http://foo.com', $description->getBaseUrl());
+ $description->setBaseUrl('http://foobar.com');
+ $this->assertEquals('http://foobar.com', $description->getBaseUrl());
+ }
+
+ public function testCanUseBasePath()
+ {
+ $description = new ServiceDescription(array('basePath' => 'http://foo.com'));
+ $this->assertEquals('http://foo.com', $description->getBaseUrl());
+ }
+
+ public function testModelsHaveNames()
+ {
+ $desc = array(
+ 'models' => array(
+ 'date' => array('type' => 'string'),
+ 'user'=> array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'dob' => array('$ref' => 'date')
+ )
+ )
+ )
+ );
+
+ $s = ServiceDescription::factory($desc);
+ $this->assertEquals('date', $s->getModel('date')->getName());
+ $this->assertEquals('dob', $s->getModel('user')->getProperty('dob')->getName());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php
new file mode 100644
index 0000000..be0d4ac
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Guzzle\Tests\Service\Exception;
+
+use Guzzle\Http\Exception\MultiTransferException;
+use Guzzle\Http\Message\Request;
+use Guzzle\Service\Exception\CommandTransferException;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+
+/**
+ * @covers Guzzle\Service\Exception\CommandTransferException
+ */
+class CommandTransferExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testStoresCommands()
+ {
+ $c1 = new MockCommand();
+ $c2 = new MockCommand();
+ $e = new CommandTransferException('Test');
+ $e->addSuccessfulCommand($c1)->addFailedCommand($c2);
+ $this->assertSame(array($c1), $e->getSuccessfulCommands());
+ $this->assertSame(array($c2), $e->getFailedCommands());
+ $this->assertSame(array($c1, $c2), $e->getAllCommands());
+ }
+
+ public function testConvertsMultiExceptionIntoCommandTransfer()
+ {
+ $r1 = new Request('GET', 'http://foo.com');
+ $r2 = new Request('GET', 'http://foobaz.com');
+ $e = new MultiTransferException('Test', 123);
+ $e->addSuccessfulRequest($r1)->addFailedRequest($r2);
+ $ce = CommandTransferException::fromMultiTransferException($e);
+
+ $this->assertInstanceOf('Guzzle\Service\Exception\CommandTransferException', $ce);
+ $this->assertEquals('Test', $ce->getMessage());
+ $this->assertEquals(123, $ce->getCode());
+ $this->assertSame(array($r1), $ce->getSuccessfulRequests());
+ $this->assertSame(array($r2), $ce->getFailedRequests());
+ }
+
+ public function testCanRetrieveExceptionForCommand()
+ {
+ $r1 = new Request('GET', 'http://foo.com');
+ $e1 = new \Exception('foo');
+ $c1 = $this->getMockBuilder('Guzzle\Tests\Service\Mock\Command\MockCommand')
+ ->setMethods(array('getRequest'))
+ ->getMock();
+ $c1->expects($this->once())->method('getRequest')->will($this->returnValue($r1));
+
+ $e = new MultiTransferException('Test', 123);
+ $e->addFailedRequestWithException($r1, $e1);
+ $ce = CommandTransferException::fromMultiTransferException($e);
+ $ce->addFailedCommand($c1);
+
+ $this->assertSame($e1, $ce->getExceptionForFailedCommand($c1));
+ }
+
+ public function testAddsNonRequestExceptions()
+ {
+ $e = new MultiTransferException();
+ $e->add(new \Exception('bar'));
+ $e->addFailedRequestWithException(new Request('GET', 'http://www.foo.com'), new \Exception('foo'));
+ $ce = CommandTransferException::fromMultiTransferException($e);
+ $this->assertEquals(2, count($ce));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php
new file mode 100644
index 0000000..6455295
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Guzzle\Tests\Service\Exception;
+
+use Guzzle\Service\Exception\InconsistentClientTransferException;
+
+class InconsistentClientTransferExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testStoresCommands()
+ {
+ $items = array('foo', 'bar');
+ $e = new InconsistentClientTransferException($items);
+ $this->assertEquals($items, $e->getCommands());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php
new file mode 100644
index 0000000..ef789d8
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Guzzle\Tests\Service\Exception;
+
+use Guzzle\Service\Exception\ValidationException;
+
+class ValidationExceptionTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testCanSetAndRetrieveErrors()
+ {
+ $errors = array('foo', 'bar');
+
+ $e = new ValidationException('Foo');
+ $e->setErrors($errors);
+ $this->assertEquals($errors, $e->getErrors());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php
new file mode 100644
index 0000000..4ab423e
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock\Command;
+
+use Guzzle\Service\Description\Operation;
+
+class IterableCommand extends MockCommand
+{
+ protected function createOperation()
+ {
+ return new Operation(array(
+ 'name' => 'iterable_command',
+ 'parameters' => array(
+ 'page_size' => array('type' => 'integer'),
+ 'next_token' => array('type' => 'string')
+ )
+ ));
+ }
+
+ protected function build()
+ {
+ $this->request = $this->client->createRequest('GET');
+
+ // Add the next token and page size query string values
+ $this->request->getQuery()->set('next_token', $this->get('next_token'));
+
+ if ($this->get('page_size')) {
+ $this->request->getQuery()->set('page_size', $this->get('page_size'));
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php
new file mode 100644
index 0000000..831a7e7
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock\Command;
+
+use Guzzle\Service\Description\Operation;
+
+class MockCommand extends \Guzzle\Service\Command\AbstractCommand
+{
+ protected function createOperation()
+ {
+ return new Operation(array(
+ 'name' => get_called_class() == __CLASS__ ? 'mock_command' : 'sub.sub',
+ 'httpMethod' => 'POST',
+ 'parameters' => array(
+ 'test' => array(
+ 'default' => 123,
+ 'required' => true,
+ 'doc' => 'Test argument'
+ ),
+ '_internal' => array(
+ 'default' => 'abc'
+ ),
+ 'foo' => array('filters' => array('strtoupper'))
+ )
+ ));
+ }
+
+ protected function build()
+ {
+ $this->request = $this->client->createRequest();
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php
new file mode 100644
index 0000000..72ae1f6
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock\Command;
+
+use Guzzle\Service\Description\Operation;
+
+class OtherCommand extends MockCommand
+{
+ protected function createOperation()
+ {
+ return new Operation(array(
+ 'name' => 'other_command',
+ 'parameters' => array(
+ 'test' => array(
+ 'default' => '123',
+ 'required' => true,
+ 'doc' => 'Test argument'
+ ),
+ 'other' => array(),
+ 'arg' => array('type' => 'string'),
+ 'static' => array('static' => true, 'default' => 'this is static')
+ )
+ ));
+ }
+
+ protected function build()
+ {
+ $this->request = $this->client->getRequest('HEAD');
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php
new file mode 100644
index 0000000..d348480
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock\Command\Sub;
+
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+
+class Sub extends MockCommand {}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/MockClient.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/MockClient.php
new file mode 100644
index 0000000..6b9f55a
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/MockClient.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock;
+
+use Guzzle\Common\Collection;
+use Guzzle\Service\Client;
+
+/**
+ * Mock Guzzle Service
+ */
+class MockClient extends Client
+{
+ /**
+ * Factory method to create a new mock client
+ *
+ * @param array|Collection $config Configuration data. Array keys:
+ * base_url - Base URL of web service
+ * api_version - API version
+ * scheme - URI scheme: http or https
+ * * username - API username
+ * * password - API password
+ * * subdomain - Unfuddle account subdomain
+ *
+ * @return MockClient
+ */
+ public static function factory($config = array())
+ {
+ $config = Collection::fromConfig($config, array(
+ 'base_url' => '{scheme}://127.0.0.1:8124/{api_version}/{subdomain}',
+ 'scheme' => 'http',
+ 'api_version' => 'v1'
+ ), array('username', 'password', 'subdomain'));
+
+ return new self($config->get('base_url'), $config);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php
new file mode 100644
index 0000000..8faf412
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Guzzle\Tests\Service\Mock\Model;
+
+use Guzzle\Service\Resource\ResourceIterator;
+
+class MockCommandIterator extends ResourceIterator
+{
+ public $calledNext = 0;
+
+ protected function sendRequest()
+ {
+ if ($this->nextToken) {
+ $this->command->set('next_token', $this->nextToken);
+ }
+
+ $this->command->set('page_size', (int) $this->calculatePageSize());
+ $this->command->execute();
+
+ $data = json_decode($this->command->getResponse()->getBody(true), true);
+
+ $this->nextToken = $data['next_token'];
+
+ return $data['resources'];
+ }
+
+ public function next()
+ {
+ $this->calledNext++;
+ parent::next();
+ }
+
+ public function getResources()
+ {
+ return $this->resources;
+ }
+
+ public function getIteratedCount()
+ {
+ return $this->iteratedCount;
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php
new file mode 100644
index 0000000..41c2073
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Guzzle\Tests\Service\Resource;
+
+use Guzzle\Service\Resource\CompositeResourceIteratorFactory;
+use Guzzle\Service\Resource\ResourceIteratorClassFactory;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+
+/**
+ * @covers Guzzle\Service\Resource\CompositeResourceIteratorFactory
+ */
+class CompositeResourceIteratorFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Iterator was not found for mock_command
+ */
+ public function testEnsuresIteratorClassExists()
+ {
+ $factory = new CompositeResourceIteratorFactory(array(
+ new ResourceIteratorClassFactory(array('Foo', 'Bar'))
+ ));
+ $cmd = new MockCommand();
+ $this->assertFalse($factory->canBuild($cmd));
+ $factory->build($cmd);
+ }
+
+ public function testBuildsResourceIterators()
+ {
+ $f1 = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model');
+ $factory = new CompositeResourceIteratorFactory(array());
+ $factory->addFactory($f1);
+ $command = new MockCommand();
+ $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock'));
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php
new file mode 100644
index 0000000..d166e92
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Guzzle\Tests\Service\Resource;
+
+use Guzzle\Service\Resource\MapResourceIteratorFactory;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+
+/**
+ * @covers Guzzle\Service\Resource\MapResourceIteratorFactory
+ */
+class MapResourceIteratorFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Iterator was not found for mock_command
+ */
+ public function testEnsuresIteratorClassExists()
+ {
+ $factory = new MapResourceIteratorFactory(array('Foo', 'Bar'));
+ $factory->build(new MockCommand());
+ }
+
+ public function testBuildsResourceIterators()
+ {
+ $factory = new MapResourceIteratorFactory(array(
+ 'mock_command' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator'
+ ));
+ $iterator = $factory->build(new MockCommand());
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ }
+
+ public function testUsesWildcardMappings()
+ {
+ $factory = new MapResourceIteratorFactory(array(
+ '*' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator'
+ ));
+ $iterator = $factory->build(new MockCommand());
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php
new file mode 100644
index 0000000..7214133
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Guzzle\Tests\Service\Resource;
+
+use Guzzle\Service\Resource\Model;
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Common\Collection;
+
+/**
+ * @covers Guzzle\Service\Resource\Model
+ */
+class ModelTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testOwnsStructure()
+ {
+ $param = new Parameter(array('type' => 'object'));
+ $model = new Model(array('foo' => 'bar'), $param);
+ $this->assertSame($param, $model->getStructure());
+ $this->assertEquals('bar', $model->get('foo'));
+ $this->assertEquals('bar', $model['foo']);
+ }
+
+ public function testCanBeUsedWithoutStructure()
+ {
+ $model = new Model(array(
+ 'Foo' => 'baz',
+ 'Bar' => array(
+ 'Boo' => 'Bam'
+ )
+ ));
+ $transform = function ($key, $value) {
+ return ($value && is_array($value)) ? new Collection($value) : $value;
+ };
+ $model = $model->map($transform);
+ $this->assertInstanceOf('Guzzle\Common\Collection', $model->getPath('Bar'));
+ }
+
+ public function testAllowsFiltering()
+ {
+ $model = new Model(array(
+ 'Foo' => 'baz',
+ 'Bar' => 'a'
+ ));
+ $model = $model->filter(function ($i, $v) {
+ return $v[0] == 'a';
+ });
+ $this->assertEquals(array('Bar' => 'a'), $model->toArray());
+ }
+
+ public function testDoesNotIncludeEmptyStructureInString()
+ {
+ $model = new Model(array('Foo' => 'baz'));
+ $str = (string) $model;
+ $this->assertContains('Debug output of model', $str);
+ $this->assertNotContains('Model structure', $str);
+ }
+
+ public function testDoesIncludeModelStructureInString()
+ {
+ $model = new Model(array('Foo' => 'baz'), new Parameter(array('name' => 'Foo')));
+ $str = (string) $model;
+ $this->assertContains('Debug output of Foo model', $str);
+ $this->assertContains('Model structure', $str);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php
new file mode 100644
index 0000000..7b061b5
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Guzzle\Tests\Service\Resource;
+
+use Guzzle\Service\Resource\ResourceIteratorClassFactory;
+use Guzzle\Tests\Service\Mock\Command\MockCommand;
+
+/**
+ * @covers Guzzle\Service\Resource\ResourceIteratorClassFactory
+ * @covers Guzzle\Service\Resource\AbstractResourceIteratorFactory
+ */
+class ResourceIteratorClassFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Iterator was not found for mock_command
+ */
+ public function testEnsuresIteratorClassExists()
+ {
+ $factory = new ResourceIteratorClassFactory(array('Foo', 'Bar'));
+ $factory->registerNamespace('Baz');
+ $command = new MockCommand();
+ $factory->build($command);
+ }
+
+ public function testBuildsResourceIterators()
+ {
+ $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model');
+ $command = new MockCommand();
+ $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock'));
+ $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator);
+ }
+
+ public function testChecksIfCanBuild()
+ {
+ $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service');
+ $this->assertFalse($factory->canBuild(new MockCommand()));
+ $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model');
+ $this->assertTrue($factory->canBuild(new MockCommand()));
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php
new file mode 100644
index 0000000..573fb6d
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Guzzle\Tests\Service\Resource;
+
+use Guzzle\Service\Resource\ResourceIterator;
+use Guzzle\Tests\Service\Mock\Model\MockCommandIterator;
+
+/**
+ * @group server
+ * @covers Guzzle\Service\Resource\ResourceIterator
+ */
+class ResourceIteratorTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ public function testDescribesEvents()
+ {
+ $this->assertInternalType('array', ResourceIterator::getAllEvents());
+ }
+
+ public function testConstructorConfiguresDefaults()
+ {
+ $ri = $this->getMockForAbstractClass('Guzzle\\Service\\Resource\\ResourceIterator', array(
+ $this->getServiceBuilder()->get('mock')->getCommand('iterable_command'),
+ array(
+ 'limit' => 10,
+ 'page_size' => 3
+ )
+ ), 'MockIterator');
+
+ $this->assertEquals(false, $ri->getNextToken());
+ $this->assertEquals(false, $ri->current());
+ }
+
+ public function testSendsRequestsForNextSetOfResources()
+ {
+ // Queue up an array of responses for iterating
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }"
+ ));
+
+ // Create a new resource iterator using the IterableCommand mock
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array(
+ 'page_size' => 3
+ ));
+
+ // Ensure that no requests have been sent yet
+ $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false)));
+
+ //$this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray());
+ $ri->toArray();
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals(3, count($requests));
+
+ $this->assertEquals(3, $requests[0]->getQuery()->get('page_size'));
+ $this->assertEquals(3, $requests[1]->getQuery()->get('page_size'));
+ $this->assertEquals(3, $requests[2]->getQuery()->get('page_size'));
+
+ // Reset and resend
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }",
+ ));
+
+ $d = array();
+ foreach ($ri as $data) {
+ $d[] = $data;
+ }
+ $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $d);
+ }
+
+ public function testCalculatesPageSize()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"j\", \"k\"] }"
+ ));
+
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array(
+ 'page_size' => 3,
+ 'limit' => 7
+ ));
+
+ $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray());
+ $requests = $this->getServer()->getReceivedRequests(true);
+ $this->assertEquals(3, count($requests));
+ $this->assertEquals(3, $requests[0]->getQuery()->get('page_size'));
+ $this->assertEquals(3, $requests[1]->getQuery()->get('page_size'));
+ $this->assertEquals(1, $requests[2]->getQuery()->get('page_size'));
+ }
+
+ public function testUseAsArray()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"g\", \"h\", \"i\"] }"
+ ));
+
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'));
+
+ // Ensure that the key is never < 0
+ $this->assertEquals(0, $ri->key());
+ $this->assertEquals(0, count($ri));
+
+ // Ensure that the iterator can be used as KVP array
+ $data = array();
+ foreach ($ri as $key => $value) {
+ $data[$key] = $value;
+ }
+
+ // Ensure that the iterate is countable
+ $this->assertEquals(6, count($ri));
+ $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i'), $data);
+ }
+
+ public function testBailsWhenSendReturnsNoResults()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [] }"
+ ));
+
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'));
+
+ // Ensure that the iterator can be used as KVP array
+ $data = $ri->toArray();
+
+ // Ensure that the iterate is countable
+ $this->assertEquals(3, count($ri));
+ $this->assertEquals(array('d', 'e', 'f'), $data);
+
+ $this->assertEquals(2, $ri->getRequestCount());
+ }
+
+ public function testHoldsDataOptions()
+ {
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'));
+ $this->assertNull($ri->get('foo'));
+ $this->assertSame($ri, $ri->set('foo', 'bar'));
+ $this->assertEquals('bar', $ri->get('foo'));
+ }
+
+ public function testSettingLimitOrPageSizeClearsData()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }",
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }"
+ ));
+
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'));
+ $ri->toArray();
+ $this->assertNotEmpty($this->readAttribute($ri, 'resources'));
+
+ $ri->setLimit(10);
+ $this->assertEmpty($this->readAttribute($ri, 'resources'));
+
+ $ri->toArray();
+ $this->assertNotEmpty($this->readAttribute($ri, 'resources'));
+ $ri->setPageSize(10);
+ $this->assertEmpty($this->readAttribute($ri, 'resources'));
+ }
+
+ public function testWorksWithCustomAppendIterator()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue(array(
+ "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }"
+ ));
+ $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'));
+ $a = new \Guzzle\Iterator\AppendIterator();
+ $a->append($ri);
+ $results = iterator_to_array($a, false);
+ $this->assertEquals(4, $ri->calledNext);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php
new file mode 100644
index 0000000..083aaa0
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Guzzle\Tests\Stream;
+
+use Guzzle\Stream\Stream;
+use Guzzle\Stream\PhpStreamRequestFactory;
+use Guzzle\Http\Client;
+
+/**
+ * @group server
+ * @covers \Guzzle\Stream\PhpStreamRequestFactory
+ */
+class PhpStreamRequestFactoryTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /** @var Client */
+ protected $client;
+
+ /** @var PhpStreamRequestFactory */
+ protected $factory;
+
+ protected function setUp()
+ {
+ $this->client = new Client($this->getServer()->getUrl());
+ $this->factory = new PhpStreamRequestFactory();
+ }
+
+ public function testOpensValidStreamByCreatingContext()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+ $request = $this->client->get('/');
+ $stream = $this->factory->fromRequest($request);
+ $this->assertEquals('hi', (string) $stream);
+ $headers = $this->factory->getLastResponseHeaders();
+ $this->assertContains('HTTP/1.1 200 OK', $headers);
+ $this->assertContains('Content-Length: 2', $headers);
+ $this->assertSame($headers, $stream->getCustomData('response_headers'));
+ $this->assertEquals(2, $stream->getSize());
+ }
+
+ public function testOpensValidStreamByPassingContextAndMerging()
+ {
+ $request = $this->client->get('/');
+ $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory')
+ ->setMethods(array('createContext', 'createStream'))
+ ->getMock();
+ $this->factory->expects($this->never())
+ ->method('createContext');
+ $this->factory->expects($this->once())
+ ->method('createStream')
+ ->will($this->returnValue(new Stream(fopen('php://temp', 'r'))));
+
+ $context = array('http' => array('method' => 'HEAD', 'ignore_errors' => false));
+ $this->factory->fromRequest($request, stream_context_create($context));
+ $options = stream_context_get_options($this->readAttribute($this->factory, 'context'));
+ $this->assertEquals('HEAD', $options['http']['method']);
+ $this->assertFalse($options['http']['ignore_errors']);
+ $this->assertEquals('1.1', $options['http']['protocol_version']);
+ }
+
+ public function testAppliesProxySettings()
+ {
+ $request = $this->client->get('/');
+ $request->getCurlOptions()->set(CURLOPT_PROXY, 'tcp://foo.com');
+ $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory')
+ ->setMethods(array('createStream'))
+ ->getMock();
+ $this->factory->expects($this->once())
+ ->method('createStream')
+ ->will($this->returnValue(new Stream(fopen('php://temp', 'r'))));
+ $this->factory->fromRequest($request);
+ $options = stream_context_get_options($this->readAttribute($this->factory, 'context'));
+ $this->assertEquals('tcp://foo.com', $options['http']['proxy']);
+ }
+
+ public function testAddsPostFields()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+ $request = $this->client->post('/', array('Foo' => 'Bar'), array('foo' => 'baz bar'));
+ $stream = $this->factory->fromRequest($request);
+ $this->assertEquals('hi', (string) $stream);
+
+ $headers = $this->factory->getLastResponseHeaders();
+ $this->assertContains('HTTP/1.1 200 OK', $headers);
+ $this->assertContains('Content-Length: 2', $headers);
+ $this->assertSame($headers, $stream->getCustomData('response_headers'));
+
+ $received = $this->getServer()->getReceivedRequests();
+ $this->assertEquals(1, count($received));
+ $this->assertContains('POST / HTTP/1.1', $received[0]);
+ $this->assertContains('host: ', $received[0]);
+ $this->assertContains('user-agent: Guzzle/', $received[0]);
+ $this->assertContains('foo: Bar', $received[0]);
+ $this->assertContains('content-length: 13', $received[0]);
+ $this->assertContains('foo=baz%20bar', $received[0]);
+ }
+
+ public function testAddsBody()
+ {
+ $this->getServer()->flush();
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+ $request = $this->client->put('/', array('Foo' => 'Bar'), 'Testing...123');
+ $stream = $this->factory->fromRequest($request);
+ $this->assertEquals('hi', (string) $stream);
+
+ $headers = $this->factory->getLastResponseHeaders();
+ $this->assertContains('HTTP/1.1 200 OK', $headers);
+ $this->assertContains('Content-Length: 2', $headers);
+ $this->assertSame($headers, $stream->getCustomData('response_headers'));
+
+ $received = $this->getServer()->getReceivedRequests();
+ $this->assertEquals(1, count($received));
+ $this->assertContains('PUT / HTTP/1.1', $received[0]);
+ $this->assertContains('host: ', $received[0]);
+ $this->assertContains('user-agent: Guzzle/', $received[0]);
+ $this->assertContains('foo: Bar', $received[0]);
+ $this->assertContains('content-length: 13', $received[0]);
+ $this->assertContains('Testing...123', $received[0]);
+ }
+
+ public function testCanDisableSslValidation()
+ {
+ $request = $this->client->get('/');
+ $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);
+ $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory')
+ ->setMethods(array('createStream'))
+ ->getMock();
+ $this->factory->expects($this->once())
+ ->method('createStream')
+ ->will($this->returnValue(new Stream(fopen('php://temp', 'r'))));
+ $this->factory->fromRequest($request);
+ $options = stream_context_get_options($this->readAttribute($this->factory, 'context'));
+ $this->assertFalse($options['ssl']['verify_peer']);
+ }
+
+ public function testUsesSslValidationByDefault()
+ {
+ $request = $this->client->get('/');
+ $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory')
+ ->setMethods(array('createStream'))
+ ->getMock();
+ $this->factory->expects($this->once())
+ ->method('createStream')
+ ->will($this->returnValue(new Stream(fopen('php://temp', 'r'))));
+ $this->factory->fromRequest($request);
+ $options = stream_context_get_options($this->readAttribute($this->factory, 'context'));
+ $this->assertTrue($options['ssl']['verify_peer']);
+ $this->assertSame($request->getCurlOptions()->get(CURLOPT_CAINFO), $options['ssl']['cafile']);
+ }
+
+ public function testBasicAuthAddsUserAndPassToUrl()
+ {
+ $request = $this->client->get('/');
+ $request->setAuth('Foo', 'Bar');
+ $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory')
+ ->setMethods(array('createStream'))
+ ->getMock();
+ $this->factory->expects($this->once())
+ ->method('createStream')
+ ->will($this->returnValue(new Stream(fopen('php://temp', 'r'))));
+ $this->factory->fromRequest($request);
+ $this->assertContains('Foo:Bar@', (string) $this->readAttribute($this->factory, 'url'));
+ }
+
+ public function testCanCreateCustomStreamClass()
+ {
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi");
+ $request = $this->client->get('/');
+ $stream = $this->factory->fromRequest($request, array(), array('stream_class' => 'Guzzle\Http\EntityBody'));
+ $this->assertInstanceOf('Guzzle\Http\EntityBody', $stream);
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php
new file mode 100644
index 0000000..4973f25
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace Guzzle\Tests\Stream;
+
+use Guzzle\Stream\Stream;
+
+/**
+ * @group server
+ * @covers Guzzle\Stream\Stream
+ */
+class StreamTest extends \Guzzle\Tests\GuzzleTestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidArgument()
+ {
+ $stream = new Stream(true);
+ }
+
+ public function testConstructor()
+ {
+ $handle = fopen('php://temp', 'r+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals($handle, $stream->getStream());
+ $this->assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isLocal());
+ $this->assertTrue($stream->isSeekable());
+ $this->assertEquals('PHP', $stream->getWrapper());
+ $this->assertEquals('TEMP', $stream->getStreamType());
+ $this->assertEquals(4, $stream->getSize());
+ $this->assertEquals('php://temp', $stream->getUri());
+ $this->assertEquals(array(), $stream->getWrapperData());
+ $this->assertFalse($stream->isConsumed());
+ unset($stream);
+ }
+
+ public function testCanModifyStream()
+ {
+ $handle1 = fopen('php://temp', 'r+');
+ $handle2 = fopen('php://temp', 'r+');
+ $stream = new Stream($handle1);
+ $this->assertSame($handle1, $stream->getStream());
+ $stream->setStream($handle2, 10);
+ $this->assertEquals(10, $stream->getSize());
+ $this->assertSame($handle2, $stream->getStream());
+ }
+
+ public function testStreamClosesHandleOnDestruct()
+ {
+ $handle = fopen('php://temp', 'r');
+ $stream = new Stream($handle);
+ unset($stream);
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testConvertsToString()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('data', (string) $stream);
+ unset($stream);
+
+ $handle = fopen(__DIR__ . '/../TestData/FileBody.txt', 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals('', (string) $stream);
+ unset($stream);
+ }
+
+ public function testConvertsToStringAndRestoresCursorPos()
+ {
+ $handle = fopen('php://temp', 'w+');
+ $stream = new Stream($handle);
+ $stream->write('foobazbar');
+ $stream->seek(3);
+ $this->assertEquals('foobazbar', (string) $stream);
+ $this->assertEquals(3, $stream->ftell());
+ }
+
+ public function testIsConsumed()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertFalse($stream->isConsumed());
+ $stream->read(4);
+ $this->assertTrue($stream->isConsumed());
+ }
+
+ public function testAllowsSettingManualSize()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $stream->setSize(10);
+ $this->assertEquals(10, $stream->getSize());
+ unset($stream);
+ }
+
+ public function testWrapsStream()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertTrue($stream->isSeekable());
+ $this->assertTrue($stream->isReadable());
+ $this->assertTrue($stream->seek(0));
+ $this->assertEquals('da', $stream->read(2));
+ $this->assertEquals('ta', $stream->read(2));
+ $this->assertTrue($stream->seek(0));
+ $this->assertEquals('data', $stream->read(4));
+ $stream->write('_appended');
+ $stream->seek(0);
+ $this->assertEquals('data_appended', $stream->read(13));
+ }
+
+ public function testGetSize()
+ {
+ $size = filesize(__DIR__ . '/../../../bootstrap.php');
+ $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals($handle, $stream->getStream());
+ $this->assertEquals($size, $stream->getSize());
+ $this->assertEquals($size, $stream->getSize());
+ unset($stream);
+
+ // Make sure that false is returned when the size cannot be determined
+ $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+ $handle = fopen('http://127.0.0.1:' . $this->getServer()->getPort(), 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals(false, $stream->getSize());
+ unset($stream);
+ }
+
+ public function testEnsuresSizeIsConsistent()
+ {
+ $h = fopen('php://temp', 'r+');
+ fwrite($h, 'foo');
+ $stream = new Stream($h);
+ $this->assertEquals(3, $stream->getSize());
+ $stream->write('test');
+ $this->assertEquals(7, $stream->getSize());
+ fclose($h);
+ }
+
+ public function testAbstractsMetaData()
+ {
+ $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals('plainfile', $stream->getMetaData('wrapper_type'));
+ $this->assertEquals(null, $stream->getMetaData('wrapper_data'));
+ $this->assertInternalType('array', $stream->getMetaData());
+ }
+
+ public function testDoesNotAttemptToWriteToReadonlyStream()
+ {
+ $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals(0, $stream->write('foo'));
+ }
+
+ public function testProvidesStreamPosition()
+ {
+ $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r');
+ $stream = new Stream($handle);
+ $stream->read(2);
+ $this->assertSame(ftell($handle), $stream->ftell());
+ $this->assertEquals(2, $stream->ftell());
+ }
+
+ public function testRewindIsSeekZero()
+ {
+ $stream = new Stream(fopen('php://temp', 'w+'));
+ $stream->write('foobazbar');
+ $this->assertTrue($stream->rewind());
+ $this->assertEquals('foobazbar', $stream->read(9));
+ }
+
+ public function testCanDetachStream()
+ {
+ $r = fopen('php://temp', 'w+');
+ $stream = new Stream($r);
+ $stream->detachStream();
+ $this->assertNull($stream->getStream());
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json
new file mode 100644
index 0000000..c354ed7
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json
@@ -0,0 +1,3 @@
+{
+ "includes": ["foo.json"]
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json
new file mode 100644
index 0000000..765237b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json
@@ -0,0 +1,3 @@
+{
+ "includes": ["foo.json", "bar.json"]
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json
new file mode 100644
index 0000000..cee5005
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json
@@ -0,0 +1,8 @@
+{
+ "includes": ["recursive.json"],
+ "operations": {
+ "abstract": {
+ "httpMethod": "POST"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json
new file mode 100644
index 0000000..c354ed7
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json
@@ -0,0 +1,3 @@
+{
+ "includes": ["foo.json"]
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response
new file mode 100644
index 0000000..b6938a2
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Length: 0
+
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json
new file mode 100644
index 0000000..7b2a9da
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json
@@ -0,0 +1,18 @@
+{
+ "includes": [ "json2.json" ],
+ "services": {
+ "abstract": {
+ "access_key": "xyz",
+ "secret": "abc"
+ },
+ "mock": {
+ "class": "Guzzle\\Tests\\Service\\Mock\\MockClient",
+ "extends": "abstract",
+ "params": {
+ "username": "foo",
+ "password": "baz",
+ "subdomain": "bar"
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json
new file mode 100644
index 0000000..08e5566
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json
@@ -0,0 +1,11 @@
+{
+ "services": {
+ "foo": {
+ "class": "Guzzle\\Tests\\Service\\Mock\\MockClient",
+ "extends": "abstract",
+ "params": {
+ "baz": "bar"
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json
new file mode 100644
index 0000000..25452e4
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json
@@ -0,0 +1,71 @@
+{
+ "abstract": {
+ "access_key": "xyz",
+ "secret": "abc"
+ },
+ "mock": {
+ "class": "Guzzle\\Tests\\Service\\Mock\\MockClient",
+ "extends": "abstract",
+ "params": {
+ "username": "foo",
+ "password": "baz",
+ "subdomain": "bar"
+ }
+ },
+
+ "test.abstract.aws": {
+ "params": {
+ "access_key": "12345",
+ "secret_key": "abcd"
+ }
+ },
+
+ "test.s3": {
+ "class": "Guzzle\\Service\\Aws\\S3Client",
+ "extends": "test.abstract.aws",
+ "params": {
+ "devpay_product_token": "",
+ "devpay_user_token": ""
+ }
+ },
+
+ "test.simple_db": {
+ "class": "Guzzle\\Service\\Aws\\SimpleDb\\SimpleDbClient",
+ "extends": "test.abstract.aws"
+ },
+
+ "test.sqs": {
+ "class": "Guzzle\\Service\\Aws\\Sqs\\SqsClient",
+ "extends": "test.abstract.aws"
+ },
+
+ "test.centinel": {
+ "class": "Guzzle\\Service\\CardinalCommerce\\Centinel.CentinelClient",
+ "params": {
+ "password": "test",
+ "processor_id": "123",
+ "merchant_id": "456"
+ }
+ },
+
+ "test.mws": {
+ "class": "Guzzle\\Service\\Mws\\MwsClient",
+ "extends": "test.abstract.aws",
+ "params": {
+ "merchant_id": "ABCDE",
+ "marketplace_id": "FGHIJ",
+ "application_name": "GuzzleTest",
+ "application_version": "0.1",
+ "base_url": "https://mws.amazonservices.com"
+ }
+ },
+
+ "mock": {
+ "class": "Guzzle\\Tests\\Service\\Mock\\MockClient",
+ "params": {
+ "username": "test_user",
+ "password": "****",
+ "subdomain": "test"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json
new file mode 100644
index 0000000..01557ca
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json
@@ -0,0 +1,40 @@
+{
+ "includes": [ "test_service2.json" ],
+ "operations": {
+ "test": {
+ "uri": "/path"
+ },
+ "concrete": {
+ "extends": "abstract"
+ },
+ "foo_bar": {
+ "uri": "/testing",
+ "parameters": {
+ "other": {
+ "location": "json",
+ "location_key": "Other"
+ },
+ "test": {
+ "type": "object",
+ "location": "json",
+ "properties": {
+ "baz": {
+ "type": "boolean",
+ "default": true
+ },
+ "bar": {
+ "type": "string",
+ "filters": [
+ {
+ "method": "strtolower",
+ "args": ["test", "@value"]
+ },
+ "strtoupper"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json
new file mode 100644
index 0000000..66dd9ef
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json
@@ -0,0 +1,7 @@
+{
+ "operations": {
+ "abstract": {
+ "uri": "/abstract"
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json
new file mode 100644
index 0000000..ae2ae0b
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json
@@ -0,0 +1,40 @@
+{
+ "includes": [ "test_service2.json" ],
+ "operations": {
+ "test": {
+ "uri": "/path"
+ },
+ "concrete": {
+ "extends": "abstract"
+ },
+ "baz_qux": {
+ "uri": "/testing",
+ "parameters": {
+ "other": {
+ "location": "json",
+ "location_key": "Other"
+ },
+ "test": {
+ "type": "object",
+ "location": "json",
+ "properties": {
+ "baz": {
+ "type": "boolean",
+ "default": true
+ },
+ "bar": {
+ "type": "string",
+ "filters": [
+ {
+ "method": "strtolower",
+ "args": ["test", "@value"]
+ },
+ "strtoupper"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/tests/bootstrap.php b/vendor/guzzle/guzzle/tests/bootstrap.php
new file mode 100644
index 0000000..28908d3
--- /dev/null
+++ b/vendor/guzzle/guzzle/tests/bootstrap.php
@@ -0,0 +1,10 @@
+<?php
+error_reporting(E_ALL | E_STRICT);
+
+require_once 'PHPUnit/TextUI/TestRunner.php';
+require dirname(__DIR__) . '/vendor/autoload.php';
+
+// Add the services file to the default service builder
+$servicesFile = __DIR__ . '/Guzzle/Tests/TestData/services/services.json';
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory($servicesFile);
+Guzzle\Tests\GuzzleTestCase::setServiceBuilder($builder);
diff --git a/vendor/jamiebicknell/Sparkline/LICENSE.md b/vendor/jamiebicknell/Sparkline/LICENSE.md
new file mode 100644
index 0000000..fa81603
--- /dev/null
+++ b/vendor/jamiebicknell/Sparkline/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Jamie Bicknell - @jamiebicknell
+
+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. \ No newline at end of file
diff --git a/vendor/jamiebicknell/Sparkline/README.md b/vendor/jamiebicknell/Sparkline/README.md
new file mode 100644
index 0000000..0309b38
--- /dev/null
+++ b/vendor/jamiebicknell/Sparkline/README.md
@@ -0,0 +1,101 @@
+# Sparkline
+
+PHP script to generate sparklines, with browser cachine with ETag.
+
+## Usage
+
+```html
+<img src='sparkline.php?size=80x20&data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&back=fff&line=5bb763&fill=d5f7d8' />
+```
+
+## Examples
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493691.png' alt='EG1' /><br />
+`sparkline.php`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493692.png' alt='EG2' /><br />
+`sparkline.php?data=5`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493693.png' alt='EG3' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493694.png' alt='EG4' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&line=5bb763&fill=d5f7d8`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493695.png' alt='EG5' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&line=fd8626&fill=ffedde`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493696.png' alt='EG6' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&line=ed5565&fill=ffe2e2`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493697.png' alt='EG7' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&line=444&fill=eee`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493698.png' alt='EG8' /><br />
+`sparkline.php?data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16&line=31475c&fill=fff`
+
+<img src='http://jamiebicknell.github.io/Sparkline/sparkline_1403094493699.png' alt='EG9' /><br />
+`sparkline.php?size=185x40&data=2,4,5,6,10,7,8,5,7,7,11,8,6,9,11,9,13,14,12,16`
+
+
+## Query Parameters
+
+<table>
+ <tr>
+ <th>Key</th>
+ <th>Example Value</th>
+ <th>Default</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>size</td>
+ <td>100x25, 100</td>
+ <td>80x20</td>
+ <td>Width must be between 50 and 80<br />Height must be between 20 and 800</td>
+ </tr>
+ <tr>
+ <td>data</td>
+ <td>10,20,50,20,30,40,50,120,90</td>
+ <td></td>
+ <td>Comma separated list of values to plot</td>
+ </tr>
+ <tr>
+ <td>back</td>
+ <td>eeeeee, ddd</td>
+ <td>ffffff</td>
+ <td>Hexadecimal code for background colour</td>
+ </tr>
+ <tr>
+ <td>line</td>
+ <td>555555, 222</td>
+ <td>1388db</td>
+ <td>Hexadecimal code for line colour</td>
+ </tr>
+ <tr>
+ <td>fill</td>
+ <td>cccccc, bbb</td>
+ <td>e6f2fa</td>
+ <td>Hexadecimal code for fill colour</td>
+ </tr>
+</table>
+
+## Size Parameter
+
+<table>
+ <tr>
+ <th>Value</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>100</td>
+ <td>Creates a square image 100px in width and 100px in height</td>
+ </tr>
+ <tr>
+ <td>80x20</td>
+ <td>Creates an image 80px in width and 20px in height</td>
+ </tr>
+</table>
+
+## License
+
+Sparkline is licensed under the [MIT license](http://opensource.org/licenses/MIT), see [LICENSE.md](https://github.com/jamiebicknell/Sparkline/blob/master/LICENSE.md) for details. \ No newline at end of file
diff --git a/vendor/jamiebicknell/Sparkline/composer.json b/vendor/jamiebicknell/Sparkline/composer.json
new file mode 100644
index 0000000..061d792
--- /dev/null
+++ b/vendor/jamiebicknell/Sparkline/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "jamiebicknell/Sparkline",
+ "description": "PHP script to generate sparklines",
+ "keywords": [
+ "sparkline",
+ "sparklines",
+ "php",
+ "gd"
+ ],
+ "homepage": "http://github.com/jamiebicknell/Sparkline",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jamie Bicknell",
+ "homepage": "http://www.jamiebicknell.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.2.0"
+ }
+} \ No newline at end of file
diff --git a/vendor/jamiebicknell/Sparkline/sparkline.php b/vendor/jamiebicknell/Sparkline/sparkline.php
new file mode 100644
index 0000000..adfb9e1
--- /dev/null
+++ b/vendor/jamiebicknell/Sparkline/sparkline.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+Title: Sparkline
+URL: http://github.com/jamiebicknell/Sparkline
+Author: Jamie Bicknell
+Twitter: @jamiebicknell
+*/
+
+function isHex($string)
+{
+ return preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/i', $string);
+}
+
+function hexToRgb($hex)
+{
+ $hex = ltrim(strtolower($hex), '#');
+ $hex = isset($hex[3]) ? $hex : $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
+ $dec = hexdec($hex);
+ return array(0xFF & ($dec >> 0x10), 0xFF & ($dec >> 0x8), 0xFF & $dec);
+}
+
+$size = isset($_GET['size']) ? str_replace('x', '', $_GET['size']) != '' ? $_GET['size'] : '80x20' : '80x20';
+$back = isset($_GET['back']) ? isHex($_GET['back']) ? $_GET['back'] : 'ffffff' : 'ffffff';
+$line = isset($_GET['line']) ? isHex($_GET['line']) ? $_GET['line'] : '1388db' : '1388db';
+$fill = isset($_GET['fill']) ? isHex($_GET['fill']) ? $_GET['fill'] : 'e6f2fa' : 'e6f2fa';
+$data = isset($_GET['data']) ? explode(',', $_GET['data']) : array();
+
+list($w, $h) = explode('x', $size);
+$w = floor(max(50, min(800, $w)));
+$h = !strstr($size, 'x') ? $w : floor(max(20, min(800, $h)));
+$t = 1.75;
+$s = 4;
+
+$w *= $s;
+$h *= $s;
+$t *= $s;
+
+$salt = 'v1.0.1';
+$hash = md5($salt . $_SERVER['QUERY_STRING']);
+
+$data = (count($data) < 2) ? array_fill(0, 2, $data[0]) : $data;
+$count = count($data);
+$step = $w / ($count - 1);
+
+$min = min($data);
+$max = max($data);
+if ($max != $min) {
+ foreach ($data as $k => $v) {
+ $data[$k] -= $min;
+ }
+ $max = max($data);
+}
+
+if (!extension_loaded('gd')) {
+ die('GD extension is not installed');
+}
+
+if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
+ if ($_SERVER['HTTP_IF_NONE_MATCH'] == $hash) {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
+ die();
+ }
+}
+
+$im = imagecreatetruecolor($w, $h);
+list($r, $g, $b) = hexToRgb($back);
+$bg = imagecolorallocate($im, $r, $g, $b);
+list($r, $g, $b) = hexToRgb($line);
+$fg = imagecolorallocate($im, $r, $g, $b);
+list($r, $g, $b) = hexToRgb($fill);
+$lg = imagecolorallocate($im, $r, $g, $b);
+imagefill($im, 0, 0, $bg);
+
+imagesetthickness($im, $t);
+
+foreach ($data as $k => $v) {
+ $v = $v > 0 ? round($v / $max * $h) : 0;
+ $data[$k] = max($s, min($v, $h - $s));
+}
+
+$x1 = 0;
+$y1 = $h - $data[0];
+$line = array();
+$poly = array(0, $h + 50, $x1, $y1);
+for ($i = 1; $i < $count; $i++) {
+ $x2 = $x1 + $step;
+ $y2 = $h - $data[$i];
+ array_push($line, array($x1, $y1, $x2, $y2));
+ array_push($poly, $x2, $y2);
+ $x1 = $x2;
+ $y1 = $y2;
+}
+array_push($poly, $x2, $h + 50);
+
+imagefilledpolygon($im, $poly, $count + 2, $lg);
+
+foreach ($line as $k => $v) {
+ list($x1, $y1, $x2, $y2) = $v;
+ imageline($im, $x1, $y1, $x2, $y2, $fg);
+}
+
+$om = imagecreatetruecolor($w / $s, $h / $s);
+imagecopyresampled($om, $im, 0, 0, 0, 0, $w / $s, $h / $s, $w, $h);
+imagedestroy($im);
+
+header('Content-Type: image/png');
+header('Content-Disposition: inline; filename="sparkline_' . time() . substr(microtime(), 2, 3) . '.png"');
+header('ETag: ' . $hash);
+header('Accept-Ranges: none');
+header('Cache-Control: max-age=604800, must-revalidate');
+header('Expires: ' . gmdate('D, d M Y H:i:s T', strtotime('+7 days')));
+imagepng($om);
+imagedestroy($om);
diff --git a/vendor/league/oauth2-client/CHANGELOG.md b/vendor/league/oauth2-client/CHANGELOG.md
new file mode 100644
index 0000000..97e27c5
--- /dev/null
+++ b/vendor/league/oauth2-client/CHANGELOG.md
@@ -0,0 +1,154 @@
+# OAuth 2.0 Client Changelog
+
+## 0.12.1
+
+_Released: 2015-06-20_
+
+* FIX: Scope separators for LinkedIn and Instagram are now correctly a single space
+
+## 0.12.0
+
+_Released: 2015-06-15_
+
+* BREAK: LinkedIn Provider: Default scopes removed from LinkedIn Provider. See "[Managing LinkedIn Scopes](https://github.com/thephpleague/oauth2-client/blob/9cea9864c2e89bce1b922d1e37ba5378b3b0b264/README.md#managing-linkedin-scopes)" in the README for information on how to set scopes. See [#327](https://github.com/thephpleague/oauth2-client/pull/327) and [#307](https://github.com/thephpleague/oauth2-client/pull/307) for details on this change.
+* FIX: LinkedIn Provider: A scenario existed in which `publicProfileUrl` was not set, generating a PHP notice; this has been fixed.
+* FIX: Instagram Provider: Fixed scope separator.
+* Documentation updates and corrections.
+
+
+## 0.11.0
+
+_Released: 2015-04-25_
+
+* Identity Provider: Better handling of error responses
+* Documentation updates
+
+
+## 0.10.1
+
+_Released: 2015-04-02_
+
+* FIX: Invalid JSON triggering fatal error
+* FIX: Sending headers along with auth `getAccessToken()` requests
+* Now running Travis CI tests on PHP 7
+* Documentation updates
+
+
+## 0.10.0
+
+_Released: 2015-03-10_
+
+* Providers: Added `getHeaders()` to ProviderInterface and updated AbstractProvider to provide the method
+* Providers: Updated all bundled providers to support new `$authorizationHeader` property
+* Identity Provider: Update IDPException to account for empty strings
+* Identity Provider: Added `getResponseBody()` method to IDPException
+* Documentation updates, minor bug fixes, and coding standards fixes
+
+
+## 0.9.0
+
+_Released: 2015-02-24_
+
+* Add `AbstractProvider::prepareAccessTokenResult()` to provide additional token response preparation to providers
+* Remove custom provider code from AccessToken
+* Add links to README for Dropbox and Square providers
+
+
+## 0.8.1
+
+_Released: 2015-02-12_
+
+* Allow `approval_prompt` to be set by providers. This fixes an issue where some providers have problems if the `approval_prompt` is present in the query string.
+
+
+## 0.8.0
+
+_Released: 2015-02-10_
+
+* Facebook Provider: Upgrade to Graph API v2.2
+* Google Provider: Add `access_type` parameter for Google authorization URL
+* Get a more reliable response body on errors
+
+
+## 0.7.2
+
+_Released: 2015-02-03_
+
+* GitHub Provider: Fix regression
+* Documentation updates
+
+
+## 0.7.1
+
+_Released: 2015-01-06_
+
+* Google Provider: fixed issue where Google API was not returning the user ID
+
+
+## 0.7.0
+
+_Released: 2014-12-29_
+
+* Improvements to Provider\AbstractProvider (addition of `userUid()`, `userEmail()`, and `userScreenName()`)
+* GitHub Provider: Support for GitHub Enterprise
+* GitHub Provider: Methods to allow fetching user email addresses
+* Google Provider: Updated scopes and endpoints to remove deprecated values
+* Documentation updates, minor bug fixes, and coding standards fixes
+
+
+## 0.6.0
+
+_Released: 2014-12-03_
+
+* Added ability to specify a redirect handler for providers through use of a callback (see [Provider\AbstractProvider::setRedirectHandler()](https://github.com/thephpleague/oauth2-client/blob/55de45401eaa21f53c0b2414091da6f3b0f3fcb7/src/Provider/AbstractProvider.php#L314-L317))
+* Updated authorize and token URLs for the Microsoft provider; the old URLs had been phased out and were no longer working (see #146)
+* Increased test coverage
+* Documentation updates, minor bug fixes, and coding standards fixes
+
+
+## 0.5.0
+
+_Released: 2014-11-28_
+
+* Added `ClientCredentials` and `Password` grants
+* Added support for providers to set their own `uid` parameter key name
+* Added support for Google's `hd` (hosted domain) parameter
+* Added support for providing a custom `state` parameter to the authorization URL
+* LinkedIn `pictureUrl` is now an optional response element
+* Added Battle.net provider package link to README
+* Added Meetup provider package link to README
+* Added `.gitattributes` file
+* Increased test coverage
+* A number of documentation fixes, minor bug fixes, and coding standards fixes
+
+
+## 0.4.0
+
+_Released: 2014-10-28_
+
+* Added `ProviderInterface` and removed `IdentityProvider`.
+* Expose generated state to allow for CSRF validation.
+* Renamed `League\OAuth2\Client\Provider\User` to `League\OAuth2\Client\Entity\User`.
+* Entity: User: added `gender` and `locale` properties
+* Updating logic for populating the token expiration time.
+
+
+## 0.3.0
+
+_Released: 2014-04-26_
+
+* This release made some huge leaps forward, including 100% unit-coverage and a bunch of new features.
+
+
+## 0.2.0
+
+_Released: 2013-05-28_
+
+* No release notes available.
+
+
+## 0.1.0
+
+_Released: 2013-05-25_
+
+* Initial release.
diff --git a/vendor/league/oauth2-client/CONTRIBUTING.md b/vendor/league/oauth2-client/CONTRIBUTING.md
new file mode 100644
index 0000000..ce73506
--- /dev/null
+++ b/vendor/league/oauth2-client/CONTRIBUTING.md
@@ -0,0 +1,42 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-client).
+
+
+## Pull Requests
+
+- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.
+
+- **Create topic branches** - Don't ask us to pull from your master branch.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
+
+- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.
+
+- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.
+
+
+## Running Tests
+
+``` bash
+$ ./vendor/bin/phpunit
+```
+
+
+## Running PHP Code Sniffer
+
+``` bash
+$ ./vendor/bin/phpcs src --standard=psr2 -sp
+```
+
+**Happy coding**!
diff --git a/vendor/league/oauth2-client/LICENSE b/vendor/league/oauth2-client/LICENSE
new file mode 100644
index 0000000..b71bd59
--- /dev/null
+++ b/vendor/league/oauth2-client/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Alex Bilbie <hello@alexbilbie.com>
+
+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.
diff --git a/vendor/league/oauth2-client/README.md b/vendor/league/oauth2-client/README.md
new file mode 100644
index 0000000..0b55d60
--- /dev/null
+++ b/vendor/league/oauth2-client/README.md
@@ -0,0 +1,247 @@
+# OAuth 2.0 Client
+
+[![Join the chat at https://gitter.im/thephpleague/oauth2-client](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/thephpleague/oauth2-client?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+[![Build Status](https://travis-ci.org/thephpleague/oauth2-client.svg?branch=master)](https://travis-ci.org/thephpleague/oauth2-client)
+[![Coverage Status](https://coveralls.io/repos/thephpleague/oauth2-client/badge.svg?branch=master)](https://coveralls.io/r/thephpleague/oauth2-client?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/league/oauth2-client/v/stable)](https://packagist.org/packages/league/oauth2-client)
+[![Total Downloads](https://poser.pugx.org/league/oauth2-client/downloads)](https://packagist.org/packages/league/oauth2-client)
+[![Latest Unstable Version](https://poser.pugx.org/league/oauth2-client/v/unstable)](https://packagist.org/packages/league/oauth2-client)
+[![License](https://poser.pugx.org/league/oauth2-client/license)](https://packagist.org/packages/league/oauth2-client)
+
+This package makes it stupidly simple to integrate your application with OAuth 2.0 identity providers.
+
+Everyone is used to seeing those "Connect with Facebook/Google/etc" buttons around the Internet and social network
+integration is an important feature of most web-apps these days. Many of these sites use an Authentication and Authorization standard called OAuth 2.0.
+
+It will work with any OAuth 2.0 provider (be it an OAuth 2.0 Server for your own API or Facebook) and provides support
+for popular systems out of the box. This package abstracts out some of the subtle but important differences between various providers, handles access tokens and refresh tokens, and allows you easy access to profile information on these other sites.
+
+This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send
+a patch via pull request.
+
+[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
+[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
+[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
+
+
+## Requirements
+
+The following versions of PHP are supported.
+
+* PHP 5.4
+* PHP 5.5
+* PHP 5.6
+* PHP 7.0
+* HHVM
+
+## Usage
+
+### Authorization Code Flow
+
+*Note: This example code requires the Google+ API to be enabled in your developer console*
+
+```php
+$provider = new League\OAuth2\Client\Provider\<ProviderName>([
+ 'clientId' => 'XXXXXXXX',
+ 'clientSecret' => 'XXXXXXXX',
+ 'redirectUri' => 'https://your-registered-redirect-uri/',
+ 'scopes' => ['email', '...', '...'],
+]);
+
+if (!isset($_GET['code'])) {
+
+ // If we don't have an authorization code then get one
+ $authUrl = $provider->getAuthorizationUrl();
+ $_SESSION['oauth2state'] = $provider->state;
+ header('Location: '.$authUrl);
+ exit;
+
+// Check given state against previously stored one to mitigate CSRF attack
+} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
+
+ unset($_SESSION['oauth2state']);
+ exit('Invalid state');
+
+} else {
+
+ // Try to get an access token (using the authorization code grant)
+ $token = $provider->getAccessToken('authorization_code', [
+ 'code' => $_GET['code']
+ ]);
+
+ // Optional: Now you have a token you can look up a users profile data
+ try {
+
+ // We got an access token, let's now get the user's details
+ $userDetails = $provider->getUserDetails($token);
+
+ // Use these details to create a new profile
+ printf('Hello %s!', $userDetails->firstName);
+
+ } catch (Exception $e) {
+
+ // Failed to get user details
+ exit('Oh dear...');
+ }
+
+ // Use this to interact with an API on the users behalf
+ echo $token->accessToken;
+
+ // Use this to get a new access token if the old one expires
+ echo $token->refreshToken;
+
+ // Unix timestamp of when the token will expire, and need refreshing
+ echo $token->expires;
+}
+```
+
+### Refreshing a Token
+
+Once and as long as your application is authorized, you then only need to refresh an expired access token. To do so, simply reuse this refresh token from your data store to request a refresh.
+
+```php
+$provider = new League\OAuth2\Client\Provider\<ProviderName>([
+ 'clientId' => 'XXXXXXXX',
+ 'clientSecret' => 'XXXXXXXX',
+ 'redirectUri' => 'https://your-registered-redirect-uri/',
+]);
+
+$grant = new \League\OAuth2\Client\Grant\RefreshToken();
+$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]);
+```
+
+
+### Built-In Providers
+
+This package currently has built-in support for:
+
+- Eventbrite
+- Facebook
+- Github
+- Google
+- Instagram
+- LinkedIn
+- Microsoft
+
+These are as many OAuth 2 services as we plan to support officially. Maintaining a wide selection of providers
+damages our ability to make this package the best it can be, especially as we progress towards v1.0.
+
+#### Managing LinkedIn Scopes
+
+The LinkedIn provider included in this package does not include scopes by default. When creating your LinkedIn provider, you can specify the scopes your application may authorize.
+
+```php
+$provider = new League\OAuth2\Client\Provider\LinkedIn([
+ 'clientId' => '{linkedin-client-id}',
+ 'clientSecret' => '{linkedin-client-secret}',
+ 'redirectUri' => 'https://example.com/callback-url',
+ 'scopes' => ['r_basicprofile','r_emailaddress'],
+]);
+```
+
+At the time of authoring this documentation, the following scopes are available.
+
+- r_basicprofile
+- r_emailaddress
+- rw_company_admin
+- w_share
+
+### Third-Party Providers
+
+If you would like to support other providers, please make them available as a Composer package, then link to them
+below.
+
+These providers allow integration with other providers not supported by `oauth2-client`. They may require an older version
+so please help them out with a pull request if you notice this.
+
+- [Amazon](https://github.com/lemonstand/oauth2-amazon/)
+- [Auth0](https://github.com/RiskioFr/oauth2-auth0)
+- [Battle.net](https://packagist.org/packages/depotwarehouse/oauth2-bnet)
+- [BookingSync](https://github.com/BookingSync/oauth2-bookingsync-php)
+- [Clover](https://github.com/wheniwork/oauth2-clover)
+- [Coinbase](https://github.com/openclerk/coinbase-oauth2)
+- [Dropbox](https://github.com/pixelfear/oauth2-dropbox)
+- [FreeAgent](https://github.com/CloudManaged/oauth2-freeagent)
+- [Google Nest](https://github.com/JC5/nest-oauth2-provider)
+- [Mail.ru](https://packagist.org/packages/aego/oauth2-mailru)
+- [Meetup](https://github.com/howlowck/meetup-oauth2-provider)
+- [Naver](https://packagist.org/packages/deminoth/oauth2-naver)
+- [Odnoklassniki](https://packagist.org/packages/aego/oauth2-odnoklassniki)
+- [Reddit](https://github.com/rtheunissen/oauth2-reddit)
+- [Square](https://packagist.org/packages/wheniwork/oauth2-square)
+- [Twitch.tv](https://github.com/tpavlek/oauth2-twitch)
+- [Uber](https://github.com/stevenmaguire/oauth2-uber)
+- [Vend](https://github.com/wheniwork/oauth2-vend)
+- [Vkontakte](https://packagist.org/packages/j4k/oauth2-vkontakte)
+- [Yandex](https://packagist.org/packages/aego/oauth2-yandex)
+- [ZenPayroll](https://packagist.org/packages/wheniwork/oauth2-zenpayroll)
+- [Envato](https://github.com/dilab/envato-oauth2-provider)
+
+### Implementing your own provider
+
+If you are working with an oauth2 service not supported out-of-the-box or by an existing package, it is quite simple to
+implement your own. Simply extend `League\OAuth2\Client\Provider\AbstractProvider` and implement the required abstract
+methods:
+
+```php
+abstract public function urlAuthorize();
+abstract public function urlAccessToken();
+abstract public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token);
+abstract public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token);
+```
+
+Each of these abstract methods contain a docblock defining their expectations and typical behaviour. Once you have
+extended this class, you can simply follow the example above using your new `Provider`.
+
+#### Custom account identifiers in access token responses
+
+Some OAuth2 Server implementations include a field in their access token response defining some identifier
+for the user account that just requested the access token. In many cases this field, if present, is called "uid", but
+some providers define custom identifiers in their response. If your provider uses a nonstandard name for the "uid" field,
+when extending the AbstractProvider, in your new class, define a property `public $uidKey` and set it equal to whatever
+your provider uses as its key. For example, Battle.net uses `accountId` as the key for the identifier field, so in that
+provider you would add a property:
+
+```php
+public $uidKey = 'accountId';
+```
+
+### Client Packages
+
+Some developers use this library as a base for their own PHP API wrappers, and that seems like a really great idea. It might make it slightly tricky to integrate their provider with an existing generic "OAuth 2.0 All the Things" login system, but it does make working with them easier.
+
+- [Sniply](https://github.com/younes0/sniply)
+
+## Install
+
+Via Composer
+
+``` bash
+$ composer require league/oauth2-client
+```
+
+## Testing
+
+``` bash
+$ ./vendor/bin/phpunit
+```
+
+## Contributing
+
+Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details.
+
+
+## Credits
+
+- [Alex Bilbie](https://github.com/alexbilbie)
+- [Ben Corlett](https://github.com/bencorlett)
+- [James Mills](https://github.com/jamesmills)
+- [Phil Sturgeon](https://github.com/philsturgeon)
+- [Tom Anderson](https://github.com/TomHAnderson)
+- [All Contributors](https://github.com/thephpleague/oauth2-client/contributors)
+
+
+## License
+
+The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information.
diff --git a/vendor/league/oauth2-client/composer.json b/vendor/league/oauth2-client/composer.json
new file mode 100644
index 0000000..5f50502
--- /dev/null
+++ b/vendor/league/oauth2-client/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "league/oauth2-client",
+ "description": "OAuth 2.0 Client Library",
+ "license": "MIT",
+ "require": {
+ "php": ">=5.4.0",
+ "guzzle/guzzle": "~3.7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0",
+ "mockery/mockery": "~0.9",
+ "squizlabs/php_codesniffer": "~2.0",
+ "satooshi/php-coveralls": "0.6.*",
+ "jakub-onderka/php-parallel-lint": "0.8.*"
+ },
+ "keywords": [
+ "oauth",
+ "oauth2",
+ "authorization",
+ "authentication",
+ "idp",
+ "identity",
+ "sso",
+ "single sign on"
+ ],
+ "authors": [
+ {
+ "name": "Alex Bilbie",
+ "email": "hello@alexbilbie.com",
+ "homepage": "http://www.alexbilbie.com",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "League\\OAuth2\\Client\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "League\\OAuth2\\Client\\Test\\": "test/src/"
+ }
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Entity/User.php b/vendor/league/oauth2-client/src/Entity/User.php
new file mode 100644
index 0000000..756e09a
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Entity/User.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace League\OAuth2\Client\Entity;
+
+class User
+{
+ protected $uid;
+ protected $nickname;
+ protected $name;
+ protected $firstName;
+ protected $lastName;
+ protected $email;
+ protected $location;
+ protected $description;
+ protected $imageUrl;
+ protected $urls;
+ protected $gender;
+ protected $locale;
+
+ public function __get($name)
+ {
+ if (!property_exists($this, $name)) {
+ throw new \OutOfRangeException(sprintf(
+ '%s does not contain a property by the name of "%s"',
+ __CLASS__,
+ $name
+ ));
+ }
+
+ return $this->{$name};
+ }
+
+ public function __set($property, $value)
+ {
+ if (!property_exists($this, $property)) {
+ throw new \OutOfRangeException(sprintf(
+ '%s does not contain a property by the name of "%s"',
+ __CLASS__,
+ $property
+ ));
+ }
+
+ $this->$property = $value;
+
+ return $this;
+ }
+
+ public function __isset($name)
+ {
+ return (property_exists($this, $name));
+ }
+
+ public function getArrayCopy()
+ {
+ return [
+ 'uid' => $this->uid,
+ 'nickname' => $this->nickname,
+ 'name' => $this->name,
+ 'firstName' => $this->firstName,
+ 'lastName' => $this->lastName,
+ 'email' => $this->email,
+ 'location' => $this->location,
+ 'description' => $this->description,
+ 'imageUrl' => $this->imageUrl,
+ 'urls' => $this->urls,
+ 'gender' => $this->gender,
+ 'locale' => $this->locale,
+ ];
+ }
+
+ public function exchangeArray(array $data)
+ {
+ foreach ($data as $key => $value) {
+ $key = strtolower($key);
+ switch ($key) {
+ case 'uid':
+ $this->uid = $value;
+ break;
+ case 'nickname':
+ $this->nickname = $value;
+ break;
+ case 'name':
+ $this->name = $value;
+ break;
+ case 'firstname':
+ $this->firstName = $value;
+ break;
+ case 'lastname':
+ $this->lastName = $value;
+ break;
+ case 'email':
+ $this->email = $value;
+ break;
+ case 'location':
+ $this->location = $value;
+ break;
+ case 'description':
+ $this->description = $value;
+ break;
+ case 'imageurl':
+ $this->imageUrl = $value;
+ break;
+ case 'urls':
+ $this->urls = $value;
+ break;
+ case 'gender':
+ $this->gender = $value;
+ break;
+ case 'locale':
+ $this->locale = $value;
+ break;
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Exception/IDPException.php b/vendor/league/oauth2-client/src/Exception/IDPException.php
new file mode 100644
index 0000000..668a7e7
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Exception/IDPException.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace League\OAuth2\Client\Exception;
+
+class IDPException extends \Exception
+{
+ protected $result;
+
+ public function __construct($result)
+ {
+ if (!empty($result['error']) && is_array($result['error'])) {
+ // Error response is wrapped in a top entity type, JSON:API style.
+ $result = $result['error'];
+ }
+
+ $this->result = $result;
+
+ $code = isset($result['code']) ? $result['code'] : 0;
+
+ if (isset($result['error']) && $result['error'] !== '') {
+ // OAuth 2.0 Draft 10 style
+ $message = $result['error'];
+ } elseif (isset($result['message']) && $result['message'] !== '') {
+ // cURL style
+ $message = $result['message'];
+ } else {
+ $message = 'Unknown Error.';
+ }
+
+ parent::__construct($message, $code);
+ }
+
+ public function getResponseBody()
+ {
+ return $this->result;
+ }
+
+ public function getType()
+ {
+ $result = 'Exception';
+
+ if (isset($this->result['error'])) {
+ $message = $this->result['error'];
+
+ if (is_string($message)) {
+ // OAuth 2.0 Draft 10 style
+ $result = $message;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * To make debugging easier.
+ *
+ * @return string The string representation of the error.
+ */
+ public function __toString()
+ {
+ $str = $this->getType().': ';
+
+ if ($this->code != 0) {
+ $str .= $this->code.': ';
+ }
+
+ return $str.$this->message;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php
new file mode 100644
index 0000000..bda0965
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace League\OAuth2\Client\Grant;
+
+use League\OAuth2\Client\Token\AccessToken;
+
+class AuthorizationCode implements GrantInterface
+{
+ public function __toString()
+ {
+ return 'authorization_code';
+ }
+
+ public function prepRequestParams($defaultParams, $params)
+ {
+ if (! isset($params['code']) || empty($params['code'])) {
+ throw new \BadMethodCallException('Missing authorization code');
+ }
+
+ return array_merge($defaultParams, $params);
+ }
+
+ public function handleResponse($response = [])
+ {
+ return new AccessToken($response);
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Grant/ClientCredentials.php b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php
new file mode 100644
index 0000000..7a73a79
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace League\OAuth2\Client\Grant;
+
+use League\OAuth2\Client\Token\AccessToken;
+
+class ClientCredentials implements GrantInterface
+{
+ public function __toString()
+ {
+ return 'client_credentials';
+ }
+
+ public function prepRequestParams($defaultParams, $params)
+ {
+ $params['grant_type'] = 'client_credentials';
+
+ return array_merge($defaultParams, $params);
+ }
+
+ public function handleResponse($response = array())
+ {
+ return new AccessToken($response);
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Grant/GrantInterface.php b/vendor/league/oauth2-client/src/Grant/GrantInterface.php
new file mode 100644
index 0000000..a744ea9
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Grant/GrantInterface.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace League\OAuth2\Client\Grant;
+
+interface GrantInterface
+{
+ public function __toString();
+
+ public function handleResponse($response = []);
+
+ public function prepRequestParams($defaultParams, $params);
+}
diff --git a/vendor/league/oauth2-client/src/Grant/Password.php b/vendor/league/oauth2-client/src/Grant/Password.php
new file mode 100644
index 0000000..b124b34
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Grant/Password.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace League\OAuth2\Client\Grant;
+
+use League\OAuth2\Client\Token\AccessToken;
+
+class Password implements GrantInterface
+{
+ public function __toString()
+ {
+ return 'password';
+ }
+
+ public function prepRequestParams($defaultParams, $params)
+ {
+ if (! isset($params['username']) || empty($params['username'])) {
+ throw new \BadMethodCallException('Missing username');
+ }
+
+ if (! isset($params['password']) || empty($params['password'])) {
+ throw new \BadMethodCallException('Missing password');
+ }
+
+ $params['grant_type'] = 'password';
+
+ return array_merge($defaultParams, $params);
+ }
+
+ public function handleResponse($response = array())
+ {
+ return new AccessToken($response);
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Grant/RefreshToken.php b/vendor/league/oauth2-client/src/Grant/RefreshToken.php
new file mode 100644
index 0000000..86e7f6e
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Grant/RefreshToken.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace League\OAuth2\Client\Grant;
+
+use League\OAuth2\Client\Token\AccessToken as AccessToken;
+
+class RefreshToken implements GrantInterface
+{
+ public function __toString()
+ {
+ return 'refresh_token';
+ }
+
+ public function prepRequestParams($defaultParams, $params)
+ {
+ if (! isset($params['refresh_token']) || empty($params['refresh_token'])) {
+ throw new \BadMethodCallException('Missing refresh_token');
+ }
+
+ $params['grant_type'] = 'refresh_token';
+
+ return array_merge($defaultParams, $params);
+ }
+
+ public function handleResponse($response = [])
+ {
+ return new AccessToken($response);
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/AbstractProvider.php b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php
new file mode 100644
index 0000000..a200013
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php
@@ -0,0 +1,399 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use Closure;
+use Guzzle\Http\Exception\BadResponseException;
+use Guzzle\Service\Client as GuzzleClient;
+use League\OAuth2\Client\Exception\IDPException as IDPException;
+use League\OAuth2\Client\Grant\GrantInterface;
+use League\OAuth2\Client\Token\AccessToken as AccessToken;
+
+abstract class AbstractProvider implements ProviderInterface
+{
+ public $clientId = '';
+
+ public $clientSecret = '';
+
+ public $redirectUri = '';
+
+ public $state;
+
+ public $name;
+
+ public $uidKey = 'uid';
+
+ public $scopes = [];
+
+ public $method = 'post';
+
+ public $scopeSeparator = ',';
+
+ public $responseType = 'json';
+
+ public $headers = [];
+
+ public $authorizationHeader;
+
+ /**
+ * @var GuzzleClient
+ */
+ protected $httpClient;
+
+ protected $redirectHandler;
+
+ /**
+ * @var int This represents: PHP_QUERY_RFC1738, which is the default value for php 5.4
+ * and the default encoding type for the http_build_query setup
+ */
+ protected $httpBuildEncType = 1;
+
+ public function __construct($options = [])
+ {
+ foreach ($options as $option => $value) {
+ if (property_exists($this, $option)) {
+ $this->{$option} = $value;
+ }
+ }
+
+ $this->setHttpClient(new GuzzleClient());
+ }
+
+ public function setHttpClient(GuzzleClient $client)
+ {
+ $this->httpClient = $client;
+
+ return $this;
+ }
+
+ public function getHttpClient()
+ {
+ $client = clone $this->httpClient;
+
+ return $client;
+ }
+
+ /**
+ * Get the URL that this provider uses to begin authorization.
+ *
+ * @return string
+ */
+ abstract public function urlAuthorize();
+
+ /**
+ * Get the URL that this provider uses to request an access token.
+ *
+ * @return string
+ */
+ abstract public function urlAccessToken();
+
+ /**
+ * Get the URL that this provider uses to request user details.
+ *
+ * Since this URL is typically an authorized route, most providers will require you to pass the access_token as
+ * a parameter to the request. For example, the google url is:
+ *
+ * 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token='.$token
+ *
+ * @param AccessToken $token
+ * @return string
+ */
+ abstract public function urlUserDetails(AccessToken $token);
+
+ /**
+ * Given an object response from the server, process the user details into a format expected by the user
+ * of the client.
+ *
+ * @param object $response
+ * @param AccessToken $token
+ * @return mixed
+ */
+ abstract public function userDetails($response, AccessToken $token);
+
+ public function getScopes()
+ {
+ return $this->scopes;
+ }
+
+ public function setScopes(array $scopes)
+ {
+ $this->scopes = $scopes;
+ }
+
+ public function getAuthorizationUrl($options = [])
+ {
+ $this->state = isset($options['state']) ? $options['state'] : md5(uniqid(rand(), true));
+
+ $params = [
+ 'client_id' => $this->clientId,
+ 'redirect_uri' => $this->redirectUri,
+ 'state' => $this->state,
+ 'scope' => is_array($this->scopes) ? implode($this->scopeSeparator, $this->scopes) : $this->scopes,
+ 'response_type' => isset($options['response_type']) ? $options['response_type'] : 'code',
+ 'approval_prompt' => isset($options['approval_prompt']) ? $options['approval_prompt'] : 'auto',
+ ];
+
+ return $this->urlAuthorize().'?'.$this->httpBuildQuery($params, '', '&');
+ }
+
+ // @codeCoverageIgnoreStart
+ public function authorize($options = [])
+ {
+ $url = $this->getAuthorizationUrl($options);
+ if ($this->redirectHandler) {
+ $handler = $this->redirectHandler;
+ return $handler($url);
+ }
+ // @codeCoverageIgnoreStart
+ header('Location: ' . $url);
+ exit;
+ // @codeCoverageIgnoreEnd
+ }
+
+ public function getAccessToken($grant = 'authorization_code', $params = [])
+ {
+ if (is_string($grant)) {
+ // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode'
+ $className = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $grant)));
+ $grant = 'League\\OAuth2\\Client\\Grant\\'.$className;
+ if (! class_exists($grant)) {
+ throw new \InvalidArgumentException('Unknown grant "'.$grant.'"');
+ }
+ $grant = new $grant();
+ } elseif (! $grant instanceof GrantInterface) {
+ $message = get_class($grant).' is not an instance of League\OAuth2\Client\Grant\GrantInterface';
+ throw new \InvalidArgumentException($message);
+ }
+
+ $defaultParams = [
+ 'client_id' => $this->clientId,
+ 'client_secret' => $this->clientSecret,
+ 'redirect_uri' => $this->redirectUri,
+ 'grant_type' => $grant,
+ ];
+
+ $requestParams = $grant->prepRequestParams($defaultParams, $params);
+
+ try {
+ switch (strtoupper($this->method)) {
+ case 'GET':
+ // @codeCoverageIgnoreStart
+ // No providers included with this library use get but 3rd parties may
+ $client = $this->getHttpClient();
+ $client->setBaseUrl($this->urlAccessToken() . '?' . $this->httpBuildQuery($requestParams, '', '&'));
+ $request = $client->get(null, $this->getHeaders(), $requestParams)->send();
+ $response = $request->getBody();
+ break;
+ // @codeCoverageIgnoreEnd
+ case 'POST':
+ $client = $this->getHttpClient();
+ $client->setBaseUrl($this->urlAccessToken());
+ $request = $client->post(null, $this->getHeaders(), $requestParams)->send();
+ $response = $request->getBody();
+ break;
+ // @codeCoverageIgnoreStart
+ default:
+ throw new \InvalidArgumentException('Neither GET nor POST is specified for request');
+ // @codeCoverageIgnoreEnd
+ }
+ } catch (BadResponseException $e) {
+ // @codeCoverageIgnoreStart
+ $response = $e->getResponse()->getBody();
+ // @codeCoverageIgnoreEnd
+ }
+
+ $result = $this->prepareResponse($response);
+
+ if (isset($result['error']) && ! empty($result['error'])) {
+ // @codeCoverageIgnoreStart
+ throw new IDPException($result);
+ // @codeCoverageIgnoreEnd
+ }
+
+ $result = $this->prepareAccessTokenResult($result);
+
+ return $grant->handleResponse($result);
+ }
+
+ /**
+ * Prepare the response, parsing according to configuration and returning
+ * the response as an array.
+ *
+ * @param string $response
+ * @return array
+ */
+ protected function prepareResponse($response)
+ {
+ $result = [];
+
+ switch ($this->responseType) {
+ case 'json':
+ $json = json_decode($response, true);
+
+ if (JSON_ERROR_NONE === json_last_error()) {
+ $result = $json;
+ }
+
+ break;
+ case 'string':
+ parse_str($response, $result);
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Prepare the access token response for the grant. Custom mapping of
+ * expirations, etc should be done here.
+ *
+ * @param array $result
+ * @return array
+ */
+ protected function prepareAccessTokenResult(array $result)
+ {
+ $this->setResultUid($result);
+ return $result;
+ }
+
+ /**
+ * Sets any result keys we've received matching our provider-defined uidKey to the key "uid".
+ *
+ * @param array $result
+ */
+ protected function setResultUid(array &$result)
+ {
+ // If we're operating with the default uidKey there's nothing to do.
+ if ($this->uidKey === "uid") {
+ return;
+ }
+
+ if (isset($result[$this->uidKey])) {
+ // The AccessToken expects a "uid" to have the key "uid".
+ $result['uid'] = $result[$this->uidKey];
+ }
+ }
+
+ public function getUserDetails(AccessToken $token)
+ {
+ $response = $this->fetchUserDetails($token);
+
+ return $this->userDetails(json_decode($response), $token);
+ }
+
+ public function getUserUid(AccessToken $token)
+ {
+ $response = $this->fetchUserDetails($token, true);
+
+ return $this->userUid(json_decode($response), $token);
+ }
+
+ public function getUserEmail(AccessToken $token)
+ {
+ $response = $this->fetchUserDetails($token, true);
+
+ return $this->userEmail(json_decode($response), $token);
+ }
+
+ public function getUserScreenName(AccessToken $token)
+ {
+ $response = $this->fetchUserDetails($token, true);
+
+ return $this->userScreenName(json_decode($response), $token);
+ }
+
+ public function userUid($response, AccessToken $token)
+ {
+ return isset($response->id) && $response->id ? $response->id : null;
+ }
+
+ public function userEmail($response, AccessToken $token)
+ {
+ return isset($response->email) && $response->email ? $response->email : null;
+ }
+
+ public function userScreenName($response, AccessToken $token)
+ {
+ return isset($response->name) && $response->name ? $response->name : null;
+ }
+
+ /**
+ * Build HTTP the HTTP query, handling PHP version control options
+ *
+ * @param array $params
+ * @param integer $numeric_prefix
+ * @param string $arg_separator
+ * @param null|integer $enc_type
+ *
+ * @return string
+ * @codeCoverageIgnoreStart
+ */
+ protected function httpBuildQuery($params, $numeric_prefix = 0, $arg_separator = '&', $enc_type = null)
+ {
+ if (version_compare(PHP_VERSION, '5.4.0', '>=') && !defined('HHVM_VERSION')) {
+ if ($enc_type === null) {
+ $enc_type = $this->httpBuildEncType;
+ }
+ $url = http_build_query($params, $numeric_prefix, $arg_separator, $enc_type);
+ } else {
+ $url = http_build_query($params, $numeric_prefix, $arg_separator);
+ }
+
+ return $url;
+ }
+
+ protected function fetchUserDetails(AccessToken $token)
+ {
+ $url = $this->urlUserDetails($token);
+
+ $headers = $this->getHeaders($token);
+
+ return $this->fetchProviderData($url, $headers);
+ }
+
+ protected function fetchProviderData($url, array $headers = [])
+ {
+ try {
+ $client = $this->getHttpClient();
+ $client->setBaseUrl($url);
+
+ if ($headers) {
+ $client->setDefaultOption('headers', $headers);
+ }
+
+ $request = $client->get()->send();
+ $response = $request->getBody();
+ } catch (BadResponseException $e) {
+ // @codeCoverageIgnoreStart
+ $response = $e->getResponse()->getBody();
+ $result = $this->prepareResponse($response);
+ throw new IDPException($result);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $response;
+ }
+
+ protected function getAuthorizationHeaders($token)
+ {
+ $headers = [];
+ if ($this->authorizationHeader) {
+ $headers['Authorization'] = $this->authorizationHeader . ' ' . $token;
+ }
+ return $headers;
+ }
+
+ public function getHeaders($token = null)
+ {
+ $headers = $this->headers;
+ if ($token) {
+ $headers = array_merge($headers, $this->getAuthorizationHeaders($token));
+ }
+ return $headers;
+ }
+
+ public function setRedirectHandler(Closure $handler)
+ {
+ $this->redirectHandler = $handler;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Eventbrite.php b/vendor/league/oauth2-client/src/Provider/Eventbrite.php
new file mode 100644
index 0000000..116051f
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Eventbrite.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+
+class Eventbrite extends AbstractProvider
+{
+ public $authorizationHeader = 'Bearer';
+
+ public function urlAuthorize()
+ {
+ return 'https://www.eventbrite.com/oauth/authorize';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://www.eventbrite.com/oauth/token';
+ }
+
+ public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return 'https://www.eventbrite.com/json/user_get';
+ }
+
+ public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ $user = new User();
+ $user->exchangeArray([
+ 'uid' => $response->user->user_id,
+ 'email' => $response->user->email,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->user->user_id;
+ }
+
+ public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return isset($response->user->email) && $response->user->email ? $response->user->email : null;
+ }
+
+ public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->user->user_id;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Facebook.php b/vendor/league/oauth2-client/src/Provider/Facebook.php
new file mode 100644
index 0000000..9155836
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Facebook.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+
+class Facebook extends AbstractProvider
+{
+ /**
+ * @const string The fallback Graph API version to use for requests.
+ */
+ const DEFAULT_GRAPH_VERSION = 'v2.2';
+
+ /**
+ * @var string The Graph API version to use for requests.
+ */
+ protected $graphApiVersion;
+
+ public $scopes = ['public_profile', 'email'];
+
+ public $responseType = 'string';
+
+ public function __construct($options)
+ {
+ parent::__construct($options);
+ $this->graphApiVersion = (isset($options['graphApiVersion']))
+ ? $options['graphApiVersion']
+ : static::DEFAULT_GRAPH_VERSION;
+ }
+
+ public function urlAuthorize()
+ {
+ return 'https://www.facebook.com/'.$this->graphApiVersion.'/dialog/oauth';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://graph.facebook.com/'.$this->graphApiVersion.'/oauth/access_token';
+ }
+
+ public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
+ {
+ $fields = implode(',', [
+ 'id',
+ 'name',
+ 'first_name',
+ 'last_name',
+ 'email',
+ 'hometown',
+ 'bio',
+ 'picture.type(large){url}',
+ 'gender',
+ 'locale',
+ 'link',
+ ]);
+
+ return 'https://graph.facebook.com/'.$this->graphApiVersion.'/me?fields='.$fields.'&access_token='.$token;
+ }
+
+ public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ $user = new User();
+
+ $email = (isset($response->email)) ? $response->email : null;
+ // The "hometown" field will only be returned if you ask for the `user_hometown` permission.
+ $location = (isset($response->hometown->name)) ? $response->hometown->name : null;
+ $description = (isset($response->bio)) ? $response->bio : null;
+ $imageUrl = (isset($response->picture->data->url)) ? $response->picture->data->url : null;
+ $gender = (isset($response->gender)) ? $response->gender : null;
+ $locale = (isset($response->locale)) ? $response->locale : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->id,
+ 'name' => $response->name,
+ 'firstname' => $response->first_name,
+ 'lastname' => $response->last_name,
+ 'email' => $email,
+ 'location' => $location,
+ 'description' => $description,
+ 'imageurl' => $imageUrl,
+ 'gender' => $gender,
+ 'locale' => $locale,
+ 'urls' => [ 'Facebook' => $response->link ],
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->id;
+ }
+
+ public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return isset($response->email) && $response->email ? $response->email : null;
+ }
+
+ public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return [$response->first_name, $response->last_name];
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Github.php b/vendor/league/oauth2-client/src/Provider/Github.php
new file mode 100644
index 0000000..8182a4e
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Github.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+use League\OAuth2\Client\Token\AccessToken;
+
+class Github extends AbstractProvider
+{
+ public $responseType = 'string';
+
+ public $authorizationHeader = 'token';
+
+ public $domain = 'https://github.com';
+
+ public $apiDomain = 'https://api.github.com';
+
+ public function urlAuthorize()
+ {
+ return $this->domain.'/login/oauth/authorize';
+ }
+
+ public function urlAccessToken()
+ {
+ return $this->domain.'/login/oauth/access_token';
+ }
+
+ public function urlUserDetails(AccessToken $token)
+ {
+ if ($this->domain === 'https://github.com') {
+ return $this->apiDomain.'/user';
+ }
+ return $this->domain.'/api/v3/user';
+ }
+
+ public function urlUserEmails(AccessToken $token)
+ {
+ if ($this->domain === 'https://github.com') {
+ return $this->apiDomain.'/user/emails';
+ }
+ return $this->domain.'/api/v3/user/emails';
+ }
+
+ public function userDetails($response, AccessToken $token)
+ {
+ $user = new User();
+
+ $name = (isset($response->name)) ? $response->name : null;
+ $email = (isset($response->email)) ? $response->email : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->id,
+ 'nickname' => $response->login,
+ 'name' => $name,
+ 'email' => $email,
+ 'urls' => [
+ 'GitHub' => $this->domain.'/'.$response->login,
+ ],
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, AccessToken $token)
+ {
+ return $response->id;
+ }
+
+ public function getUserEmails(AccessToken $token)
+ {
+ $response = $this->fetchUserEmails($token);
+
+ return $this->userEmails(json_decode($response), $token);
+ }
+
+ public function userEmail($response, AccessToken $token)
+ {
+ return isset($response->email) && $response->email ? $response->email : null;
+ }
+
+ public function userEmails($response, AccessToken $token)
+ {
+ return $response;
+ }
+
+ public function userScreenName($response, AccessToken $token)
+ {
+ return $response->name;
+ }
+
+ protected function fetchUserEmails(AccessToken $token)
+ {
+ $url = $this->urlUserEmails($token);
+
+ $headers = $this->getHeaders($token);
+
+ return $this->fetchProviderData($url, $headers);
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Google.php b/vendor/league/oauth2-client/src/Provider/Google.php
new file mode 100644
index 0000000..87393ee
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Google.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+
+class Google extends AbstractProvider
+{
+ public $scopeSeparator = ' ';
+
+ public $scopes = [
+ 'profile',
+ 'email',
+ ];
+
+ public $authorizationHeader = 'OAuth';
+
+ /**
+ * @var string If set, this will be sent to google as the "hd" parameter.
+ * @link https://developers.google.com/accounts/docs/OAuth2Login#hd-param
+ */
+ public $hostedDomain = '';
+
+ public function setHostedDomain($hd)
+ {
+ $this->hostedDomain = $hd;
+ }
+
+ public function getHostedDomain()
+ {
+ return $this->hostedDomain;
+ }
+
+ /**
+ * @var string If set, this will be sent to google as the "access_type" parameter.
+ * @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
+ */
+ public $accessType = '';
+
+ public function setAccessType($accessType)
+ {
+ $this->accessType = $accessType;
+ }
+
+ public function getAccessType()
+ {
+ return $this->accessType;
+ }
+
+ public function urlAuthorize()
+ {
+ return 'https://accounts.google.com/o/oauth2/auth';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://accounts.google.com/o/oauth2/token';
+ }
+
+ public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return
+ 'https://www.googleapis.com/plus/v1/people/me?'.
+ 'fields=id%2Cname(familyName%2CgivenName)%2CdisplayName%2C'.
+ 'emails%2Fvalue%2Cimage%2Furl&alt=json';
+ }
+
+ public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ $response = (array) $response;
+
+ $user = new User();
+
+ $imageUrl = (isset($response['image']) &&
+ $response['image']->url) ? $response['image']->url : null;
+ $email =
+ (isset($response['emails']) &&
+ count($response['emails']) &&
+ $response['emails'][0]->value)? $response['emails'][0]->value : null;
+
+ $user->exchangeArray([
+ 'uid' => $response['id'],
+ 'name' => $response['displayName'],
+ 'firstname' => $response['name']->givenName,
+ 'lastName' => $response['name']->familyName,
+ 'email' => $email,
+ 'imageUrl' => $imageUrl,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->id;
+ }
+
+ public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return ($response->emails &&
+ count($response->emails) &&
+ $response->emails[0]->value) ? $response->emails[0]->value : null;
+ }
+
+ public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return [$response->name->givenName, $response->name->familyName];
+ }
+
+ public function getAuthorizationUrl($options = array())
+ {
+ $url = parent::getAuthorizationUrl($options);
+
+ if (!empty($this->hostedDomain)) {
+ $url .= '&' . $this->httpBuildQuery(['hd' => $this->hostedDomain]);
+ }
+
+ if (!empty($this->accessType)) {
+ $url .= '&' . $this->httpBuildQuery(['access_type'=> $this->accessType]);
+ }
+
+ return $url;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Instagram.php b/vendor/league/oauth2-client/src/Provider/Instagram.php
new file mode 100644
index 0000000..0377c8a
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Instagram.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+
+class Instagram extends AbstractProvider
+{
+ public $scopeSeparator = ' ';
+ public $scopes = ['basic'];
+ public $responseType = 'json';
+
+ public function urlAuthorize()
+ {
+ return 'https://api.instagram.com/oauth/authorize';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://api.instagram.com/oauth/access_token';
+ }
+
+ public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return 'https://api.instagram.com/v1/users/self?access_token='.$token;
+ }
+
+ public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ $user = new User();
+
+ $description = (isset($response->data->bio)) ? $response->data->bio : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->data->id,
+ 'nickname' => $response->data->username,
+ 'name' => $response->data->full_name,
+ 'description' => $description,
+ 'imageUrl' => $response->data->profile_picture,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->data->id;
+ }
+
+ public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return;
+ }
+
+ public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
+ {
+ return $response->data->full_name;
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/LinkedIn.php b/vendor/league/oauth2-client/src/Provider/LinkedIn.php
new file mode 100644
index 0000000..c790ddf
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/LinkedIn.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+use League\OAuth2\Client\Token\AccessToken;
+
+class LinkedIn extends AbstractProvider
+{
+ public $scopes = [];
+ public $scopeSeparator = ' ';
+ public $responseType = 'json';
+ public $authorizationHeader = 'Bearer';
+ public $fields = [
+ 'id', 'email-address', 'first-name', 'last-name', 'headline',
+ 'location', 'industry', 'picture-url', 'public-profile-url',
+ ];
+
+ public function urlAuthorize()
+ {
+ return 'https://www.linkedin.com/uas/oauth2/authorization';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://www.linkedin.com/uas/oauth2/accessToken';
+ }
+
+ public function urlUserDetails(AccessToken $token)
+ {
+ $fields = implode(',', $this->fields);
+ return 'https://api.linkedin.com/v1/people/~:(' . $fields . ')?format=json';
+ }
+
+ public function userDetails($response, AccessToken $token)
+ {
+ $user = new User();
+
+ $email = (isset($response->emailAddress)) ? $response->emailAddress : null;
+ $location = (isset($response->location->name)) ? $response->location->name : null;
+ $description = (isset($response->headline)) ? $response->headline : null;
+ $pictureUrl = (isset($response->pictureUrl)) ? $response->pictureUrl : null;
+ $publicProfileUrl = (isset($response->publicProfileUrl)) ? $response->publicProfileUrl : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->id,
+ 'name' => $response->firstName.' '.$response->lastName,
+ 'firstname' => $response->firstName,
+ 'lastname' => $response->lastName,
+ 'email' => $email,
+ 'location' => $location,
+ 'description' => $description,
+ 'imageurl' => $pictureUrl,
+ 'urls' => $publicProfileUrl,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, AccessToken $token)
+ {
+ return $response->id;
+ }
+
+ public function userEmail($response, AccessToken $token)
+ {
+ return isset($response->emailAddress) && $response->emailAddress
+ ? $response->emailAddress
+ : null;
+ }
+
+ public function userScreenName($response, AccessToken $token)
+ {
+ return [$response->firstName, $response->lastName];
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Microsoft.php b/vendor/league/oauth2-client/src/Provider/Microsoft.php
new file mode 100644
index 0000000..d0ed12e
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Microsoft.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+use League\OAuth2\Client\Token\AccessToken;
+
+class Microsoft extends AbstractProvider
+{
+ public $scopes = ['wl.basic', 'wl.emails'];
+ public $responseType = 'json';
+
+ public function urlAuthorize()
+ {
+ return 'https://login.live.com/oauth20_authorize.srf';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://login.live.com/oauth20_token.srf';
+ }
+
+ public function urlUserDetails(AccessToken $token)
+ {
+ return 'https://apis.live.net/v5.0/me?access_token='.$token;
+ }
+
+ public function userDetails($response, AccessToken $token)
+ {
+ $client = $this->getHttpClient();
+ $client->setBaseUrl('https://apis.live.net/v5.0/'.$response->id.'/picture');
+ $request = $client->get()->send();
+ $info = $request->getInfo();
+ $imageUrl = $info['url'];
+
+ $user = new User();
+
+ $email = (isset($response->emails->preferred)) ? $response->emails->preferred : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->id,
+ 'name' => $response->name,
+ 'firstname' => $response->first_name,
+ 'lastname' => $response->last_name,
+ 'email' => $email,
+ 'imageurl' => $imageUrl,
+ 'urls' => $response->link.'/cid-'.$response->id,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, AccessToken $token)
+ {
+ return $response->id;
+ }
+
+ public function userEmail($response, AccessToken $token)
+ {
+ return isset($response->emails->preferred) && $response->emails->preferred
+ ? $response->emails->preferred
+ : null;
+ }
+
+ public function userScreenName($response, AccessToken $token)
+ {
+ return [$response->first_name, $response->last_name];
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Provider/ProviderInterface.php b/vendor/league/oauth2-client/src/Provider/ProviderInterface.php
new file mode 100644
index 0000000..efe6087
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/ProviderInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Token\AccessToken as AccessToken;
+
+interface ProviderInterface
+{
+ public function urlAuthorize();
+
+ public function urlAccessToken();
+
+ public function urlUserDetails(AccessToken $token);
+
+ public function userDetails($response, AccessToken $token);
+
+ public function getScopes();
+
+ public function setScopes(array $scopes);
+
+ public function getAuthorizationUrl($options = []);
+
+ public function authorize($options = []);
+
+ public function getAccessToken($grant = 'authorization_code', $params = []);
+
+ public function getHeaders($token = null);
+
+ public function getUserDetails(AccessToken $token);
+
+ public function getUserUid(AccessToken $token);
+
+ public function getUserEmail(AccessToken $token);
+
+ public function getUserScreenName(AccessToken $token);
+}
diff --git a/vendor/league/oauth2-client/src/Provider/Vkontakte.php b/vendor/league/oauth2-client/src/Provider/Vkontakte.php
new file mode 100644
index 0000000..da44ae2
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Provider/Vkontakte.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace League\OAuth2\Client\Provider;
+
+use League\OAuth2\Client\Entity\User;
+use League\OAuth2\Client\Token\AccessToken;
+
+class Vkontakte extends AbstractProvider
+{
+ public $uidKey = 'user_id';
+
+ public function urlAuthorize()
+ {
+ return 'https://oauth.vk.com/authorize';
+ }
+
+ public function urlAccessToken()
+ {
+ return 'https://oauth.vk.com/access_token';
+ }
+
+ public function urlUserDetails(AccessToken $token)
+ {
+ $fields = ['nickname',
+ 'screen_name',
+ 'sex',
+ 'bdate',
+ 'city',
+ 'country',
+ 'timezone',
+ 'photo_50',
+ 'photo_100',
+ 'photo_200_orig',
+ 'has_mobile',
+ 'contacts',
+ 'education',
+ 'online',
+ 'counters',
+ 'relation',
+ 'last_seen',
+ 'status',
+ 'can_write_private_message',
+ 'can_see_all_posts',
+ 'can_see_audio',
+ 'can_post',
+ 'universities',
+ 'schools',
+ 'verified', ];
+
+ return "https://api.vk.com/method/users.get?user_id={$token->uid}&fields="
+ .implode(",", $fields)."&access_token={$token}";
+ }
+
+ public function userDetails($response, AccessToken $token)
+ {
+ $response = $response->response[0];
+
+ $user = new User();
+
+ $email = (isset($response->email)) ? $response->email : null;
+ $location = (isset($response->country)) ? $response->country : null;
+ $description = (isset($response->status)) ? $response->status : null;
+
+ $user->exchangeArray([
+ 'uid' => $response->uid,
+ 'nickname' => $response->nickname,
+ 'name' => $response->screen_name,
+ 'firstname' => $response->first_name,
+ 'lastname' => $response->last_name,
+ 'email' => $email,
+ 'location' => $location,
+ 'description' => $description,
+ 'imageUrl' => $response->photo_200_orig,
+ ]);
+
+ return $user;
+ }
+
+ public function userUid($response, AccessToken $token)
+ {
+ $response = $response->response[0];
+
+ return $response->uid;
+ }
+
+ public function userEmail($response, AccessToken $token)
+ {
+ $response = $response->response[0];
+
+ return isset($response->email) && $response->email ? $response->email : null;
+ }
+
+ public function userScreenName($response, AccessToken $token)
+ {
+ $response = $response->response[0];
+
+ return [$response->first_name, $response->last_name];
+ }
+}
diff --git a/vendor/league/oauth2-client/src/Token/AccessToken.php b/vendor/league/oauth2-client/src/Token/AccessToken.php
new file mode 100755
index 0000000..bcfbfb1
--- /dev/null
+++ b/vendor/league/oauth2-client/src/Token/AccessToken.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace League\OAuth2\Client\Token;
+
+use InvalidArgumentException;
+
+class AccessToken
+{
+ /**
+ * @var string accessToken
+ */
+ public $accessToken;
+
+ /**
+ * @var int expires
+ */
+ public $expires;
+
+ /**
+ * @var string refreshToken
+ */
+ public $refreshToken;
+
+ /**
+ * @var string uid
+ */
+ public $uid;
+
+ /**
+ * Sets the token, expiry, etc values.
+ *
+ * @param array $options token options
+ * @return void
+ */
+ public function __construct(array $options = null)
+ {
+ if (! isset($options['access_token'])) {
+ throw new \InvalidArgumentException(
+ 'Required option not passed: access_token'.PHP_EOL
+ .print_r($options, true)
+ );
+ }
+
+ $this->accessToken = $options['access_token'];
+
+ if (!empty($options['uid'])) {
+ $this->uid = $options['uid'];
+ }
+
+ if (!empty($options['refresh_token'])) {
+ $this->refreshToken = $options['refresh_token'];
+ }
+
+ // We need to know when the token expires. Show preference to
+ // 'expires_in' since it is defined in RFC6749 Section 5.1.
+ // Defer to 'expires' if it is provided instead.
+ if (!empty($options['expires_in'])) {
+ $this->expires = time() + ((int) $options['expires_in']);
+ } elseif (!empty($options['expires'])) {
+ // Some providers supply the seconds until expiration rather than
+ // the exact timestamp. Take a best guess at which we received.
+ $expires = $options['expires'];
+ $expiresInFuture = $expires > time();
+ $this->expires = $expiresInFuture ? $expires : time() + ((int) $expires);
+ }
+ }
+
+ /**
+ * Returns the token key.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->accessToken;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/.gitattributes b/vendor/swiftmailer/swiftmailer/.gitattributes
new file mode 100644
index 0000000..33b8efd
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.gitattributes
@@ -0,0 +1,9 @@
+*.crt -crlf
+*.key -crlf
+*.srl -crlf
+*.pub -crlf
+*.priv -crlf
+*.txt -crlf
+
+# ignore /notes in the git-generated distributed .zip archive
+/notes export-ignore
diff --git a/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md b/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..5db6524
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,19 @@
+<!-- Please fill in this template according to your issue. -->
+
+| Q | A
+| ------------------- | -----
+| Bug report? | yes/no
+| Feature request? | yes/no
+| RFC? | yes/no
+| How used? | Standalone/Symfony/3party
+| Swiftmailer version | x.y.z
+| PHP version | x.y.z
+
+### Observed behaviour
+<!-- What does the code do? -->
+
+### Expected behaviour
+<!-- What should the code do? -->
+
+### Example
+<!-- Example to reproduce the issue. -->
diff --git a/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md b/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..4b39510
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,14 @@
+<!-- Please fill in this template according to the PR you're about to submit. -->
+
+| Q | A
+| ------------- | ---
+| Bug fix? | yes/no
+| New feature? | yes/no
+| Doc update? | yes/no
+| BC breaks? | yes/no
+| Deprecations? | yes/no
+| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
+| License | MIT
+
+
+<!-- Replace this comment by the description of your issue. -->
diff --git a/vendor/swiftmailer/swiftmailer/.gitignore b/vendor/swiftmailer/swiftmailer/.gitignore
new file mode 100644
index 0000000..20d389a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.gitignore
@@ -0,0 +1,8 @@
+/.php_cs.cache
+/.phpunit
+/build/*
+/composer.lock
+/phpunit.xml
+/tests/acceptance.conf.php
+/tests/smoke.conf.php
+/vendor/
diff --git a/vendor/swiftmailer/swiftmailer/.php_cs.dist b/vendor/swiftmailer/swiftmailer/.php_cs.dist
new file mode 100644
index 0000000..f18d65d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.php_cs.dist
@@ -0,0 +1,15 @@
+<?php
+
+return PhpCsFixer\Config::create()
+ ->setRules(array(
+ '@Symfony' => true,
+ '@Symfony:risky' => true,
+ 'array_syntax' => array('syntax' => 'long'),
+ 'no_unreachable_default_argument_value' => false,
+ 'braces' => array('allow_single_line_closure' => true),
+ 'heredoc_to_nowdoc' => false,
+ 'phpdoc_annotation_without_dot' => false,
+ ))
+ ->setRiskyAllowed(true)
+ ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
+;
diff --git a/vendor/swiftmailer/swiftmailer/.travis.yml b/vendor/swiftmailer/swiftmailer/.travis.yml
new file mode 100644
index 0000000..fc24d05
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/.travis.yml
@@ -0,0 +1,31 @@
+language: php
+
+sudo: false
+
+before_script:
+ - cp tests/acceptance.conf.php.default tests/acceptance.conf.php
+ - cp tests/smoke.conf.php.default tests/smoke.conf.php
+ - composer self-update
+ - composer update --no-interaction --prefer-source
+ - gem install mime-types -v 2.99.1
+ - gem install mailcatcher
+ - mailcatcher --smtp-port 4456
+
+script: ./vendor/bin/simple-phpunit
+
+matrix:
+ include:
+ - php: 5.3
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: 7.1
+ - php: hhvm
+ allow_failures:
+ - php: hhvm
+ fast_finish: true
+
+cache:
+ directories:
+ - .phpunit
diff --git a/vendor/swiftmailer/swiftmailer/CHANGES b/vendor/swiftmailer/swiftmailer/CHANGES
new file mode 100644
index 0000000..3532ec2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/CHANGES
@@ -0,0 +1,287 @@
+Changelog
+=========
+
+5.4.12 (2018-07-31)
+-------------------
+
+ * fixed typo
+
+5.4.11 (2018-07-31)
+-------------------
+
+ * fixed startTLS support for PHP 5.6-
+
+5.4.10 (2018-07-27)
+-------------------
+
+ * fixed startTLS only allowed tls1.0, now allowed: tls1.0, tls1.1, tls1.2
+
+5.4.9 (2018-01-23)
+------------------
+
+ * no changes, last version of the 5.x series
+
+5.4.8 (2017-05-01)
+------------------
+
+ * fixed encoding inheritance in addPart()
+ * fixed sorting MIME children when their types are equal
+
+5.4.7 (2017-04-20)
+------------------
+
+ * fixed NTLMAuthenticator clobbering bcmath scale
+
+5.4.6 (2017-02-13)
+------------------
+
+ * removed exceptions thrown in destructors as they lead to fatal errors
+ * switched to use sha256 by default in DKIM as per the RFC
+ * fixed an 'Undefined variable: pipes' PHP notice
+ * fixed long To headers when using the mail transport
+ * fixed NTLMAuthenticator when no domain is passed with the username
+ * prevented fatal error during unserialization of a message
+ * fixed a PHP warning when sending a message that has a length of a multiple of 8192
+
+5.4.5 (2016-12-29)
+------------------
+
+ * SECURITY FIX: fixed CVE-2016-10074 by disallowing potentially unsafe shell characters
+
+ Prior to 5.4.5, the mail transport (Swift_Transport_MailTransport) was vulnerable to passing
+ arbitrary shell arguments if the "From", "ReturnPath" or "Sender" header came
+ from a non-trusted source, potentially allowing Remote Code Execution
+ * deprecated the mail transport
+
+5.4.4 (2016-11-23)
+------------------
+
+ * reverted escaping command-line args to mail (PHP mail() function already does it)
+
+5.4.3 (2016-07-08)
+------------------
+
+ * fixed SimpleHeaderSet::has()/get() when the 0 index is removed
+ * removed the need to have mcrypt installed
+ * fixed broken MIME header encoding with quotes/colons and non-ascii chars
+ * allowed mail transport send for messages without To header
+ * fixed PHP 7 support
+
+5.4.2 (2016-05-01)
+------------------
+
+ * fixed support for IPv6 sockets
+ * added auto-retry when sending messages from the memory spool
+ * fixed consecutive read calls in Swift_ByteStream_FileByteStream
+ * added support for iso-8859-15 encoding
+ * fixed PHP mail extra params on missing reversePath
+ * added methods to set custom stream context options
+ * fixed charset changes in QpContentEncoderProxy
+ * added return-path header to the ignoredHeaders list of DKIMSigner
+ * fixed crlf for subject using mail
+ * fixed add soft line break only when necessary
+ * fixed escaping command-line args to mail
+
+5.4.1 (2015-06-06)
+------------------
+
+ * made Swiftmailer exceptions confirm to PHP base exception constructor signature
+ * fixed MAIL FROM & RCPT TO headers to be RFC compliant
+
+5.4.0 (2015-03-14)
+------------------
+
+ * added the possibility to add extra certs to PKCS#7 signature
+ * fix base64 encoding with streams
+ * added a new RESULT_SPOOLED status for SpoolTransport
+ * fixed getBody() on attachments when called more than once
+ * removed dots from generated filenames in filespool
+
+5.3.1 (2014-12-05)
+------------------
+
+ * fixed cloning of messages with attachments
+
+5.3.0 (2014-10-04)
+------------------
+
+ * fixed cloning when using signers
+ * reverted removal of Swift_Encoding
+ * drop support for PHP 5.2.x
+
+5.2.2 (2014-09-20)
+------------------
+
+ * fixed Japanese support
+ * fixed the memory spool when the message changes when in the pool
+ * added support for cloning messages
+ * fixed PHP warning in the redirect plugin
+ * changed the way to and cc-ed email are sent to only use one transaction
+
+5.2.1 (2014-06-13)
+------------------
+
+ * SECURITY FIX: fixed CLI escaping when using sendmail as a transport
+
+ Prior to 5.2.1, the sendmail transport (Swift_Transport_SendmailTransport)
+ was vulnerable to an arbitrary shell execution if the "From" header came
+ from a non-trusted source and no "Return-Path" is configured.
+
+ * fixed parameter in DKIMSigner
+ * fixed compatibility with PHP < 5.4
+
+5.2.0 (2014-05-08)
+------------------
+
+ * fixed Swift_ByteStream_FileByteStream::read() to match to the specification
+ * fixed from-charset and to-charset arguments in mbstring_convert_encoding() usages
+ * fixed infinite loop in StreamBuffer
+ * fixed NullTransport to return the number of ignored emails instead of 0
+ * Use phpunit and mockery for unit testing (realityking)
+
+5.1.0 (2014-03-18)
+------------------
+
+ * fixed data writing to stream when sending large messages
+ * added support for libopendkim (https://github.com/xdecock/php-opendkim)
+ * merged SignedMessage and Message
+ * added Gmail XOAuth2 authentication
+ * updated the list of known mime types
+ * added NTLM authentication
+
+5.0.3 (2013-12-03)
+------------------
+
+ * fixed double-dot bug
+ * fixed DKIM signer
+
+5.0.2 (2013-08-30)
+------------------
+
+ * handled correct exception type while reading IoBuffer output
+
+5.0.1 (2013-06-17)
+------------------
+
+ * changed the spool to only start the transport when a mail has to be sent
+ * fixed compatibility with PHP 5.2
+ * fixed LICENSE file
+
+5.0.0 (2013-04-30)
+------------------
+
+ * changed the license from LGPL to MIT
+
+4.3.1 (2013-04-11)
+------------------
+
+ * removed usage of the native QP encoder when the charset is not UTF-8
+ * fixed usage of uniqid to avoid collisions
+ * made a performance improvement when tokenizing large headers
+ * fixed usage of the PHP native QP encoder on PHP 5.4.7+
+
+4.3.0 (2013-01-08)
+------------------
+
+ * made the temporary directory configurable via the TMPDIR env variable
+ * added S/MIME signer and encryption support
+
+4.2.2 (2012-10-25)
+------------------
+
+ * added the possibility to throttle messages per second in ThrottlerPlugin (mostly for Amazon SES)
+ * switched mime.qpcontentencoder to automatically use the PHP native encoder on PHP 5.4.7+
+ * allowed specifying a whitelist with regular expressions in RedirectingPlugin
+
+4.2.1 (2012-07-13)
+------------------
+
+ * changed the coding standards to PSR-1/2
+ * fixed issue with autoloading
+ * added NativeQpContentEncoder to enhance performance (for PHP 5.3+)
+
+4.2.0 (2012-06-29)
+------------------
+
+ * added documentation about how to use the Japanese support introduced in 4.1.8
+ * added a way to override the default configuration in a lazy way
+ * changed the PEAR init script to lazy-load the initialization
+ * fixed a bug when calling Swift_Preferences before anything else (regression introduced in 4.1.8)
+
+4.1.8 (2012-06-17)
+------------------
+
+ * added Japanese iso-2022-jp support
+ * changed the init script to lazy-load the initialization
+ * fixed docblocks (@id) which caused some problems with libraries parsing the dobclocks
+ * fixed Swift_Mime_Headers_IdentificationHeader::setId() when passed an array of ids
+ * fixed encoding of email addresses in headers
+ * added replacements setter to the Decorator plugin
+
+4.1.7 (2012-04-26)
+------------------
+
+ * fixed QpEncoder safeMapShareId property
+
+4.1.6 (2012-03-23)
+------------------
+
+ * reduced the size of serialized Messages
+
+4.1.5 (2012-01-04)
+------------------
+
+ * enforced Swift_Spool::queueMessage() to return a Boolean
+ * made an optimization to the memory spool: start the transport only when required
+ * prevented stream_socket_client() from generating an error and throw a Swift_TransportException instead
+ * fixed a PHP warning when calling to mail() when safe_mode is off
+ * many doc tweaks
+
+4.1.4 (2011-12-16)
+------------------
+
+ * added a memory spool (Swift_MemorySpool)
+ * fixed too many opened files when sending emails with attachments
+
+4.1.3 (2011-10-27)
+------------------
+
+ * added STARTTLS support
+ * added missing @return tags on fluent methods
+ * added a MessageLogger plugin that logs all sent messages
+ * added composer.json
+
+4.1.2 (2011-09-13)
+------------------
+
+ * fixed wrong detection of magic_quotes_runtime
+ * fixed fatal errors when no To or Subject header has been set
+ * fixed charset on parameter header continuations
+ * added documentation about how to install Swiftmailer from the PEAR channel
+ * fixed various typos and markup problem in the documentation
+ * fixed warning when cache directory does not exist
+ * fixed "slashes are escaped" bug
+ * changed require_once() to require() in autoload
+
+4.1.1 (2011-07-04)
+------------------
+
+ * added missing file in PEAR package
+
+4.1.0 (2011-06-30)
+------------------
+
+ * documentation has been converted to ReST
+
+4.1.0 RC1 (2011-06-17)
+----------------------
+
+New features:
+
+ * changed the Decorator Plugin to allow replacements in all headers
+ * added Swift_Mime_Grammar and Swift_Validate to validate an email address
+ * modified the autoloader to lazy-initialize Swiftmailer
+ * removed Swift_Mailer::batchSend()
+ * added NullTransport
+ * added new plugins: RedirectingPlugin and ImpersonatePlugin
+ * added a way to send messages asynchronously (Spool)
diff --git a/vendor/swiftmailer/swiftmailer/LICENSE b/vendor/swiftmailer/swiftmailer/LICENSE
new file mode 100644
index 0000000..485f1d6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013-2016 Fabien Potencier
+
+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.
diff --git a/vendor/swiftmailer/swiftmailer/README b/vendor/swiftmailer/swiftmailer/README
new file mode 100644
index 0000000..52c0757
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/README
@@ -0,0 +1,15 @@
+Swift Mailer
+------------
+
+Swift Mailer is a component based mailing solution for PHP 5.
+It is released under the MIT license.
+
+Homepage: https://swiftmailer.symfony.com/
+Documentation: https://swiftmailer.symfony.com/docs/introduction.html
+Bugs: https://github.com/swiftmailer/swiftmailer/issues
+Repository: https://github.com/swiftmailer/swiftmailer
+
+Swift Mailer is highly object-oriented by design and lends itself
+to use in complex web application with a great deal of flexibility.
+
+For full details on usage, see the documentation.
diff --git a/vendor/swiftmailer/swiftmailer/VERSION b/vendor/swiftmailer/swiftmailer/VERSION
new file mode 100644
index 0000000..82a2d1d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/VERSION
@@ -0,0 +1 @@
+Swift-5.4.12
diff --git a/vendor/swiftmailer/swiftmailer/composer.json b/vendor/swiftmailer/swiftmailer/composer.json
new file mode 100644
index 0000000..44a1050
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/composer.json
@@ -0,0 +1,37 @@
+{
+ "name": "swiftmailer/swiftmailer",
+ "type": "library",
+ "description": "Swiftmailer, free feature-rich PHP mailer",
+ "keywords": ["mail","mailer","email"],
+ "homepage": "https://swiftmailer.symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Chris Corbyn"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.1",
+ "symfony/phpunit-bridge": "~3.2"
+ },
+ "autoload": {
+ "files": ["lib/swift_required.php"]
+ },
+ "autoload-dev": {
+ "psr-0": {
+ "Swift_": "tests/unit"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4-dev"
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/doc/headers.rst b/vendor/swiftmailer/swiftmailer/doc/headers.rst
new file mode 100644
index 0000000..2c11c18
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/headers.rst
@@ -0,0 +1,739 @@
+Message Headers
+===============
+
+Sometimes you'll want to add your own headers to a message or modify/remove
+headers that are already present. You work with the message's HeaderSet to do
+this.
+
+Header Basics
+-------------
+
+All MIME entities in Swift Mailer -- including the message itself --
+store their headers in a single object called a HeaderSet. This HeaderSet is
+retrieved with the ``getHeaders()`` method.
+
+As mentioned in the previous chapter, everything that forms a part of a message
+in Swift Mailer is a MIME entity that is represented by an instance of
+``Swift_Mime_MimeEntity``. This includes -- most notably -- the message object
+itself, attachments, MIME parts and embedded images. Each of these MIME entities
+consists of a body and a set of headers that describe the body.
+
+For all of the "standard" headers in these MIME entities, such as the
+``Content-Type``, there are named methods for working with them, such as
+``setContentType()`` and ``getContentType()``. This is because headers are a
+moderately complex area of the library. Each header has a slightly different
+required structure that it must meet in order to comply with the standards that
+govern email (and that are checked by spam blockers etc).
+
+You fetch the HeaderSet from a MIME entity like so:
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ // Fetch the HeaderSet from a Message object
+ $headers = $message->getHeaders();
+
+ $attachment = Swift_Attachment::fromPath('document.pdf');
+
+ // Fetch the HeaderSet from an attachment object
+ $headers = $attachment->getHeaders();
+
+The job of the HeaderSet is to contain and manage instances of Header objects.
+Depending upon the MIME entity the HeaderSet came from, the contents of the
+HeaderSet will be different, since an attachment for example has a different
+set of headers to those in a message.
+
+You can find out what the HeaderSet contains with a quick loop, dumping out
+the names of the headers:
+
+.. code-block:: php
+
+ foreach ($headers->getAll() as $header) {
+ printf("%s<br />\n", $header->getFieldName());
+ }
+
+ /*
+ Content-Transfer-Encoding
+ Content-Type
+ MIME-Version
+ Date
+ Message-ID
+ From
+ Subject
+ To
+ */
+
+You can also dump out the rendered HeaderSet by calling its ``toString()``
+method:
+
+.. code-block:: php
+
+ echo $headers->toString();
+
+ /*
+ Message-ID: <1234869991.499a9ee7f1d5e@swift.generated>
+ Date: Tue, 17 Feb 2009 22:26:31 +1100
+ Subject: Awesome subject!
+ From: sender@example.org
+ To: recipient@example.org
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: quoted-printable
+ */
+
+Where the complexity comes in is when you want to modify an existing header.
+This complexity comes from the fact that each header can be of a slightly
+different type (such as a Date header, or a header that contains email
+addresses, or a header that has key-value parameters on it!). Each header in the
+HeaderSet is an instance of ``Swift_Mime_Header``. They all have common
+functionality, but knowing exactly what type of header you're working with will
+allow you a little more control.
+
+You can determine the type of header by comparing the return value of its
+``getFieldType()`` method with the constants ``TYPE_TEXT``,
+``TYPE_PARAMETERIZED``, ``TYPE_DATE``, ``TYPE_MAILBOX``, ``TYPE_ID`` and
+``TYPE_PATH`` which are defined in ``Swift_Mime_Header``.
+
+
+.. code-block:: php
+
+ foreach ($headers->getAll() as $header) {
+ switch ($header->getFieldType()) {
+ case Swift_Mime_Header::TYPE_TEXT: $type = 'text';
+ break;
+ case Swift_Mime_Header::TYPE_PARAMETERIZED: $type = 'parameterized';
+ break;
+ case Swift_Mime_Header::TYPE_MAILBOX: $type = 'mailbox';
+ break;
+ case Swift_Mime_Header::TYPE_DATE: $type = 'date';
+ break;
+ case Swift_Mime_Header::TYPE_ID: $type = 'ID';
+ break;
+ case Swift_Mime_Header::TYPE_PATH: $type = 'path';
+ break;
+ }
+ printf("%s: is a %s header<br />\n", $header->getFieldName(), $type);
+ }
+
+ /*
+ Content-Transfer-Encoding: is a text header
+ Content-Type: is a parameterized header
+ MIME-Version: is a text header
+ Date: is a date header
+ Message-ID: is a ID header
+ From: is a mailbox header
+ Subject: is a text header
+ To: is a mailbox header
+ */
+
+Headers can be removed from the set, modified within the set, or added to the
+set.
+
+The following sections show you how to work with the HeaderSet and explain the
+details of each implementation of ``Swift_Mime_Header`` that may
+exist within the HeaderSet.
+
+Header Types
+------------
+
+Because all headers are modeled on different data (dates, addresses, text!)
+there are different types of Header in Swift Mailer. Swift Mailer attempts to
+categorize all possible MIME headers into more general groups, defined by a
+small number of classes.
+
+Text Headers
+~~~~~~~~~~~~
+
+Text headers are the simplest type of Header. They contain textual information
+with no special information included within it -- for example the Subject
+header in a message.
+
+There's nothing particularly interesting about a text header, though it is
+probably the one you'd opt to use if you need to add a custom header to a
+message. It represents text just like you'd think it does. If the text
+contains characters that are not permitted in a message header (such as new
+lines, or non-ascii characters) then the header takes care of encoding the
+text so that it can be used.
+
+No header -- including text headers -- in Swift Mailer is vulnerable to
+header-injection attacks. Swift Mailer breaks any attempt at header injection by
+encoding the dangerous data into a non-dangerous form.
+
+It's easy to add a new text header to a HeaderSet. You do this by calling the
+HeaderSet's ``addTextHeader()`` method.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addTextHeader('Your-Header-Name', 'the header value');
+
+Changing the value of an existing text header is done by calling it's
+``setValue()`` method.
+
+.. code-block:: php
+
+ $subject = $message->getHeaders()->get('Subject');
+
+ $subject->setValue('new subject');
+
+When output via ``toString()``, a text header produces something like the
+following:
+
+.. code-block:: php
+
+ $subject = $message->getHeaders()->get('Subject');
+
+ $subject->setValue('amazing subject line');
+
+ echo $subject->toString();
+
+ /*
+
+ Subject: amazing subject line
+
+ */
+
+If the header contains any characters that are outside of the US-ASCII range
+however, they will be encoded. This is nothing to be concerned about since
+mail clients will decode them back.
+
+.. code-block:: php
+
+ $subject = $message->getHeaders()->get('Subject');
+
+ $subject->setValue('contains – dash');
+
+ echo $subject->toString();
+
+ /*
+
+ Subject: contains =?utf-8?Q?=E2=80=93?= dash
+
+ */
+
+Parameterized Headers
+~~~~~~~~~~~~~~~~~~~~~
+
+Parameterized headers are text headers that contain key-value parameters
+following the textual content. The Content-Type header of a message is a
+parameterized header since it contains charset information after the content
+type.
+
+The parameterized header type is a special type of text header. It extends the
+text header by allowing additional information to follow it. All of the methods
+from text headers are available in addition to the methods described here.
+
+Adding a parameterized header to a HeaderSet is done by using the
+``addParameterizedHeader()`` method which takes a text value like
+``addTextHeader()`` but it also accepts an associative array of
+key-value parameters.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addParameterizedHeader(
+ 'Header-Name', 'header value',
+ array('foo' => 'bar')
+ );
+
+To change the text value of the header, call it's ``setValue()`` method just as
+you do with text headers.
+
+To change the parameters in the header, call the header's ``setParameters()``
+method or the ``setParameter()`` method (note the pluralization).
+
+.. code-block:: php
+
+ $type = $message->getHeaders()->get('Content-Type');
+
+ // setParameters() takes an associative array
+ $type->setParameters(array(
+ 'name' => 'file.txt',
+ 'charset' => 'iso-8859-1'
+ ));
+
+ // setParameter() takes two args for $key and $value
+ $type->setParameter('charset', 'iso-8859-1');
+
+When output via ``toString()``, a parameterized header produces something like
+the following:
+
+.. code-block:: php
+
+ $type = $message->getHeaders()->get('Content-Type');
+
+ $type->setValue('text/html');
+ $type->setParameter('charset', 'utf-8');
+
+ echo $type->toString();
+
+ /*
+
+ Content-Type: text/html; charset=utf-8
+
+ */
+
+If the header contains any characters that are outside of the US-ASCII range
+however, they will be encoded, just like they are for text headers. This is
+nothing to be concerned about since mail clients will decode them back.
+Likewise, if the parameters contain any non-ascii characters they will be
+encoded so that they can be transmitted safely.
+
+.. code-block:: php
+
+ $attachment = Swift_Attachment::newInstance();
+
+ $disp = $attachment->getHeaders()->get('Content-Disposition');
+
+ $disp->setValue('attachment');
+ $disp->setParameter('filename', 'report–may.pdf');
+
+ echo $disp->toString();
+
+ /*
+
+ Content-Disposition: attachment; filename*=utf-8''report%E2%80%93may.pdf
+
+ */
+
+Date Headers
+~~~~~~~~~~~~
+
+Date headers contains an RFC 2822 formatted date (i.e. what PHP's ``date('r')``
+returns). They are used anywhere a date or time is needed to be presented as a
+message header.
+
+The data on which a date header is modeled is simply a UNIX timestamp such as
+that returned by ``time()`` or ``strtotime()``. The timestamp is used to create
+a correctly structured RFC 2822 formatted date such as
+``Tue, 17 Feb 2009 22:26:31 +1100``.
+
+The obvious place this header type is used is in the ``Date:`` header of the
+message itself.
+
+It's easy to add a new date header to a HeaderSet. You do this by calling
+the HeaderSet's ``addDateHeader()`` method.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addDateHeader('Your-Header-Name', strtotime('3 days ago'));
+
+Changing the value of an existing date header is done by calling it's
+``setTimestamp()`` method.
+
+.. code-block:: php
+
+ $date = $message->getHeaders()->get('Date');
+
+ $date->setTimestamp(time());
+
+When output via ``toString()``, a date header produces something like the
+following:
+
+.. code-block:: php
+
+ $date = $message->getHeaders()->get('Date');
+
+ echo $date->toString();
+
+ /*
+
+ Date: Wed, 18 Feb 2009 13:35:02 +1100
+
+ */
+
+Mailbox (e-mail address) Headers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Mailbox headers contain one or more email addresses, possibly with
+personalized names attached to them. The data on which they are modeled is
+represented by an associative array of email addresses and names.
+
+Mailbox headers are probably the most complex header type to understand in
+Swift Mailer because they accept their input as an array which can take various
+forms, as described in the previous chapter.
+
+All of the headers that contain e-mail addresses in a message -- with the
+exception of ``Return-Path:`` which has a stricter syntax -- use this header
+type. That is, ``To:``, ``From:`` etc.
+
+You add a new mailbox header to a HeaderSet by calling the HeaderSet's
+``addMailboxHeader()`` method.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addMailboxHeader('Your-Header-Name', array(
+ 'person1@example.org' => 'Person Name One',
+ 'person2@example.org',
+ 'person3@example.org',
+ 'person4@example.org' => 'Another named person'
+ ));
+
+Changing the value of an existing mailbox header is done by calling it's
+``setNameAddresses()`` method.
+
+.. code-block:: php
+
+ $to = $message->getHeaders()->get('To');
+
+ $to->setNameAddresses(array(
+ 'joe@example.org' => 'Joe Bloggs',
+ 'john@example.org' => 'John Doe',
+ 'no-name@example.org'
+ ));
+
+If you don't wish to concern yourself with the complicated accepted input
+formats accepted by ``setNameAddresses()`` as described in the previous chapter
+and you only want to set one or more addresses (not names) then you can just
+use the ``setAddresses()`` method instead.
+
+.. code-block:: php
+
+ $to = $message->getHeaders()->get('To');
+
+ $to->setAddresses(array(
+ 'joe@example.org',
+ 'john@example.org',
+ 'no-name@example.org'
+ ));
+
+.. note::
+
+ Both methods will accept the above input format in practice.
+
+If all you want to do is set a single address in the header, you can use a
+string as the input parameter to ``setAddresses()`` and/or
+``setNameAddresses()``.
+
+.. code-block:: php
+
+ $to = $message->getHeaders()->get('To');
+
+ $to->setAddresses('joe-bloggs@example.org');
+
+When output via ``toString()``, a mailbox header produces something like the
+following:
+
+.. code-block:: php
+
+ $to = $message->getHeaders()->get('To');
+
+ $to->setNameAddresses(array(
+ 'person1@example.org' => 'Name of Person',
+ 'person2@example.org',
+ 'person3@example.org' => 'Another Person'
+ ));
+
+ echo $to->toString();
+
+ /*
+
+ To: Name of Person <person1@example.org>, person2@example.org, Another Person
+ <person3@example.org>
+
+ */
+
+ID Headers
+~~~~~~~~~~
+
+ID headers contain identifiers for the entity (or the message). The most
+notable ID header is the Message-ID header on the message itself.
+
+An ID that exists inside an ID header looks more-or-less less like an email
+address. For example, ``<1234955437.499becad62ec2@example.org>``.
+The part to the left of the @ sign is usually unique, based on the current time
+and some random factor. The part on the right is usually a domain name.
+
+Any ID passed to the header's ``setId()`` method absolutely MUST conform to
+this structure, otherwise you'll get an Exception thrown at you by Swift Mailer
+(a ``Swift_RfcComplianceException``). This is to ensure that the generated
+email complies with relevant RFC documents and therefore is less likely to be
+blocked as spam.
+
+It's easy to add a new ID header to a HeaderSet. You do this by calling
+the HeaderSet's ``addIdHeader()`` method.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addIdHeader('Your-Header-Name', '123456.unqiue@example.org');
+
+Changing the value of an existing ID header is done by calling its
+``setId()`` method::
+
+ $msgId = $message->getHeaders()->get('Message-ID');
+
+ $msgId->setId(time() . '.' . uniqid('thing') . '@example.org');
+
+When output via ``toString()``, an ID header produces something like the
+following:
+
+.. code-block:: php
+
+ $msgId = $message->getHeaders()->get('Message-ID');
+
+ echo $msgId->toString();
+
+ /*
+
+ Message-ID: <1234955437.499becad62ec2@example.org>
+
+ */
+
+Path Headers
+~~~~~~~~~~~~
+
+Path headers are like very-restricted mailbox headers. They contain a single
+email address with no associated name. The Return-Path header of a message is
+a path header.
+
+You add a new path header to a HeaderSet by calling the HeaderSet's
+``addPathHeader()`` method.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $headers = $message->getHeaders();
+
+ $headers->addPathHeader('Your-Header-Name', 'person@example.org');
+
+Changing the value of an existing path header is done by calling its
+``setAddress()`` method.
+
+.. code-block:: php
+
+ $return = $message->getHeaders()->get('Return-Path');
+
+ $return->setAddress('my-address@example.org');
+
+When output via ``toString()``, a path header produces something like the
+following:
+
+.. code-block:: php
+
+ $return = $message->getHeaders()->get('Return-Path');
+
+ $return->setAddress('person@example.org');
+
+ echo $return->toString();
+
+ /*
+
+ Return-Path: <person@example.org>
+
+ */
+
+Header Operations
+-----------------
+
+Working with the headers in a message involves knowing how to use the methods
+on the HeaderSet and on the individual Headers within the HeaderSet.
+
+Adding new Headers
+~~~~~~~~~~~~~~~~~~
+
+New headers can be added to the HeaderSet by using one of the provided
+``add..Header()`` methods.
+
+To add a header to a MIME entity (such as the message):
+
+Get the HeaderSet from the entity by via its ``getHeaders()`` method.
+
+* Add the header to the HeaderSet by calling one of the ``add..Header()``
+ methods.
+
+The added header will appear in the message when it is sent.
+
+.. code-block:: php
+
+ // Adding a custom header to a message
+ $message = Swift_Message::newInstance();
+ $headers = $message->getHeaders();
+ $headers->addTextHeader('X-Mine', 'something here');
+
+ // Adding a custom header to an attachment
+ $attachment = Swift_Attachment::fromPath('/path/to/doc.pdf');
+ $attachment->getHeaders()->addDateHeader('X-Created-Time', time());
+
+Retrieving Headers
+~~~~~~~~~~~~~~~~~~
+
+Headers are retrieved through the HeaderSet's ``get()`` and ``getAll()``
+methods.
+
+To get a header, or several headers from a MIME entity:
+
+* Get the HeaderSet from the entity by via its ``getHeaders()`` method.
+
+* Get the header(s) from the HeaderSet by calling either ``get()`` or
+ ``getAll()``.
+
+When using ``get()`` a single header is returned that matches the name (case
+insensitive) that is passed to it. When using ``getAll()`` with a header name,
+an array of headers with that name are returned. Calling ``getAll()`` with no
+arguments returns an array of all headers present in the entity.
+
+.. note::
+
+ It's valid for some headers to appear more than once in a message (e.g.
+ the Received header). For this reason ``getAll()`` exists to fetch all
+ headers with a specified name. In addition, ``get()`` accepts an optional
+ numerical index, starting from zero to specify which header you want more
+ specifically.
+
+.. note::
+
+ If you want to modify the contents of the header and you don't know for
+ sure what type of header it is then you may need to check the type by
+ calling its ``getFieldType()`` method.
+
+ .. code-block:: php
+
+ $headers = $message->getHeaders();
+
+ // Get the To: header
+ $toHeader = $headers->get('To');
+
+ // Get all headers named "X-Foo"
+ $fooHeaders = $headers->getAll('X-Foo');
+
+ // Get the second header named "X-Foo"
+ $foo = $headers->get('X-Foo', 1);
+
+ // Get all headers that are present
+ $all = $headers->getAll();
+
+Check if a Header Exists
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can check if a named header is present in a HeaderSet by calling its
+``has()`` method.
+
+To check if a header exists:
+
+* Get the HeaderSet from the entity by via its ``getHeaders()`` method.
+
+* Call the HeaderSet's ``has()`` method specifying the header you're looking
+ for.
+
+If the header exists, ``true`` will be returned or ``false`` if not.
+
+.. note::
+
+ It's valid for some headers to appear more than once in a message (e.g.
+ the Received header). For this reason ``has()`` accepts an optional
+ numerical index, starting from zero to specify which header you want to
+ check more specifically.
+
+ .. code-block:: php
+
+ $headers = $message->getHeaders();
+
+ // Check if the To: header exists
+ if ($headers->has('To')) {
+ echo 'To: exists';
+ }
+
+ // Check if an X-Foo header exists twice (i.e. check for the 2nd one)
+ if ($headers->has('X-Foo', 1)) {
+ echo 'Second X-Foo header exists';
+ }
+
+Removing Headers
+~~~~~~~~~~~~~~~~
+
+Removing a Header from the HeaderSet is done by calling the HeaderSet's
+``remove()`` or ``removeAll()`` methods.
+
+To remove an existing header:
+
+* Get the HeaderSet from the entity by via its ``getHeaders()`` method.
+
+* Call the HeaderSet's ``remove()`` or ``removeAll()`` methods specifying the
+ header you want to remove.
+
+When calling ``remove()`` a single header will be removed. When calling
+``removeAll()`` all headers with the given name will be removed. If no headers
+exist with the given name, no errors will occur.
+
+.. note::
+
+ It's valid for some headers to appear more than once in a message (e.g.
+ the Received header). For this reason ``remove()`` accepts an optional
+ numerical index, starting from zero to specify which header you want to
+ check more specifically. For the same reason, ``removeAll()`` exists to
+ remove all headers that have the given name.
+
+ .. code-block:: php
+
+ $headers = $message->getHeaders();
+
+ // Remove the Subject: header
+ $headers->remove('Subject');
+
+ // Remove all X-Foo headers
+ $headers->removeAll('X-Foo');
+
+ // Remove only the second X-Foo header
+ $headers->remove('X-Foo', 1);
+
+Modifying a Header's Content
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To change a Header's content you should know what type of header it is and then
+call it's appropriate setter method. All headers also have a
+``setFieldBodyModel()`` method that accepts a mixed parameter and delegates to
+the correct setter.
+
+To modify an existing header:
+
+* Get the HeaderSet from the entity by via its ``getHeaders()`` method.
+
+* Get the Header by using the HeaderSet's ``get()``.
+
+* Call the Header's appropriate setter method or call the header's
+ ``setFieldBodyModel()`` method.
+
+The header will be updated inside the HeaderSet and the changes will be seen
+when the message is sent.
+
+.. code-block:: php
+
+ $headers = $message->getHeaders();
+
+ // Change the Subject: header
+ $subj = $headers->get('Subject');
+ $subj->setValue('new subject here');
+
+ // Change the To: header
+ $to = $headers->get('To');
+ $to->setNameAddresses(array(
+ 'person@example.org' => 'Person',
+ 'thing@example.org'
+ ));
+
+ // Using the setFieldBodyModel() just delegates to the correct method
+ // So here to calls setNameAddresses()
+ $to->setFieldBodyModel(array(
+ 'person@example.org' => 'Person',
+ 'thing@example.org'
+ ));
diff --git a/vendor/swiftmailer/swiftmailer/doc/help-resources.rst b/vendor/swiftmailer/swiftmailer/doc/help-resources.rst
new file mode 100644
index 0000000..4208935
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/help-resources.rst
@@ -0,0 +1,44 @@
+Getting Help
+============
+
+There are a number of ways you can get help when using Swift Mailer, depending
+upon the nature of your problem. For bug reports and feature requests create a
+new ticket in GitHub. For general advice ask on the Google Group
+(swiftmailer).
+
+Submitting Bugs & Feature Requests
+----------------------------------
+
+Bugs and feature requests should be posted on GitHub.
+
+If you post a bug or request a feature in the forum, or on the Google Group
+you will most likely be asked to create a ticket in `GitHub`_ since it is
+simply not feasible to manage such requests from a number of a different
+sources.
+
+When you go to GitHub you will be asked to create a username and password
+before you can create a ticket. This is free and takes very little time.
+
+When you create your ticket, do not assign it to any milestones. A developer
+will assess your ticket and re-assign it as needed.
+
+If your ticket is reporting a bug present in the current version, which was
+not present in the previous version please include the tag "regression" in
+your ticket.
+
+GitHub will update you when work is performed on your ticket.
+
+Ask on the Google Group
+-----------------------
+
+You can seek advice at Google Groups, within the "swiftmailer" `group`_.
+
+You can post messages to this group if you want help, or there's something you
+wish to discuss with the developers and with other users.
+
+This is probably the fastest way to get help since it is primarily email-based
+for most users, though bug reports should not be posted here since they may
+not be resolved.
+
+.. _`GitHub`: https://github.com/swiftmailer/swiftmailer/issues
+.. _`group`: http://groups.google.com/group/swiftmailer
diff --git a/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst b/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst
new file mode 100644
index 0000000..978dca2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst
@@ -0,0 +1,46 @@
+Including Swift Mailer (Autoloading)
+====================================
+
+If you are using Composer, Swift Mailer will be automatically autoloaded.
+
+If not, you can use the built-in autoloader by requiring the
+``swift_required.php`` file::
+
+ require_once '/path/to/swift-mailer/lib/swift_required.php';
+
+ /* rest of code goes here */
+
+If you want to override the default Swift Mailer configuration, call the
+``init()`` method on the ``Swift`` class and pass it a valid PHP callable (a
+PHP function name, a PHP 5.3 anonymous function, ...)::
+
+ require_once '/path/to/swift-mailer/lib/swift_required.php';
+
+ function swiftmailer_configurator() {
+ // configure Swift Mailer
+
+ Swift_DependencyContainer::getInstance()->...
+ Swift_Preferences::getInstance()->...
+ }
+
+ Swift::init('swiftmailer_configurator');
+
+ /* rest of code goes here */
+
+The advantage of using the ``init()`` method is that your code will be
+executed only if you use Swift Mailer in your script.
+
+.. note::
+
+ While Swift Mailer's autoloader is designed to play nicely with other
+ autoloaders, sometimes you may have a need to avoid using Swift Mailer's
+ autoloader and use your own instead. Include the ``swift_init.php``
+ instead of the ``swift_required.php`` if you need to do this. The very
+ minimum include is the ``swift_init.php`` file since Swift Mailer will not
+ work without the dependency injection this file sets up:
+
+ .. code-block:: php
+
+ require_once '/path/to/swift-mailer/lib/swift_init.php';
+
+ /* rest of code goes here */
diff --git a/vendor/swiftmailer/swiftmailer/doc/index.rst b/vendor/swiftmailer/swiftmailer/doc/index.rst
new file mode 100644
index 0000000..a1a0a92
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/index.rst
@@ -0,0 +1,16 @@
+Swiftmailer
+===========
+
+.. toctree::
+ :maxdepth: 2
+
+ introduction
+ overview
+ installing
+ help-resources
+ including-the-files
+ messages
+ headers
+ sending
+ plugins
+ japanese
diff --git a/vendor/swiftmailer/swiftmailer/doc/installing.rst b/vendor/swiftmailer/swiftmailer/doc/installing.rst
new file mode 100644
index 0000000..557211d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/installing.rst
@@ -0,0 +1,89 @@
+Installing the Library
+======================
+
+Installing with Composer
+------------------------
+
+The recommended way to install Swiftmailer is via Composer:
+
+.. code-block:: bash
+
+ $ php composer.phar require swiftmailer/swiftmailer @stable
+
+Installing from Git
+-------------------
+
+It's possible to download and install Swift Mailer directly from github.com if
+you want to keep up-to-date with ease.
+
+Swift Mailer's source code is kept in a git repository at github.com so you
+can get the source directly from the repository.
+
+.. note::
+
+ You do not need to have git installed to use Swift Mailer from GitHub. If
+ you don't have git installed, go to `GitHub`_ and click the "Download"
+ button.
+
+Cloning the Repository
+~~~~~~~~~~~~~~~~~~~~~~
+
+The repository can be cloned from git://github.com/swiftmailer/swiftmailer.git
+using the ``git clone`` command.
+
+You will need to have ``git`` installed before you can use the
+``git clone`` command.
+
+To clone the repository:
+
+* Open your favorite terminal environment (command line).
+
+* Move to the directory you want to clone to.
+
+* Run the command ``git clone git://github.com/swiftmailer/swiftmailer.git
+ swiftmailer``.
+
+The source code will be downloaded into a directory called "swiftmailer".
+
+The example shows the process on a UNIX-like system such as Linux, BSD or Mac
+OS X.
+
+.. code-block:: bash
+
+ $ cd source_code/
+ $ git clone git://github.com/swiftmailer/swiftmailer.git swiftmailer
+ Initialized empty Git repository in /Users/chris/source_code/swiftmailer/.git/
+ remote: Counting objects: 6815, done.
+ remote: Compressing objects: 100% (2761/2761), done.
+ remote: Total 6815 (delta 3641), reused 6326 (delta 3286)
+ Receiving objects: 100% (6815/6815), 4.35 MiB | 162 KiB/s, done.
+ Resolving deltas: 100% (3641/3641), done.
+ Checking out files: 100% (1847/1847), done.
+ $ cd swiftmailer/
+ $ ls
+ CHANGES LICENSE ...
+ $
+
+Troubleshooting
+---------------
+
+Swift Mailer does not work when used with function overloading as implemented
+by ``mbstring`` (``mbstring.func_overload`` set to ``2``). A workaround is to
+temporarily change the internal encoding to ``ASCII`` when sending an email:
+
+.. code-block:: php
+
+ if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2)
+ {
+ $mbEncoding = mb_internal_encoding();
+ mb_internal_encoding('ASCII');
+ }
+
+ // Create your message and send it with Swift Mailer
+
+ if (isset($mbEncoding))
+ {
+ mb_internal_encoding($mbEncoding);
+ }
+
+.. _`GitHub`: http://github.com/swiftmailer/swiftmailer
diff --git a/vendor/swiftmailer/swiftmailer/doc/introduction.rst b/vendor/swiftmailer/swiftmailer/doc/introduction.rst
new file mode 100644
index 0000000..a85336b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/introduction.rst
@@ -0,0 +1,135 @@
+Introduction
+============
+
+Swift Mailer is a component-based library for sending e-mails from PHP
+applications.
+
+Organization of this Book
+-------------------------
+
+This book has been written so that those who need information quickly are able
+to find what they need, and those who wish to learn more advanced topics can
+read deeper into each chapter.
+
+The book begins with an overview of Swift Mailer, discussing what's included
+in the package and preparing you for the remainder of the book.
+
+It is possible to read this user guide just like any other book (from
+beginning to end). Each chapter begins with a discussion of the contents it
+contains, followed by a short code sample designed to give you a head start.
+As you get further into a chapter you will learn more about Swift Mailer's
+capabilities, but often you will be able to head directly to the topic you
+wish to learn about.
+
+Throughout this book you will be presented with code samples, which most
+people should find ample to implement Swift Mailer appropriately in their own
+projects. We will also use diagrams where appropriate, and where we believe
+readers may find it helpful we will discuss some related theory, including
+reference to certain documents you are able to find online.
+
+Code Samples
+------------
+
+Code samples presented in this book will be displayed on a different colored
+background in a monospaced font. Samples are not to be taken as copy & paste
+code snippets.
+
+Code examples are used through the book to clarify what is written in text.
+They will sometimes be usable as-is, but they should always be taken as
+outline/pseudo code only.
+
+A code sample will look like this::
+
+ class AClass
+ {
+ ...
+ }
+
+ // A Comment
+ $obj = new AClass($arg1, $arg2, ... );
+
+ /* A note about another way of doing something
+ $obj = AClass::newInstance($arg1, $arg2, ... );
+
+ */
+
+The presence of 3 dots ``...`` in a code sample indicates that we have left
+out a chunk of the code for brevity, they are not actually part of the code.
+
+We will often place multi-line comments ``/* ... */`` in the code so that we
+can show alternative ways of achieving the same result.
+
+You should read the code examples given and try to understand them. They are
+kept concise so that you are not overwhelmed with information.
+
+History of Swift Mailer
+-----------------------
+
+Swift Mailer began back in 2005 as a one-class project for sending mail over
+SMTP. It has since grown into the flexible component-based library that is in
+development today.
+
+Chris Corbyn first posted Swift Mailer on a web forum asking for comments from
+other developers. It was never intended as a fully supported open source
+project, but members of the forum began to adopt it and make use of it.
+
+Very quickly feature requests were coming for the ability to add attachments
+and use SMTP authentication, along with a number of other "obvious" missing
+features. Considering the only alternative was PHPMailer it seemed like a good
+time to bring some fresh tools to the table. Chris began working towards a
+more component based, PHP5-like approach unlike the existing single-class,
+legacy PHP4 approach taken by PHPMailer.
+
+Members of the forum offered a lot of advice and critique on the code as he
+worked through this project and released versions 2 and 3 of the library in
+2005 and 2006, which by then had been broken down into smaller classes
+offering more flexibility and supporting plugins. To this day the Swift Mailer
+team still receive a lot of feature requests from users both on the forum and
+in by email.
+
+Until 2008 Chris was the sole developer of Swift Mailer, but entering 2009 he
+gained the support of two experienced developers well-known to him: Paul
+Annesley and Christopher Thompson. This has been an extremely welcome change.
+
+As of September 2009, Chris handed over the maintenance of Swift Mailer to
+Fabien Potencier.
+
+Now 2009 and in its fourth major version Swift Mailer is more object-oriented
+and flexible than ever, both from a usability standpoint and from a
+development standpoint.
+
+By no means is Swift Mailer ready to call "finished". There are still many
+features that can be added to the library along with the constant refactoring
+that happens behind the scenes.
+
+It's a Library!
+---------------
+
+Swift Mailer is not an application - it's a library.
+
+To most experienced developers this is probably an obvious point to make, but
+it's certainly worth mentioning. Many people often contact us having gotten
+the completely wrong end of the stick in terms of what Swift Mailer is
+actually for.
+
+It's not an application. It does not have a graphical user interface. It
+cannot be opened in your web browser directly.
+
+It's a library (or a framework if you like). It provides a whole lot of
+classes that do some very complicated things, so that you don't have to. You
+"use" Swift Mailer within an application so that your application can have the
+ability to send emails.
+
+The component-based structure of the library means that you are free to
+implement it in a number of different ways and that you can pick and choose
+what you want to use.
+
+An application on the other hand (such as a blog or a forum) is already "put
+together" in a particular way, (usually) provides a graphical user interface
+and most likely doesn't offer a great deal of integration with your own
+application.
+
+Embrace the structure of the library and use the components it offers to your
+advantage. Learning what the components do, rather than blindly copying and
+pasting existing code will put you in a great position to build a powerful
+application!
diff --git a/vendor/swiftmailer/swiftmailer/doc/japanese.rst b/vendor/swiftmailer/swiftmailer/doc/japanese.rst
new file mode 100644
index 0000000..34afa7b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/japanese.rst
@@ -0,0 +1,22 @@
+Using Swift Mailer for Japanese Emails
+======================================
+
+To send emails in Japanese, you need to tweak the default configuration.
+
+After requiring the Swift Mailer autoloader (by including the
+``swift_required.php`` file), call the ``Swift::init()`` method with the
+following code::
+
+ require_once '/path/to/swift-mailer/lib/swift_required.php';
+
+ Swift::init(function () {
+ Swift_DependencyContainer::getInstance()
+ ->register('mime.qpheaderencoder')
+ ->asAliasOf('mime.base64headerencoder');
+
+ Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
+ });
+
+ /* rest of code goes here */
+
+That's all!
diff --git a/vendor/swiftmailer/swiftmailer/doc/messages.rst b/vendor/swiftmailer/swiftmailer/doc/messages.rst
new file mode 100644
index 0000000..b058b77
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/messages.rst
@@ -0,0 +1,1058 @@
+Creating Messages
+=================
+
+Creating messages in Swift Mailer is done by making use of the various MIME
+entities provided with the library. Complex messages can be quickly created
+with very little effort.
+
+Quick Reference for Creating a Message
+---------------------------------------
+
+You can think of creating a Message as being similar to the steps you perform
+when you click the Compose button in your mail client. You give it a subject,
+specify some recipients, add any attachments and write your message.
+
+To create a Message:
+
+* Call the ``newInstance()`` method of ``Swift_Message``.
+
+* Set your sender address (``From:``) with ``setFrom()`` or ``setSender()``.
+
+* Set a subject line with ``setSubject()``.
+
+* Set recipients with ``setTo()``, ``setCc()`` and/or ``setBcc()``.
+
+* Set a body with ``setBody()``.
+
+* Add attachments with ``attach()``.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the message
+ $message = Swift_Message::newInstance()
+
+ // Give the message a subject
+ ->setSubject('Your subject')
+
+ // Set the From address with an associative array
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+
+ // Set the To addresses with an associative array
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+
+ // Give it a body
+ ->setBody('Here is the message itself')
+
+ // And optionally an alternative body
+ ->addPart('<q>Here is the message itself</q>', 'text/html')
+
+ // Optionally add any attachments
+ ->attach(Swift_Attachment::fromPath('my-document.pdf'))
+ ;
+
+Message Basics
+--------------
+
+A message is a container for anything you want to send to somebody else. There
+are several basic aspects of a message that you should know.
+
+An e-mail message is made up of several relatively simple entities that are
+combined in different ways to achieve different results. All of these entities
+have the same fundamental outline but serve a different purpose. The Message
+itself can be defined as a MIME entity, an Attachment is a MIME entity, all
+MIME parts are MIME entities -- and so on!
+
+The basic units of each MIME entity -- be it the Message itself, or an
+Attachment -- are its Headers and its body:
+
+.. code-block:: text
+
+ Header-Name: A header value
+ Other-Header: Another value
+
+ The body content itself
+
+The Headers of a MIME entity, and its body must conform to some strict
+standards defined by various RFC documents. Swift Mailer ensures that these
+specifications are followed by using various types of object, including
+Encoders and different Header types to generate the entity.
+
+The Structure of a Message
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Of all of the MIME entities, a message -- ``Swift_Message``
+is the largest and most complex. It has many properties that can be updated
+and it can contain other MIME entities -- attachments for example --
+nested inside it.
+
+A Message has a lot of different Headers which are there to present
+information about the message to the recipients' mail client. Most of these
+headers will be familiar to the majority of users, but we'll list the basic
+ones. Although it's possible to work directly with the Headers of a Message
+(or other MIME entity), the standard Headers have accessor methods provided to
+abstract away the complex details for you. For example, although the Date on a
+message is written with a strict format, you only need to pass a UNIX
+timestamp to ``setDate()``.
+
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| Header | Description | Accessors |
++===============================+====================================================================================================================================+=============================================+
+| ``Message-ID`` | Identifies this message with a unique ID, usually containing the domain name and time generated | ``getId()`` / ``setId()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Return-Path`` | Specifies where bounces should go (Swift Mailer reads this for other uses) | ``getReturnPath()`` / ``setReturnPath()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``From`` | Specifies the address of the person who the message is from. This can be multiple addresses if multiple people wrote the message. | ``getFrom()`` / ``setFrom()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Sender`` | Specifies the address of the person who physically sent the message (higher precedence than ``From:``) | ``getSender()`` / ``setSender()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``To`` | Specifies the addresses of the intended recipients | ``getTo()`` / ``setTo()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Cc`` | Specifies the addresses of recipients who will be copied in on the message | ``getCc()`` / ``setCc()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Bcc`` | Specifies the addresses of recipients who the message will be blind-copied to. Other recipients will not be aware of these copies. | ``getBcc()`` / ``setBcc()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Reply-To`` | Specifies the address where replies are sent to | ``getReplyTo()`` / ``setReplyTo()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Subject`` | Specifies the subject line that is displayed in the recipients' mail client | ``getSubject()`` / ``setSubject()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Date`` | Specifies the date at which the message was sent | ``getDate()`` / ``setDate()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Content-Type`` | Specifies the format of the message (usually text/plain or text/html) | ``getContentType()`` / ``setContentType()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+| ``Content-Transfer-Encoding`` | Specifies the encoding scheme in the message | ``getEncoder()`` / ``setEncoder()`` |
++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+
+
+Working with a Message Object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Although there are a lot of available methods on a message object, you only
+need to make use of a small subset of them. Usually you'll use
+``setSubject()``, ``setTo()`` and
+``setFrom()`` before setting the body of your message with
+``setBody()``.
+
+Calling methods is simple. You just call them like functions, but using the
+object operator "``->``" to do so. If you've created
+a message object and called it ``$message`` then you'd set a
+subject on it like so:
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ $message = Swift_Message::newInstance();
+ $message->setSubject('My subject');
+
+All MIME entities (including a message) have a ``toString()``
+method that you can call if you want to take a look at what is going to be
+sent. For example, if you ``echo
+$message->toString();`` you would see something like this:
+
+.. code-block:: bash
+
+ Message-ID: <1230173678.4952f5eeb1432@swift.generated>
+ Date: Thu, 25 Dec 2008 13:54:38 +1100
+ Subject: Example subject
+ From: Chris Corbyn <chris@w3style.co.uk>
+ To: Receiver Name <recipient@example.org>
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: quoted-printable
+
+ Here is the message
+
+We'll take a closer look at the methods you use to create your message in the
+following sections.
+
+Adding Content to Your Message
+------------------------------
+
+Rich content can be added to messages in Swift Mailer with relative ease by
+calling methods such as ``setSubject()``, ``setBody()``, ``addPart()`` and
+``attach()``.
+
+Setting the Subject Line
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The subject line, displayed in the recipients' mail client can be set with the
+``setSubject()`` method, or as a parameter to ``Swift_Message::newInstance()``.
+
+To set the subject of your Message:
+
+* Call the ``setSubject()`` method of the Message, or specify it at the time
+ you create the message.
+
+ .. code-block:: php
+
+ // Pass it as a parameter when you create the message
+ $message = Swift_Message::newInstance('My amazing subject');
+
+ // Or set it after like this
+ $message->setSubject('My amazing subject');
+
+Setting the Body Content
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The body of the message -- seen when the user opens the message --
+is specified by calling the ``setBody()`` method. If an alternative body is to
+be included ``addPart()`` can be used.
+
+The body of a message is the main part that is read by the user. Often people
+want to send a message in HTML format (``text/html``), other
+times people want to send in plain text (``text/plain``), or
+sometimes people want to send both versions and allow the recipient to choose
+how they view the message.
+
+As a rule of thumb, if you're going to send a HTML email, always include a
+plain-text equivalent of the same content so that users who prefer to read
+plain text can do so.
+
+To set the body of your Message:
+
+* Call the ``setBody()`` method of the Message, or specify it at the time you
+ create the message.
+
+* Add any alternative bodies with ``addPart()``.
+
+If the recipient's mail client offers preferences for displaying text vs. HTML
+then the mail client will present that part to the user where available. In
+other cases the mail client will display the "best" part it can - usually HTML
+if you've included HTML.
+
+.. code-block:: php
+
+ // Pass it as a parameter when you create the message
+ $message = Swift_Message::newInstance('Subject here', 'My amazing body');
+
+ // Or set it after like this
+ $message->setBody('My <em>amazing</em> body', 'text/html');
+
+ // Add alternative parts with addPart()
+ $message->addPart('My amazing body in plain text', 'text/plain');
+
+Attaching Files
+---------------
+
+Attachments are downloadable parts of a message and can be added by calling
+the ``attach()`` method on the message. You can add attachments that exist on
+disk, or you can create attachments on-the-fly.
+
+Attachments are actually an interesting area of Swift Mailer and something
+that could put a lot of power at your fingertips if you grasp the concept
+behind the way a message is held together.
+
+Although we refer to files sent over e-mails as "attachments" -- because
+they're attached to the message -- lots of other parts of the message are
+actually "attached" even if we don't refer to these parts as attachments.
+
+File attachments are created by the ``Swift_Attachment`` class
+and then attached to the message via the ``attach()`` method on
+it. For all of the "every day" MIME types such as all image formats, word
+documents, PDFs and spreadsheets you don't need to explicitly set the
+content-type of the attachment, though it would do no harm to do so. For less
+common formats you should set the content-type -- which we'll cover in a
+moment.
+
+Attaching Existing Files
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Files that already exist, either on disk or at a URL can be attached to a
+message with just one line of code, using ``Swift_Attachment::fromPath()``.
+
+You can attach files that exist locally, or if your PHP installation has
+``allow_url_fopen`` turned on you can attach files from other
+websites.
+
+To attach an existing file:
+
+* Create an attachment with ``Swift_Attachment::fromPath()``.
+
+* Add the attachment to the message with ``attach()``.
+
+The attachment will be presented to the recipient as a downloadable file with
+the same filename as the one you attached.
+
+.. code-block:: php
+
+ // Create the attachment
+ // * Note that you can technically leave the content-type parameter out
+ $attachment = Swift_Attachment::fromPath('/path/to/image.jpg', 'image/jpeg');
+
+ // Attach it to the message
+ $message->attach($attachment);
+
+ // The two statements above could be written in one line instead
+ $message->attach(Swift_Attachment::fromPath('/path/to/image.jpg'));
+
+ // You can attach files from a URL if allow_url_fopen is on in php.ini
+ $message->attach(Swift_Attachment::fromPath('http://site.tld/logo.png'));
+
+Setting the Filename
+~~~~~~~~~~~~~~~~~~~~
+
+Usually you don't need to explicitly set the filename of an attachment because
+the name of the attached file will be used by default, but if you want to set
+the filename you use the ``setFilename()`` method of the Attachment.
+
+To change the filename of an attachment:
+
+* Call its ``setFilename()`` method.
+
+The attachment will be attached in the normal way, but meta-data sent inside
+the email will rename the file to something else.
+
+.. code-block:: php
+
+ // Create the attachment and call its setFilename() method
+ $attachment = Swift_Attachment::fromPath('/path/to/image.jpg')
+ ->setFilename('cool.jpg');
+
+ // Because there's a fluid interface, you can do this in one statement
+ $message->attach(
+ Swift_Attachment::fromPath('/path/to/image.jpg')->setFilename('cool.jpg')
+ );
+
+Attaching Dynamic Content
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Files that are generated at runtime, such as PDF documents or images created
+via GD can be attached directly to a message without writing them out to disk.
+Use the standard ``Swift_Attachment::newInstance()`` method.
+
+To attach dynamically created content:
+
+* Create your content as you normally would.
+
+* Create an attachment with ``Swift_Attachment::newInstance()``, specifying
+ the source data of your content along with a name and the content-type.
+
+* Add the attachment to the message with ``attach()``.
+
+The attachment will be presented to the recipient as a downloadable file
+with the filename and content-type you specify.
+
+.. note::
+
+ If you would usually write the file to disk anyway you should just attach
+ it with ``Swift_Attachment::fromPath()`` since this will use less memory:
+
+ .. code-block:: php
+
+ // Create your file contents in the normal way, but don't write them to disk
+ $data = create_my_pdf_data();
+
+ // Create the attachment with your data
+ $attachment = Swift_Attachment::newInstance($data, 'my-file.pdf', 'application/pdf');
+
+ // Attach it to the message
+ $message->attach($attachment);
+
+
+ // You can alternatively use method chaining to build the attachment
+ $attachment = Swift_Attachment::newInstance()
+ ->setFilename('my-file.pdf')
+ ->setContentType('application/pdf')
+ ->setBody($data)
+ ;
+
+Changing the Disposition
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Attachments just appear as files that can be saved to the Desktop if desired.
+You can make attachment appear inline where possible by using the
+``setDisposition()`` method of an attachment.
+
+To make an attachment appear inline:
+
+* Call its ``setDisposition()`` method.
+
+The attachment will be displayed within the email viewing window if the mail
+client knows how to display it.
+
+.. note::
+
+ If you try to create an inline attachment for a non-displayable file type
+ such as a ZIP file, the mail client should just present the attachment as
+ normal:
+
+ .. code-block:: php
+
+ // Create the attachment and call its setDisposition() method
+ $attachment = Swift_Attachment::fromPath('/path/to/image.jpg')
+ ->setDisposition('inline');
+
+
+ // Because there's a fluid interface, you can do this in one statement
+ $message->attach(
+ Swift_Attachment::fromPath('/path/to/image.jpg')->setDisposition('inline')
+ );
+
+Embedding Inline Media Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Often people want to include an image or other content inline with a HTML
+message. It's easy to do this with HTML linking to remote resources, but this
+approach is usually blocked by mail clients. Swift Mailer allows you to embed
+your media directly into the message.
+
+Mail clients usually block downloads from remote resources because this
+technique was often abused as a mean of tracking who opened an email. If
+you're sending a HTML email and you want to include an image in the message
+another approach you can take is to embed the image directly.
+
+Swift Mailer makes embedding files into messages extremely streamlined. You
+embed a file by calling the ``embed()`` method of the message,
+which returns a value you can use in a ``src`` or
+``href`` attribute in your HTML.
+
+Just like with attachments, it's possible to embed dynamically generated
+content without having an existing file available.
+
+The embedded files are sent in the email as a special type of attachment that
+has a unique ID used to reference them within your HTML attributes. On mail
+clients that do not support embedded files they may appear as attachments.
+
+Although this is commonly done for images, in theory it will work for any
+displayable (or playable) media type. Support for other media types (such as
+video) is dependent on the mail client however.
+
+Embedding Existing Files
+........................
+
+Files that already exist, either on disk or at a URL can be embedded in a
+message with just one line of code, using ``Swift_EmbeddedFile::fromPath()``.
+
+You can embed files that exist locally, or if your PHP installation has
+``allow_url_fopen`` turned on you can embed files from other websites.
+
+To embed an existing file:
+
+* Create a message object with ``Swift_Message::newInstance()``.
+
+* Set the body as HTML, and embed a file at the correct point in the message with ``embed()``.
+
+The file will be displayed with the message inline with the HTML wherever its ID
+is used as a ``src`` attribute.
+
+.. note::
+
+ ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one
+ another. ``Swift_Image`` exists for semantic purposes.
+
+.. note::
+
+ You can embed files in two stages if you prefer. Just capture the return
+ value of ``embed()`` in a variable and use that as the ``src`` attribute.
+
+ .. code-block:: php
+
+ // Create the message
+ $message = Swift_Message::newInstance('My subject');
+
+ // Set the body
+ $message->setBody(
+ '<html>' .
+ ' <head></head>' .
+ ' <body>' .
+ ' Here is an image <img src="' . // Embed the file
+ $message->embed(Swift_Image::fromPath('image.png')) .
+ '" alt="Image" />' .
+ ' Rest of message' .
+ ' </body>' .
+ '</html>',
+ 'text/html' // Mark the content-type as HTML
+ );
+
+ // You can embed files from a URL if allow_url_fopen is on in php.ini
+ $message->setBody(
+ '<html>' .
+ ' <head></head>' .
+ ' <body>' .
+ ' Here is an image <img src="' .
+ $message->embed(Swift_Image::fromPath('http://site.tld/logo.png')) .
+ '" alt="Image" />' .
+ ' Rest of message' .
+ ' </body>' .
+ '</html>',
+ 'text/html'
+ );
+
+
+ // If placing the embed() code inline becomes cumbersome
+ // it's easy to do this in two steps
+ $cid = $message->embed(Swift_Image::fromPath('image.png'));
+
+ $message->setBody(
+ '<html>' .
+ ' <head></head>' .
+ ' <body>' .
+ ' Here is an image <img src="' . $cid . '" alt="Image" />' .
+ ' Rest of message' .
+ ' </body>' .
+ '</html>',
+ 'text/html' // Mark the content-type as HTML
+ );
+
+Embedding Dynamic Content
+.........................
+
+Images that are generated at runtime, such as images created via GD can be
+embedded directly to a message without writing them out to disk. Use the
+standard ``Swift_Image::newInstance()`` method.
+
+To embed dynamically created content:
+
+* Create a message object with ``Swift_Message::newInstance()``.
+
+* Set the body as HTML, and embed a file at the correct point in the message
+ with ``embed()``. You will need to specify a filename and a content-type.
+
+The file will be displayed with the message inline with the HTML wherever its ID
+is used as a ``src`` attribute.
+
+.. note::
+
+ ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one
+ another. ``Swift_Image`` exists for semantic purposes.
+
+.. note::
+
+ You can embed files in two stages if you prefer. Just capture the return
+ value of ``embed()`` in a variable and use that as the ``src`` attribute.
+
+ .. code-block:: php
+
+ // Create your file contents in the normal way, but don't write them to disk
+ $img_data = create_my_image_data();
+
+ // Create the message
+ $message = Swift_Message::newInstance('My subject');
+
+ // Set the body
+ $message->setBody(
+ '<html>' .
+ ' <head></head>' .
+ ' <body>' .
+ ' Here is an image <img src="' . // Embed the file
+ $message->embed(Swift_Image::newInstance($img_data, 'image.jpg', 'image/jpeg')) .
+ '" alt="Image" />' .
+ ' Rest of message' .
+ ' </body>' .
+ '</html>',
+ 'text/html' // Mark the content-type as HTML
+ );
+
+
+ // If placing the embed() code inline becomes cumbersome
+ // it's easy to do this in two steps
+ $cid = $message->embed(Swift_Image::newInstance($img_data, 'image.jpg', 'image/jpeg'));
+
+ $message->setBody(
+ '<html>' .
+ ' <head></head>' .
+ ' <body>' .
+ ' Here is an image <img src="' . $cid . '" alt="Image" />' .
+ ' Rest of message' .
+ ' </body>' .
+ '</html>',
+ 'text/html' // Mark the content-type as HTML
+ );
+
+Adding Recipients to Your Message
+---------------------------------
+
+Recipients are specified within the message itself via ``setTo()``, ``setCc()``
+and ``setBcc()``. Swift Mailer reads these recipients from the message when it
+gets sent so that it knows where to send the message to.
+
+Message recipients are one of three types:
+
+* ``To:`` recipients -- the primary recipients (required)
+
+* ``Cc:`` recipients -- receive a copy of the message (optional)
+
+* ``Bcc:`` recipients -- hidden from other recipients (optional)
+
+Each type can contain one, or several addresses. It's possible to list only
+the addresses of the recipients, or you can personalize the address by
+providing the real name of the recipient.
+
+Make sure to add only valid email addresses as recipients. If you try to add an
+invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift
+Mailer will throw a ``Swift_RfcComplianceException``.
+
+If you add recipients automatically based on a data source that may contain
+invalid email addresses, you can prevent possible exceptions by validating the
+addresses using ``Swift_Validate::email($email)`` and only adding addresses
+that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and
+``setBcc()`` calls in a try-catch block and handle the
+``Swift_RfcComplianceException`` in the catch block.
+
+.. sidebar:: Syntax for Addresses
+
+ If you only wish to refer to a single email address (for example your
+ ``From:`` address) then you can just use a string.
+
+ .. code-block:: php
+
+ $message->setFrom('some@address.tld');
+
+ If you want to include a name then you must use an associative array.
+
+ .. code-block:: php
+
+ $message->setFrom(array('some@address.tld' => 'The Name'));
+
+ If you want to include multiple addresses then you must use an array.
+
+ .. code-block:: php
+
+ $message->setTo(array('some@address.tld', 'other@address.tld'));
+
+ You can mix personalized (addresses with a name) and non-personalized
+ addresses in the same list by mixing the use of associative and
+ non-associative array syntax.
+
+ .. code-block:: php
+
+ $message->setTo(array(
+ 'recipient-with-name@example.org' => 'Recipient Name One',
+ 'no-name@example.org', // Note that this is not a key-value pair
+ 'named-recipient@example.org' => 'Recipient Name Two'
+ ));
+
+Setting ``To:`` Recipients
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``To:`` recipients are required in a message and are set with the
+``setTo()`` or ``addTo()`` methods of the message.
+
+To set ``To:`` recipients, create the message object using either
+``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``,
+then call the ``setTo()`` method with a complete array of addresses, or use the
+``addTo()`` method to iteratively add recipients.
+
+The ``setTo()`` method accepts input in various formats as described earlier in
+this chapter. The ``addTo()`` method takes either one or two parameters. The
+first being the email address and the second optional parameter being the name
+of the recipient.
+
+``To:`` recipients are visible in the message headers and will be
+seen by the other recipients.
+
+.. note::
+
+ Multiple calls to ``setTo()`` will not add new recipients -- each
+ call overrides the previous calls. If you want to iteratively add
+ recipients, use the ``addTo()`` method.
+
+ .. code-block:: php
+
+ // Using setTo() to set all recipients in one go
+ $message->setTo(array(
+ 'person1@example.org',
+ 'person2@otherdomain.org' => 'Person 2 Name',
+ 'person3@example.org',
+ 'person4@example.org',
+ 'person5@example.org' => 'Person 5 Name'
+ ));
+
+ // Using addTo() to add recipients iteratively
+ $message->addTo('person1@example.org');
+ $message->addTo('person2@example.org', 'Person 2 Name');
+
+Setting ``Cc:`` Recipients
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Cc:`` recipients are set with the ``setCc()`` or ``addCc()`` methods of the
+message.
+
+To set ``Cc:`` recipients, create the message object using either
+``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call
+the ``setCc()`` method with a complete array of addresses, or use the
+``addCc()`` method to iteratively add recipients.
+
+The ``setCc()`` method accepts input in various formats as described earlier in
+this chapter. The ``addCc()`` method takes either one or two parameters. The
+first being the email address and the second optional parameter being the name
+of the recipient.
+
+``Cc:`` recipients are visible in the message headers and will be
+seen by the other recipients.
+
+.. note::
+
+ Multiple calls to ``setCc()`` will not add new recipients -- each
+ call overrides the previous calls. If you want to iteratively add Cc:
+ recipients, use the ``addCc()`` method.
+
+ .. code-block:: php
+
+ // Using setCc() to set all recipients in one go
+ $message->setCc(array(
+ 'person1@example.org',
+ 'person2@otherdomain.org' => 'Person 2 Name',
+ 'person3@example.org',
+ 'person4@example.org',
+ 'person5@example.org' => 'Person 5 Name'
+ ));
+
+ // Using addCc() to add recipients iteratively
+ $message->addCc('person1@example.org');
+ $message->addCc('person2@example.org', 'Person 2 Name');
+
+Setting ``Bcc:`` Recipients
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Bcc:`` recipients receive a copy of the message without anybody else knowing
+it, and are set with the ``setBcc()`` or ``addBcc()`` methods of the message.
+
+To set ``Bcc:`` recipients, create the message object using either ``new
+Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call the
+``setBcc()`` method with a complete array of addresses, or use
+the ``addBcc()`` method to iteratively add recipients.
+
+The ``setBcc()`` method accepts input in various formats as described earlier in
+this chapter. The ``addBcc()`` method takes either one or two parameters. The
+first being the email address and the second optional parameter being the name
+of the recipient.
+
+Only the individual ``Bcc:`` recipient will see their address in the message
+headers. Other recipients (including other ``Bcc:`` recipients) will not see the
+address.
+
+.. note::
+
+ Multiple calls to ``setBcc()`` will not add new recipients -- each
+ call overrides the previous calls. If you want to iteratively add Bcc:
+ recipients, use the ``addBcc()`` method.
+
+ .. code-block:: php
+
+ // Using setBcc() to set all recipients in one go
+ $message->setBcc(array(
+ 'person1@example.org',
+ 'person2@otherdomain.org' => 'Person 2 Name',
+ 'person3@example.org',
+ 'person4@example.org',
+ 'person5@example.org' => 'Person 5 Name'
+ ));
+
+ // Using addBcc() to add recipients iteratively
+ $message->addBcc('person1@example.org');
+ $message->addBcc('person2@example.org', 'Person 2 Name');
+
+Specifying Sender Details
+-------------------------
+
+An email must include information about who sent it. Usually this is managed
+by the ``From:`` address, however there are other options.
+
+The sender information is contained in three possible places:
+
+* ``From:`` -- the address(es) of who wrote the message (required)
+
+* ``Sender:`` -- the address of the single person who sent the message
+ (optional)
+
+* ``Return-Path:`` -- the address where bounces should go to (optional)
+
+You must always include a ``From:`` address by using ``setFrom()`` on the
+message. Swift Mailer will use this as the default ``Return-Path:`` unless
+otherwise specified.
+
+The ``Sender:`` address exists because the person who actually sent the email
+may not be the person who wrote the email. It has a higher precedence than the
+``From:`` address and will be used as the ``Return-Path:`` unless otherwise
+specified.
+
+Setting the ``From:`` Address
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A ``From:`` address is required and is set with the ``setFrom()`` method of the
+message. ``From:`` addresses specify who actually wrote the email, and usually who sent it.
+
+What most people probably don't realise is that you can have more than one
+``From:`` address if more than one person wrote the email -- for example if an
+email was put together by a committee.
+
+To set the ``From:`` address(es):
+
+* Call the ``setFrom()`` method on the Message.
+
+The ``From:`` address(es) are visible in the message headers and
+will be seen by the recipients.
+
+.. note::
+
+ If you set multiple ``From:`` addresses then you absolutely must set a
+ ``Sender:`` address to indicate who physically sent the message.
+
+ .. code-block:: php
+
+ // Set a single From: address
+ $message->setFrom('your@address.tld');
+
+ // Set a From: address including a name
+ $message->setFrom(array('your@address.tld' => 'Your Name'));
+
+ // Set multiple From: addresses if multiple people wrote the email
+ $message->setFrom(array(
+ 'person1@example.org' => 'Sender One',
+ 'person2@example.org' => 'Sender Two'
+ ));
+
+Setting the ``Sender:`` Address
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A ``Sender:`` address specifies who sent the message and is set with the
+``setSender()`` method of the message.
+
+To set the ``Sender:`` address:
+
+* Call the ``setSender()`` method on the Message.
+
+The ``Sender:`` address is visible in the message headers and will be seen by
+the recipients.
+
+This address will be used as the ``Return-Path:`` unless otherwise specified.
+
+.. note::
+
+ If you set multiple ``From:`` addresses then you absolutely must set a
+ ``Sender:`` address to indicate who physically sent the message.
+
+You must not set more than one sender address on a message because it's not
+possible for more than one person to send a single message.
+
+.. code-block:: php
+
+ $message->setSender('your@address.tld');
+
+Setting the ``Return-Path:`` (Bounce) Address
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Return-Path:`` address specifies where bounce notifications should
+be sent and is set with the ``setReturnPath()`` method of the message.
+
+You can only have one ``Return-Path:`` and it must not include
+a personal name.
+
+To set the ``Return-Path:`` address:
+
+* Call the ``setReturnPath()`` method on the Message.
+
+Bounce notifications will be sent to this address.
+
+.. code-block:: php
+
+ $message->setReturnPath('bounces@address.tld');
+
+
+Signed/Encrypted Message
+------------------------
+
+To increase the integrity/security of a message it is possible to sign and/or
+encrypt an message using one or multiple signers.
+
+S/MIME
+~~~~~~
+
+S/MIME can sign and/or encrypt a message using the OpenSSL extension.
+
+When signing a message, the signer creates a signature of the entire content of the message (including attachments).
+
+The certificate and private key must be PEM encoded, and can be either created using for example OpenSSL or
+obtained at an official Certificate Authority (CA).
+
+**The recipient must have the CA certificate in the list of trusted issuers in order to verify the signature.**
+
+**Make sure the certificate supports emailProtection.**
+
+When using OpenSSL this can done by the including the *-addtrust emailProtection* parameter when creating the certificate.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $smimeSigner = Swift_Signers_SMimeSigner::newInstance();
+ $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem');
+ $message->attachSigner($smimeSigner);
+
+When the private key is secured using a passphrase use the following instead.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $smimeSigner = Swift_Signers_SMimeSigner::newInstance();
+ $smimeSigner->setSignCertificate('/path/to/certificate.pem', array('/path/to/private-key.pem', 'passphrase'));
+ $message->attachSigner($smimeSigner);
+
+By default the signature is added as attachment,
+making the message still readable for mailing agents not supporting signed messages.
+
+Storing the message as binary is also possible but not recommended.
+
+.. code-block:: php
+
+ $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem', PKCS7_BINARY);
+
+When encrypting the message (also known as enveloping), the entire message (including attachments)
+is encrypted using a certificate, and the recipient can then decrypt the message using corresponding private key.
+
+Encrypting ensures nobody can read the contents of the message without the private key.
+
+Normally the recipient provides a certificate for encrypting and keeping the decryption key private.
+
+Using both signing and encrypting is also possible.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance();
+
+ $smimeSigner = Swift_Signers_SMimeSigner::newInstance();
+ $smimeSigner->setSignCertificate('/path/to/sign-certificate.pem', '/path/to/private-key.pem');
+ $smimeSigner->setEncryptCertificate('/path/to/encrypt-certificate.pem');
+ $message->attachSigner($smimeSigner);
+
+The used encryption cipher can be set as the second parameter of setEncryptCertificate()
+
+See http://php.net/manual/openssl.ciphers for a list of supported ciphers.
+
+By default the message is first signed and then encrypted, this can be changed by adding.
+
+.. code-block:: php
+
+ $smimeSigner->setSignThenEncrypt(false);
+
+**Changing this is not recommended as most mail agents don't support this none-standard way.**
+
+Only when having trouble with sign then encrypt method, this should be changed.
+
+Requesting a Read Receipt
+-------------------------
+
+It is possible to request a read-receipt to be sent to an address when the
+email is opened. To request a read receipt set the address with
+``setReadReceiptTo()``.
+
+To request a read receipt:
+
+* Set the address you want the receipt to be sent to with the
+ ``setReadReceiptTo()`` method on the Message.
+
+When the email is opened, if the mail client supports it a notification will be sent to this address.
+
+.. note::
+
+ Read receipts won't work for the majority of recipients since many mail
+ clients auto-disable them. Those clients that will send a read receipt
+ will make the user aware that one has been requested.
+
+ .. code-block:: php
+
+ $message->setReadReceiptTo('your@address.tld');
+
+Setting the Character Set
+-------------------------
+
+The character set of the message (and it's MIME parts) is set with the
+``setCharset()`` method. You can also change the global default of UTF-8 by
+working with the ``Swift_Preferences`` class.
+
+Swift Mailer will default to the UTF-8 character set unless otherwise
+overridden. UTF-8 will work in most instances since it includes all of the
+standard US keyboard characters in addition to most international characters.
+
+It is absolutely vital however that you know what character set your message
+(or it's MIME parts) are written in otherwise your message may be received
+completely garbled.
+
+There are two places in Swift Mailer where you can change the character set:
+
+* In the ``Swift_Preferences`` class
+
+* On each individual message and/or MIME part
+
+To set the character set of your Message:
+
+* Change the global UTF-8 setting by calling
+ ``Swift_Preferences::setCharset()``; or
+
+* Call the ``setCharset()`` method on the message or the MIME part.
+
+ .. code-block:: php
+
+ // Approach 1: Change the global setting (suggested)
+ Swift_Preferences::getInstance()->setCharset('iso-8859-2');
+
+ // Approach 2: Call the setCharset() method of the message
+ $message = Swift_Message::newInstance()
+ ->setCharset('iso-8859-2');
+
+ // Approach 3: Specify the charset when setting the body
+ $message->setBody('My body', 'text/html', 'iso-8859-2');
+
+ // Approach 4: Specify the charset for each part added
+ $message->addPart('My part', 'text/plain', 'iso-8859-2');
+
+Setting the Line Length
+-----------------------
+
+The length of lines in a message can be changed by using the ``setMaxLineLength()`` method on the message. It should be kept to less than
+1000 characters.
+
+Swift Mailer defaults to using 78 characters per line in a message. This is
+done for historical reasons and so that the message can be easily viewed in
+plain-text terminals.
+
+To change the maximum length of lines in your Message:
+
+* Call the ``setMaxLineLength()`` method on the Message.
+
+Lines that are longer than the line length specified will be wrapped between
+words.
+
+.. note::
+
+ You should never set a maximum length longer than 1000 characters
+ according to RFC 2822. Doing so could have unspecified side-effects such
+ as truncating parts of your message when it is transported between SMTP
+ servers.
+
+ .. code-block:: php
+
+ $message->setMaxLineLength(1000);
+
+Setting the Message Priority
+----------------------------
+
+You can change the priority of the message with ``setPriority()``. Setting the
+priority will not change the way your email is sent -- it is purely an
+indicative setting for the recipient.
+
+The priority of a message is an indication to the recipient what significance
+it has. Swift Mailer allows you to set the priority by calling the
+``setPriority`` method. This method takes an integer value between 1 and 5:
+
+* `Swift_Mime_SimpleMessage::PRIORITY_HIGHEST`: 1
+* `Swift_Mime_SimpleMessage::PRIORITY_HIGH`: 2
+* `Swift_Mime_SimpleMessage::PRIORITY_NORMAL`: 3
+* `Swift_Mime_SimpleMessage::PRIORITY_LOW`: 4
+* `Swift_Mime_SimpleMessage::PRIORITY_LOWEST`: 5
+
+To set the message priority:
+
+* Set the priority as an integer between 1 and 5 with the ``setPriority()``
+ method on the Message.
+
+.. code-block:: php
+
+ // Indicate "High" priority
+ $message->setPriority(2);
+
+ // Or use the constant to be more explicit
+ $message->setPriority(Swift_Mime_SimpleMessage::PRIORITY_HIGH);
diff --git a/vendor/swiftmailer/swiftmailer/doc/overview.rst b/vendor/swiftmailer/swiftmailer/doc/overview.rst
new file mode 100644
index 0000000..ebfe008
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/overview.rst
@@ -0,0 +1,159 @@
+Library Overview
+================
+
+Most features (and more) of your every day mail client software are provided
+by Swift Mailer, using object-oriented PHP code as the interface.
+
+In this chapter we will take a short tour of the various components, which put
+together form the Swift Mailer library as a whole. You will learn key
+terminology used throughout the rest of this book and you will gain a little
+understanding of the classes you will work with as you integrate Swift Mailer
+into your application.
+
+This chapter is intended to prepare you for the information contained in the
+subsequent chapters of this book. You may choose to skip this chapter if you
+are fairly technically minded, though it is likely to save you some time in
+the long run if you at least read between the lines here.
+
+System Requirements
+-------------------
+
+The basic requirements to operate Swift Mailer are extremely minimal and
+easily achieved. Historically, Swift Mailer has supported both PHP 4 and PHP 5
+by following a parallel development workflow. Now in it's fourth major
+version, and Swift Mailer operates on servers running PHP 5.3.3 or higher.
+
+The library aims to work with as many PHP 5 projects as possible:
+
+* PHP 5.3.3 or higher, with the SPL extension (standard)
+
+* Limited network access to connect to remote SMTP servers
+
+* 8 MB or more memory limit (Swift Mailer uses around 2 MB)
+
+Component Breakdown
+-------------------
+
+Swift Mailer is made up of many classes. Each of these classes can be grouped
+into a general "component" group which describes the task it is designed to
+perform.
+
+We'll take a brief look at the components which form Swift Mailer in this
+section of the book.
+
+The Mailer
+~~~~~~~~~~
+
+The mailer class, ``Swift_Mailer`` is the central class in the library where
+all of the other components meet one another. ``Swift_Mailer`` acts as a sort
+of message dispatcher, communicating with the underlying Transport to deliver
+your Message to all intended recipients.
+
+If you were to dig around in the source code for Swift Mailer you'd notice
+that ``Swift_Mailer`` itself is pretty bare. It delegates to other objects for
+most tasks and in theory, if you knew the internals of Swift Mailer well you
+could by-pass this class entirely. We wouldn't advise doing such a thing
+however -- there are reasons this class exists:
+
+* for consistency, regardless of the Transport used
+
+* to provide abstraction from the internals in the event internal API changes
+ are made
+
+* to provide convenience wrappers around aspects of the internal API
+
+An instance of ``Swift_Mailer`` is created by the developer before sending any
+Messages.
+
+Transports
+~~~~~~~~~~
+
+Transports are the classes in Swift Mailer that are responsible for
+communicating with a service in order to deliver a Message. There are several
+types of Transport in Swift Mailer, all of which implement the Swift_Transport
+interface and offer underlying start(), stop() and send() methods.
+
+Typically you will not need to know how a Transport works under-the-surface,
+you will only need to know how to create an instance of one, and which one to
+use for your environment.
+
++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+| Class | Features | Pros/cons |
++=================================+=============================================================================================+===============================================================================================================================================+
+| ``Swift_SmtpTransport`` | Sends messages over SMTP; Supports Authentication; Supports Encryption | Very portable; Pleasingly predictable results; Provides good feedback |
++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+| ``Swift_SendmailTransport`` | Communicates with a locally installed ``sendmail`` executable (Linux/UNIX) | Quick time-to-run; Provides less-accurate feedback than SMTP; Requires ``sendmail`` installation |
++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+| ``Swift_LoadBalancedTransport`` | Cycles through a collection of the other Transports to manage load-reduction | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down); Keeps the load on remote services down by spreading the work |
++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+| ``Swift_FailoverTransport`` | Works in conjunction with a collection of the other Transports to provide high-availability | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down) |
++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+
+MIME Entities
+~~~~~~~~~~~~~
+
+Everything that forms part of a Message is called a MIME Entity. All MIME
+entities in Swift Mailer share a common set of features. There are various
+types of MIME entity that serve different purposes such as Attachments and
+MIME parts.
+
+An e-mail message is made up of several relatively simple entities that are
+combined in different ways to achieve different results. All of these entities
+have the same fundamental outline but serve a different purpose. The Message
+itself can be defined as a MIME entity, an Attachment is a MIME entity, all
+MIME parts are MIME entities -- and so on!
+
+The basic units of each MIME entity -- be it the Message itself, or an
+Attachment -- are its Headers and its body:
+
+.. code-block:: text
+
+ Other-Header: Another value
+
+ The body content itself
+
+The Headers of a MIME entity, and its body must conform to some strict
+standards defined by various RFC documents. Swift Mailer ensures that these
+specifications are followed by using various types of object, including
+Encoders and different Header types to generate the entity.
+
+Each MIME component implements the base ``Swift_Mime_MimeEntity`` interface,
+which offers methods for retrieving Headers, adding new Headers, changing the
+Encoder, updating the body and so on!
+
+All MIME entities have one Header in common -- the Content-Type Header,
+updated with the entity's ``setContentType()`` method.
+
+Encoders
+~~~~~~~~
+
+Encoders are used to transform the content of Messages generated in Swift
+Mailer into a format that is safe to send across the internet and that
+conforms to RFC specifications.
+
+Generally speaking you will not need to interact with the Encoders in Swift
+Mailer -- the correct settings will be handled by the library itself.
+However they are probably worth a brief mention in the event that you do want
+to play with them.
+
+Both the Headers and the body of all MIME entities (including the Message
+itself) use Encoders to ensure the data they contain can be sent over the
+internet without becoming corrupted or misinterpreted.
+
+There are two types of Encoder: Base64 and Quoted-Printable.
+
+Plugins
+~~~~~~~
+
+Plugins exist to extend, or modify the behaviour of Swift Mailer. They respond
+to Events that are fired within the Transports during sending.
+
+There are a number of Plugins provided as part of the base Swift Mailer
+package and they all follow a common interface to respond to Events fired
+within the library. Interfaces are provided to "listen" to each type of Event
+fired and to act as desired when a listened-to Event occurs.
+
+Although several plugins are provided with Swift Mailer out-of-the-box, the
+Events system has been specifically designed to make it easy for experienced
+object-oriented developers to write their own plugins in order to achieve
+goals that may not be possible with the base library.
diff --git a/vendor/swiftmailer/swiftmailer/doc/plugins.rst b/vendor/swiftmailer/swiftmailer/doc/plugins.rst
new file mode 100644
index 0000000..6cec6be
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/plugins.rst
@@ -0,0 +1,385 @@
+Plugins
+=======
+
+Plugins are provided with Swift Mailer and can be used to extend the behavior
+of the library in situations where using simple class inheritance would be more complex.
+
+AntiFlood Plugin
+----------------
+
+Many SMTP servers have limits on the number of messages that may be sent
+during any single SMTP connection. The AntiFlood plugin provides a way to stay
+within this limit while still managing a large number of emails.
+
+A typical limit for a single connection is 100 emails. If the server you
+connect to imposes such a limit, it expects you to disconnect after that
+number of emails has been sent. You could manage this manually within a loop,
+but the AntiFlood plugin provides the necessary wrapper code so that you don't
+need to worry about this logic.
+
+Regardless of limits imposed by the server, it's usually a good idea to be
+conservative with the resources of the SMTP server. Sending will become
+sluggish if the server is being over-used so using the AntiFlood plugin will
+not be a bad idea even if no limits exist.
+
+The AntiFlood plugin's logic is basically to disconnect and the immediately
+re-connect with the SMTP server every X number of emails sent, where X is a
+number you specify to the plugin.
+
+You can also specify a time period in seconds that Swift Mailer should pause
+for between the disconnect/re-connect process. It's a good idea to pause for a
+short time (say 30 seconds every 100 emails) simply to give the SMTP server a
+chance to process its queue and recover some resources.
+
+Using the AntiFlood Plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The AntiFlood Plugin -- like all plugins -- is added with the Mailer class's
+``registerPlugin()`` method. It takes two constructor parameters: the number of
+emails to pause after, and optionally the number of seconds to pause for.
+
+To use the AntiFlood plugin:
+
+* Create an instance of the Mailer using any Transport you choose.
+
+* Create an instance of the ``Swift_Plugins_AntiFloodPlugin`` class, passing
+ in one or two constructor parameters.
+
+* Register the plugin using the Mailer's ``registerPlugin()`` method.
+
+* Continue using Swift Mailer to send messages as normal.
+
+When Swift Mailer sends messages it will count the number of messages that
+have been sent since the last re-connect. Once the number hits your specified
+threshold it will disconnect and re-connect, optionally pausing for a
+specified amount of time.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Mailer using any Transport
+ $mailer = Swift_Mailer::newInstance(
+ Swift_SmtpTransport::newInstance('smtp.example.org', 25)
+ );
+
+ // Use AntiFlood to re-connect after 100 emails
+ $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100));
+
+ // And specify a time in seconds to pause for (30 secs)
+ $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100, 30));
+
+ // Continue sending as normal
+ for ($lotsOfRecipients as $recipient) {
+ ...
+
+ $mailer->send( ... );
+ }
+
+Throttler Plugin
+----------------
+
+If your SMTP server has restrictions in place to limit the rate at which you
+send emails, then your code will need to be aware of this rate-limiting. The
+Throttler plugin makes Swift Mailer run at a rate-limited speed.
+
+Many shared hosts don't open their SMTP servers as a free-for-all. Usually
+they have policies in place (probably to discourage spammers) that only allow
+you to send a fixed number of emails per-hour/day.
+
+The Throttler plugin supports two modes of rate-limiting and with each, you
+will need to do that math to figure out the values you want. The plugin can
+limit based on the number of emails per minute, or the number of
+bytes-transferred per-minute.
+
+Using the Throttler Plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Throttler Plugin -- like all plugins -- is added with the Mailer class'
+``registerPlugin()`` method. It has two required constructor parameters that
+tell it how to do its rate-limiting.
+
+To use the Throttler plugin:
+
+* Create an instance of the Mailer using any Transport you choose.
+
+* Create an instance of the ``Swift_Plugins_ThrottlerPlugin`` class, passing
+ the number of emails, or bytes you wish to limit by, along with the mode
+ you're using.
+
+* Register the plugin using the Mailer's ``registerPlugin()`` method.
+
+* Continue using Swift Mailer to send messages as normal.
+
+When Swift Mailer sends messages it will keep track of the rate at which sending
+messages is occurring. If it realises that sending is happening too fast, it
+will cause your program to ``sleep()`` for enough time to average out the rate.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Mailer using any Transport
+ $mailer = Swift_Mailer::newInstance(
+ Swift_SmtpTransport::newInstance('smtp.example.org', 25)
+ );
+
+ // Rate limit to 100 emails per-minute
+ $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin(
+ 100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE
+ ));
+
+ // Rate limit to 10MB per-minute
+ $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin(
+ 1024 * 1024 * 10, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE
+ ));
+
+ // Continue sending as normal
+ for ($lotsOfRecipients as $recipient) {
+ ...
+
+ $mailer->send( ... );
+ }
+
+Logger Plugin
+-------------
+
+The Logger plugins helps with debugging during the process of sending. It can
+help to identify why an SMTP server is rejecting addresses, or any other
+hard-to-find problems that may arise.
+
+The Logger plugin comes in two parts. There's the plugin itself, along with
+one of a number of possible Loggers that you may choose to use. For example,
+the logger may output messages directly in realtime, or it may capture
+messages in an array.
+
+One other notable feature is the way in which the Logger plugin changes
+Exception messages. If Exceptions are being thrown but the error message does
+not provide conclusive information as to the source of the problem (such as an
+ambiguous SMTP error) the Logger plugin includes the entire SMTP transcript in
+the error message so that debugging becomes a simpler task.
+
+There are a few available Loggers included with Swift Mailer, but writing your
+own implementation is incredibly simple and is achieved by creating a short
+class that implements the ``Swift_Plugins_Logger`` interface.
+
+* ``Swift_Plugins_Loggers_ArrayLogger``: Keeps a collection of log messages
+ inside an array. The array content can be cleared or dumped out to the
+ screen.
+
+* ``Swift_Plugins_Loggers_EchoLogger``: Prints output to the screen in
+ realtime. Handy for very rudimentary debug output.
+
+Using the Logger Plugin
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The Logger Plugin -- like all plugins -- is added with the Mailer class'
+``registerPlugin()`` method. It accepts an instance of ``Swift_Plugins_Logger``
+in its constructor.
+
+To use the Logger plugin:
+
+* Create an instance of the Mailer using any Transport you choose.
+
+* Create an instance of the a Logger implementation of
+ ``Swift_Plugins_Logger``.
+
+* Create an instance of the ``Swift_Plugins_LoggerPlugin`` class, passing the
+ created Logger instance to its constructor.
+
+* Register the plugin using the Mailer's ``registerPlugin()`` method.
+
+* Continue using Swift Mailer to send messages as normal.
+
+* Dump the contents of the log with the logger's ``dump()`` method.
+
+When Swift Mailer sends messages it will keep a log of all the interactions
+with the underlying Transport being used. Depending upon the Logger that has
+been used the behaviour will differ, but all implementations offer a way to
+get the contents of the log.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Mailer using any Transport
+ $mailer = Swift_Mailer::newInstance(
+ Swift_SmtpTransport::newInstance('smtp.example.org', 25)
+ );
+
+ // To use the ArrayLogger
+ $logger = new Swift_Plugins_Loggers_ArrayLogger();
+ $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
+
+ // Or to use the Echo Logger
+ $logger = new Swift_Plugins_Loggers_EchoLogger();
+ $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
+
+ // Continue sending as normal
+ for ($lotsOfRecipients as $recipient) {
+ ...
+
+ $mailer->send( ... );
+ }
+
+ // Dump the log contents
+ // NOTE: The EchoLogger dumps in realtime so dump() does nothing for it
+ echo $logger->dump();
+
+Decorator Plugin
+----------------
+
+Often there's a need to send the same message to multiple recipients, but with
+tiny variations such as the recipient's name being used inside the message
+body. The Decorator plugin aims to provide a solution for allowing these small
+differences.
+
+The decorator plugin works by intercepting the sending process of Swift
+Mailer, reading the email address in the To: field and then looking up a set
+of replacements for a template.
+
+While the use of this plugin is simple, it is probably the most commonly
+misunderstood plugin due to the way in which it works. The typical mistake
+users make is to try registering the plugin multiple times (once for each
+recipient) -- inside a loop for example. This is incorrect.
+
+The Decorator plugin should be registered just once, but containing the list
+of all recipients prior to sending. It will use this list of recipients to
+find the required replacements during sending.
+
+Using the Decorator Plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use the Decorator plugin, simply create an associative array of replacements
+based on email addresses and then use the mailer's ``registerPlugin()`` method
+to add the plugin.
+
+First create an associative array of replacements based on the email addresses
+you'll be sending the message to.
+
+.. note::
+
+ The replacements array becomes a 2-dimensional array whose keys are the
+ email addresses and whose values are an associative array of replacements
+ for that email address. The curly braces used in this example can be any
+ type of syntax you choose, provided they match the placeholders in your
+ email template.
+
+ .. code-block:: php
+
+ $replacements = array();
+ foreach ($users as $user) {
+ $replacements[$user['email']] = array(
+ '{username}'=>$user['username'],
+ '{password}'=>$user['password']
+ );
+ }
+
+Now create an instance of the Decorator plugin using this array of replacements
+and then register it with the Mailer. Do this only once!
+
+.. code-block:: php
+
+ $decorator = new Swift_Plugins_DecoratorPlugin($replacements);
+
+ $mailer->registerPlugin($decorator);
+
+When you create your message, replace elements in the body (and/or the subject
+line) with your placeholders.
+
+.. code-block:: php
+
+ $message = Swift_Message::newInstance()
+ ->setSubject('Important notice for {username}')
+ ->setBody(
+ "Hello {username}, we have reset your password to {password}\n" .
+ "Please log in and change it at your earliest convenience."
+ )
+ ;
+
+ foreach ($users as $user) {
+ $message->addTo($user['email']);
+ }
+
+When you send this message to each of your recipients listed in your
+``$replacements`` array they will receive a message customized for just
+themselves. For example, the message used above when received may appear like
+this to one user:
+
+.. code-block:: text
+
+ Subject: Important notice for smilingsunshine2009
+
+ Hello smilingsunshine2009, we have reset your password to rainyDays
+ Please log in and change it at your earliest convenience.
+
+While another use may receive the message as:
+
+.. code-block:: text
+
+ Subject: Important notice for billy-bo-bob
+
+ Hello billy-bo-bob, we have reset your password to dancingOctopus
+ Please log in and change it at your earliest convenience.
+
+While the decorator plugin provides a means to solve this problem, there are
+various ways you could tackle this problem without the need for a plugin.
+We're trying to come up with a better way ourselves and while we have several
+(obvious) ideas we don't quite have the perfect solution to go ahead and
+implement it. Watch this space.
+
+Providing Your Own Replacements Lookup for the Decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Filling an array with replacements may not be the best solution for providing
+replacement information to the decorator. If you have a more elegant algorithm
+that performs replacement lookups on-the-fly you may provide your own
+implementation.
+
+Providing your own replacements lookup implementation for the Decorator is
+simply a matter of passing an instance of ``Swift_Plugins_Decorator_Replacements`` to the decorator plugin's constructor,
+rather than passing in an array.
+
+The Replacements interface is very simple to implement since it has just one
+method: ``getReplacementsFor($address)``.
+
+Imagine you want to look up replacements from a database on-the-fly, you might
+provide an implementation that does this. You need to create a small class.
+
+.. code-block:: php
+
+ class DbReplacements implements Swift_Plugins_Decorator_Replacements {
+ public function getReplacementsFor($address) {
+ global $db; // Your PDO instance with a connection to your database
+ $query = $db->prepare(
+ "SELECT * FROM `users` WHERE `email` = ?"
+ );
+
+ $query->execute([$address]);
+
+ if ($row = $query->fetch(PDO::FETCH_ASSOC)) {
+ return array(
+ '{username}'=>$row['username'],
+ '{password}'=>$row['password']
+ );
+ }
+ }
+ }
+
+Now all you need to do is pass an instance of your class into the Decorator
+plugin's constructor instead of passing an array.
+
+.. code-block:: php
+
+ $decorator = new Swift_Plugins_DecoratorPlugin(new DbReplacements());
+
+ $mailer->registerPlugin($decorator);
+
+For each message sent, the plugin will call your class' ``getReplacementsFor()``
+method to find the array of replacements it needs.
+
+.. note::
+
+ If your lookup algorithm is case sensitive, you should transform the
+ ``$address`` argument as appropriate -- for example by passing it
+ through ``strtolower()``.
diff --git a/vendor/swiftmailer/swiftmailer/doc/sending.rst b/vendor/swiftmailer/swiftmailer/doc/sending.rst
new file mode 100644
index 0000000..f340404
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/sending.rst
@@ -0,0 +1,571 @@
+Sending Messages
+================
+
+Quick Reference for Sending a Message
+-------------------------------------
+
+Sending a message is very straightforward. You create a Transport, use it to
+create the Mailer, then you use the Mailer to send the message.
+
+To send a Message:
+
+* Create a Transport from one of the provided Transports --
+ ``Swift_SmtpTransport``, ``Swift_SendmailTransport``
+ or one of the aggregate Transports.
+
+* Create an instance of the ``Swift_Mailer`` class, using the Transport as
+ it's constructor parameter.
+
+* Create a Message.
+
+* Send the message via the ``send()`` method on the Mailer object.
+
+.. caution::
+
+ The ``Swift_SmtpTransport`` and ``Swift_SendmailTransport`` transports use
+ ``proc_*`` PHP functions, which might not be available on your PHP
+ installation. You can easily check if that's the case by running the
+ following PHP script: ``<?php echo function_exists('proc_open') ? "Yep,
+ that will work" : "Sorry, that won't work";``
+
+When using ``send()`` the message will be sent just like it would be sent if you
+used your mail client. An integer is returned which includes the number of
+successful recipients. If none of the recipients could be sent to then zero will
+be returned, which equates to a boolean ``false``. If you set two ``To:``
+recipients and three ``Bcc:`` recipients in the message and all of the
+recipients are delivered to successfully then the value 5 will be returned.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25)
+ ->setUsername('your username')
+ ->setPassword('your password')
+ ;
+
+ /*
+ You could alternatively use a different transport such as Sendmail:
+
+ // Sendmail
+ $transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs');
+ */
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ // Create a message
+ $message = Swift_Message::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself')
+ ;
+
+ // Send the message
+ $result = $mailer->send($message);
+
+Transport Types
+~~~~~~~~~~~~~~~
+
+A Transport is the component which actually does the sending. You need to
+provide a Transport object to the Mailer class and there are several possible
+options.
+
+Typically you will not need to know how a Transport works under-the-surface,
+you will only need to know how to create an instance of one, and which one to
+use for your environment.
+
+The SMTP Transport
+..................
+
+The SMTP Transport sends messages over the (standardized) Simple Message
+Transfer Protocol. It can deal with encryption and authentication.
+
+The SMTP Transport, ``Swift_SmtpTransport`` is without doubt the most commonly
+used Transport because it will work on 99% of web servers (I just made that
+number up, but you get the idea). All the server needs is the ability to
+connect to a remote (or even local) SMTP server on the correct port number
+(usually 25).
+
+SMTP servers often require users to authenticate with a username and password
+before any mail can be sent to other domains. This is easily achieved using
+Swift Mailer with the SMTP Transport.
+
+SMTP is a protocol -- in other words it's a "way" of communicating a job
+to be done (i.e. sending a message). The SMTP protocol is the fundamental
+basis on which messages are delivered all over the internet 7 days a week, 365
+days a year. For this reason it's the most "direct" method of sending messages
+you can use and it's the one that will give you the most power and feedback
+(such as delivery failures) when using Swift Mailer.
+
+Because SMTP is generally run as a remote service (i.e. you connect to it over
+the network/internet) it's extremely portable from server-to-server. You can
+easily store the SMTP server address and port number in a configuration file
+within your application and adjust the settings accordingly if the code is
+moved or if the SMTP server is changed.
+
+Some SMTP servers -- Google for example -- use encryption for security reasons.
+Swift Mailer supports using both SSL and TLS encryption settings.
+
+Using the SMTP Transport
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The SMTP Transport is easy to use. Most configuration options can be set with
+the constructor.
+
+To use the SMTP Transport you need to know which SMTP server your code needs
+to connect to. Ask your web host if you're not sure. Lots of people ask me who
+to connect to -- I really can't answer that since it's a setting that's
+extremely specific to your hosting environment.
+
+To use the SMTP Transport:
+
+* Call ``Swift_SmtpTransport::newInstance()`` with the SMTP server name and
+ optionally with a port number (defaults to 25).
+
+* Use the returned object to create the Mailer.
+
+A connection to the SMTP server will be established upon the first call to
+``send()``.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25);
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ /*
+ It's also possible to use multiple method calls
+
+ $transport = Swift_SmtpTransport::newInstance()
+ ->setHost('smtp.example.org')
+ ->setPort(25)
+ ;
+ */
+
+Encrypted SMTP
+^^^^^^^^^^^^^^
+
+You can use SSL or TLS encryption with the SMTP Transport by specifying it as
+a parameter or with a method call.
+
+To use encryption with the SMTP Transport:
+
+* Pass the encryption setting as a third parameter to
+ ``Swift_SmtpTransport::newInstance()``; or
+
+* Call the ``setEncryption()`` method on the Transport.
+
+A connection to the SMTP server will be established upon the first call to
+``send()``. The connection will be initiated with the correct encryption
+settings.
+
+.. note::
+
+ For SSL or TLS encryption to work your PHP installation must have
+ appropriate OpenSSL transports wrappers. You can check if "tls" and/or
+ "ssl" are present in your PHP installation by using the PHP function
+ ``stream_get_transports()``
+
+ .. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 587, 'ssl');
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ /*
+ It's also possible to use multiple method calls
+
+ $transport = Swift_SmtpTransport::newInstance()
+ ->setHost('smtp.example.org')
+ ->setPort(587)
+ ->setEncryption('ssl')
+ ;
+ */
+
+SMTP with a Username and Password
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some servers require authentication. You can provide a username and password
+with ``setUsername()`` and ``setPassword()`` methods.
+
+To use a username and password with the SMTP Transport:
+
+* Create the Transport with ``Swift_SmtpTransport::newInstance()``.
+
+* Call the ``setUsername()`` and ``setPassword()`` methods on the Transport.
+
+Your username and password will be used to authenticate upon first connect
+when ``send()`` are first used on the Mailer.
+
+If authentication fails, an Exception of type ``Swift_TransportException`` will
+be thrown.
+
+.. note::
+
+ If you need to know early whether or not authentication has failed and an
+ Exception is going to be thrown, call the ``start()`` method on the
+ created Transport.
+
+ .. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport the call setUsername() and setPassword()
+ $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25)
+ ->setUsername('username')
+ ->setPassword('password')
+ ;
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+The Sendmail Transport
+......................
+
+The Sendmail Transport sends messages by communicating with a locally
+installed MTA -- such as ``sendmail``.
+
+The Sendmail Transport, ``Swift_SendmailTransport`` does not directly connect to
+any remote services. It is designed for Linux servers that have ``sendmail``
+installed. The Transport starts a local ``sendmail`` process and sends messages
+to it. Usually the ``sendmail`` process will respond quickly as it spools your
+messages to disk before sending them.
+
+The Transport is named the Sendmail Transport for historical reasons
+(``sendmail`` was the "standard" UNIX tool for sending e-mail for years). It
+will send messages using other transfer agents such as Exim or Postfix despite
+its name, provided they have the relevant sendmail wrappers so that they can be
+started with the correct command-line flags.
+
+It's a common misconception that because the Sendmail Transport returns a
+result very quickly it must therefore deliver messages to recipients quickly
+-- this is not true. It's not slow by any means, but it's certainly not
+faster than SMTP when it comes to getting messages to the intended recipients.
+This is because sendmail itself sends the messages over SMTP once they have
+been quickly spooled to disk.
+
+The Sendmail Transport has the potential to be just as smart of the SMTP
+Transport when it comes to notifying Swift Mailer about which recipients were
+rejected, but in reality the majority of locally installed ``sendmail``
+instances are not configured well enough to provide any useful feedback. As such
+Swift Mailer may report successful deliveries where they did in fact fail before
+they even left your server.
+
+You can run the Sendmail Transport in two different modes specified by command
+line flags:
+
+* "``-bs``" runs in SMTP mode so theoretically it will act like the SMTP
+ Transport
+
+* "``-t``" runs in piped mode with no feedback, but theoretically faster,
+ though not advised
+
+You can think of the Sendmail Transport as a sort of asynchronous SMTP Transport
+-- though if you have problems with delivery failures you should try using the
+SMTP Transport instead. Swift Mailer isn't doing the work here, it's simply
+passing the work to somebody else (i.e. ``sendmail``).
+
+Using the Sendmail Transport
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To use the Sendmail Transport you simply need to call
+``Swift_SendmailTransport::newInstance()`` with the command as a parameter.
+
+To use the Sendmail Transport you need to know where ``sendmail`` or another MTA
+exists on the server. Swift Mailer uses a default value of
+``/usr/sbin/sendmail``, which should work on most systems.
+
+You specify the entire command as a parameter (i.e. including the command line
+flags). Swift Mailer supports operational modes of "``-bs``" (default) and
+"``-t``".
+
+.. note::
+
+ If you run sendmail in "``-t``" mode you will get no feedback as to whether
+ or not sending has succeeded. Use "``-bs``" unless you have a reason not to.
+
+To use the Sendmail Transport:
+
+* Call ``Swift_SendmailTransport::newInstance()`` with the command, including
+ the correct command line flags. The default is to use ``/usr/sbin/sendmail
+ -bs`` if this is not specified.
+
+* Use the returned object to create the Mailer.
+
+A sendmail process will be started upon the first call to ``send()``. If the
+process cannot be started successfully an Exception of type
+``Swift_TransportException`` will be thrown.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SendmailTransport::newInstance('/usr/sbin/exim -bs');
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+The Mail Transport
+..................
+
+The Mail Transport sends messages by delegating to PHP's internal
+``mail()`` function.
+
+In my experience -- and others' -- the ``mail()`` function is not particularly
+predictable, or helpful.
+
+Quite notably, the ``mail()`` function behaves entirely differently between
+Linux and Windows servers. On linux it uses ``sendmail``, but on Windows it uses
+SMTP.
+
+In order for the ``mail()`` function to even work at all ``php.ini`` needs to be
+configured correctly, specifying the location of sendmail or of an SMTP server.
+
+The problem with ``mail()`` is that it "tries" to simplify things to the point
+that it actually makes things more complex due to poor interface design. The
+developers of Swift Mailer have gone to a lot of effort to make the Mail
+Transport work with a reasonable degree of consistency.
+
+Serious drawbacks when using this Transport are:
+
+* Unpredictable message headers
+
+* Lack of feedback regarding delivery failures
+
+* Lack of support for several plugins that require real-time delivery feedback
+
+It's a last resort, and we say that with a passion!
+
+Available Methods for Sending Messages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Mailer class offers two methods for sending Messages -- ``send()``.
+Each behaves in a slightly different way.
+
+When a message is sent in Swift Mailer, the Mailer class communicates with
+whichever Transport class you have chosen to use.
+
+Each recipient in the message should either be accepted or rejected by the
+Transport. For example, if the domain name on the email address is not
+reachable the SMTP Transport may reject the address because it cannot process
+it. Whichever method you use -- ``send()`` -- Swift Mailer will return
+an integer indicating the number of accepted recipients.
+
+.. note::
+
+ It's possible to find out which recipients were rejected -- we'll cover that
+ later in this chapter.
+
+Using the ``send()`` Method
+...........................
+
+The ``send()`` method of the ``Swift_Mailer`` class sends a message using
+exactly the same logic as your Desktop mail client would use. Just pass it a
+Message and get a result.
+
+To send a Message with ``send()``:
+
+* Create a Transport from one of the provided Transports --
+ ``Swift_SmtpTransport``, ``Swift_SendmailTransport``,
+ or one of the aggregate Transports.
+
+* Create an instance of the ``Swift_Mailer`` class, using the Transport as
+ it's constructor parameter.
+
+* Create a Message.
+
+* Send the message via the ``send()`` method on the Mailer object.
+
+The message will be sent just like it would be sent if you used your mail
+client. An integer is returned which includes the number of successful
+recipients. If none of the recipients could be sent to then zero will be
+returned, which equates to a boolean ``false``. If you set two
+``To:`` recipients and three ``Bcc:`` recipients in the message and all of the
+recipients are delivered to successfully then the value 5 will be returned.
+
+.. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SmtpTransport::newInstance('localhost', 25);
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ // Create a message
+ $message = Swift_Message::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself')
+ ;
+
+ // Send the message
+ $numSent = $mailer->send($message);
+
+ printf("Sent %d messages\n", $numSent);
+
+ /* Note that often that only the boolean equivalent of the
+ return value is of concern (zero indicates FALSE)
+
+ if ($mailer->send($message))
+ {
+ echo "Sent\n";
+ }
+ else
+ {
+ echo "Failed\n";
+ }
+
+ */
+
+Sending Emails in Batch
+.......................
+
+If you want to send a separate message to each recipient so that only their
+own address shows up in the ``To:`` field, follow the following recipe:
+
+* Create a Transport from one of the provided Transports --
+ ``Swift_SmtpTransport``, ``Swift_SendmailTransport``,
+ or one of the aggregate Transports.
+
+* Create an instance of the ``Swift_Mailer`` class, using the Transport as
+ it's constructor parameter.
+
+* Create a Message.
+
+* Iterate over the recipients and send message via the ``send()`` method on
+ the Mailer object.
+
+Each recipient of the messages receives a different copy with only their own
+email address on the ``To:`` field.
+
+Make sure to add only valid email addresses as recipients. If you try to add an
+invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift
+Mailer will throw a ``Swift_RfcComplianceException``.
+
+If you add recipients automatically based on a data source that may contain
+invalid email addresses, you can prevent possible exceptions by validating the
+addresses using ``Swift_Validate::email($email)`` and only adding addresses
+that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and
+``setBcc()`` calls in a try-catch block and handle the
+``Swift_RfcComplianceException`` in the catch block.
+
+Handling invalid addresses properly is especially important when sending emails
+in large batches since a single invalid address might cause an unhandled
+exception and stop the execution or your script early.
+
+.. note::
+
+ In the following example, two emails are sent. One to each of
+ ``receiver@domain.org`` and ``other@domain.org``. These recipients will
+ not be aware of each other.
+
+ .. code-block:: php
+
+ require_once 'lib/swift_required.php';
+
+ // Create the Transport
+ $transport = Swift_SmtpTransport::newInstance('localhost', 25);
+
+ // Create the Mailer using your created Transport
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ // Create a message
+ $message = Swift_Message::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setBody('Here is the message itself')
+ ;
+
+ // Send the message
+ $failedRecipients = array();
+ $numSent = 0;
+ $to = array('receiver@domain.org', 'other@domain.org' => 'A name');
+
+ foreach ($to as $address => $name)
+ {
+ if (is_int($address)) {
+ $message->setTo($name);
+ } else {
+ $message->setTo(array($address => $name));
+ }
+
+ $numSent += $mailer->send($message, $failedRecipients);
+ }
+
+ printf("Sent %d messages\n", $numSent);
+
+Finding out Rejected Addresses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's possible to get a list of addresses that were rejected by the Transport
+by using a by-reference parameter to ``send()``.
+
+As Swift Mailer attempts to send the message to each address given to it, if a
+recipient is rejected it will be added to the array. You can pass an existing
+array, otherwise one will be created by-reference.
+
+Collecting the list of recipients that were rejected can be useful in
+circumstances where you need to "prune" a mailing list for example when some
+addresses cannot be delivered to.
+
+Getting Failures By-reference
+.............................
+
+Collecting delivery failures by-reference with the ``send()`` method is as
+simple as passing a variable name to the method call.
+
+To get failed recipients by-reference:
+
+* Pass a by-reference variable name to the ``send()`` method of the Mailer
+ class.
+
+If the Transport rejects any of the recipients, the culprit addresses will be
+added to the array provided by-reference.
+
+.. note::
+
+ If the variable name does not yet exist, it will be initialized as an
+ empty array and then failures will be added to that array. If the variable
+ already exists it will be type-cast to an array and failures will be added
+ to it.
+
+ .. code-block:: php
+
+ $mailer = Swift_Mailer::newInstance( ... );
+
+ $message = Swift_Message::newInstance( ... )
+ ->setFrom( ... )
+ ->setTo(array(
+ 'receiver@bad-domain.org' => 'Receiver Name',
+ 'other@domain.org' => 'A name',
+ 'other-receiver@bad-domain.org' => 'Other Name'
+ ))
+ ->setBody( ... )
+ ;
+
+ // Pass a variable name to the send() method
+ if (!$mailer->send($message, $failures))
+ {
+ echo "Failures:";
+ print_r($failures);
+ }
+
+ /*
+ Failures:
+ Array (
+ 0 => receiver@bad-domain.org,
+ 1 => other-receiver@bad-domain.org
+ )
+ */
diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle
new file mode 100644
index 0000000..f895752
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle
Binary files differ
diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle
new file mode 100644
index 0000000..e1e33cb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle
Binary files differ
diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle
new file mode 100644
index 0000000..5670e2b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle
Binary files differ
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
new file mode 100644
index 0000000..82c381b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * General utility class in Swift Mailer, not to be instantiated.
+ *
+ *
+ * @author Chris Corbyn
+ */
+abstract class Swift
+{
+ /** Swift Mailer Version number generated during dist release process */
+ const VERSION = '@SWIFT_VERSION_NUMBER@';
+
+ public static $initialized = false;
+ public static $inits = array();
+
+ /**
+ * Registers an initializer callable that will be called the first time
+ * a SwiftMailer class is autoloaded.
+ *
+ * This enables you to tweak the default configuration in a lazy way.
+ *
+ * @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
+ */
+ public static function init($callable)
+ {
+ self::$inits[] = $callable;
+ }
+
+ /**
+ * Internal autoloader for spl_autoload_register().
+ *
+ * @param string $class
+ */
+ public static function autoload($class)
+ {
+ // Don't interfere with other autoloaders
+ if (0 !== strpos($class, 'Swift_')) {
+ return;
+ }
+
+ $path = __DIR__.'/'.str_replace('_', '/', $class).'.php';
+
+ if (!file_exists($path)) {
+ return;
+ }
+
+ require $path;
+
+ if (self::$inits && !self::$initialized) {
+ self::$initialized = true;
+ foreach (self::$inits as $init) {
+ call_user_func($init);
+ }
+ }
+ }
+
+ /**
+ * Configure autoloading using Swift Mailer.
+ *
+ * This is designed to play nicely with other autoloaders.
+ *
+ * @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
+ */
+ public static function registerAutoload($callable = null)
+ {
+ if (null !== $callable) {
+ self::$inits[] = $callable;
+ }
+ spl_autoload_register(array('Swift', 'autoload'));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php
new file mode 100644
index 0000000..a95bccf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Attachment class for attaching files to a {@link Swift_Mime_Message}.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Attachment extends Swift_Mime_Attachment
+{
+ /**
+ * Create a new Attachment.
+ *
+ * Details may be optionally provided to the constructor.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ */
+ public function __construct($data = null, $filename = null, $contentType = null)
+ {
+ call_user_func_array(
+ array($this, 'Swift_Mime_Attachment::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('mime.attachment')
+ );
+
+ $this->setBody($data);
+ $this->setFilename($filename);
+ if ($contentType) {
+ $this->setContentType($contentType);
+ }
+ }
+
+ /**
+ * Create a new Attachment.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ *
+ * @return Swift_Mime_Attachment
+ */
+ public static function newInstance($data = null, $filename = null, $contentType = null)
+ {
+ return new self($data, $filename, $contentType);
+ }
+
+ /**
+ * Create a new Attachment from a filesystem path.
+ *
+ * @param string $path
+ * @param string $contentType optional
+ *
+ * @return Swift_Mime_Attachment
+ */
+ public static function fromPath($path, $contentType = null)
+ {
+ return self::newInstance()->setFile(
+ new Swift_ByteStream_FileByteStream($path),
+ $contentType
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php
new file mode 100644
index 0000000..a7b0e3a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php
@@ -0,0 +1,181 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides the base functionality for an InputStream supporting filters.
+ *
+ * @author Chris Corbyn
+ */
+abstract class Swift_ByteStream_AbstractFilterableInputStream implements Swift_InputByteStream, Swift_Filterable
+{
+ /**
+ * Write sequence.
+ */
+ protected $_sequence = 0;
+
+ /**
+ * StreamFilters.
+ *
+ * @var Swift_StreamFilter[]
+ */
+ private $_filters = array();
+
+ /**
+ * A buffer for writing.
+ */
+ private $_writeBuffer = '';
+
+ /**
+ * Bound streams.
+ *
+ * @var Swift_InputByteStream[]
+ */
+ private $_mirrors = array();
+
+ /**
+ * Commit the given bytes to the storage medium immediately.
+ *
+ * @param string $bytes
+ */
+ abstract protected function _commit($bytes);
+
+ /**
+ * Flush any buffers/content with immediate effect.
+ */
+ abstract protected function _flush();
+
+ /**
+ * Add a StreamFilter to this InputByteStream.
+ *
+ * @param Swift_StreamFilter $filter
+ * @param string $key
+ */
+ public function addFilter(Swift_StreamFilter $filter, $key)
+ {
+ $this->_filters[$key] = $filter;
+ }
+
+ /**
+ * Remove an already present StreamFilter based on its $key.
+ *
+ * @param string $key
+ */
+ public function removeFilter($key)
+ {
+ unset($this->_filters[$key]);
+ }
+
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * @param string $bytes
+ *
+ * @throws Swift_IoException
+ *
+ * @return int
+ */
+ public function write($bytes)
+ {
+ $this->_writeBuffer .= $bytes;
+ foreach ($this->_filters as $filter) {
+ if ($filter->shouldBuffer($this->_writeBuffer)) {
+ return;
+ }
+ }
+ $this->_doWrite($this->_writeBuffer);
+
+ return ++$this->_sequence;
+ }
+
+ /**
+ * For any bytes that are currently buffered inside the stream, force them
+ * off the buffer.
+ *
+ * @throws Swift_IoException
+ */
+ public function commit()
+ {
+ $this->_doWrite($this->_writeBuffer);
+ }
+
+ /**
+ * Attach $is to this stream.
+ *
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ $this->_mirrors[] = $is;
+ }
+
+ /**
+ * Remove an already bound stream.
+ *
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ foreach ($this->_mirrors as $k => $stream) {
+ if ($is === $stream) {
+ if ($this->_writeBuffer !== '') {
+ $stream->write($this->_writeBuffer);
+ }
+ unset($this->_mirrors[$k]);
+ }
+ }
+ }
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ *
+ * @throws Swift_IoException
+ */
+ public function flushBuffers()
+ {
+ if ($this->_writeBuffer !== '') {
+ $this->_doWrite($this->_writeBuffer);
+ }
+ $this->_flush();
+
+ foreach ($this->_mirrors as $stream) {
+ $stream->flushBuffers();
+ }
+ }
+
+ /** Run $bytes through all filters */
+ private function _filter($bytes)
+ {
+ foreach ($this->_filters as $filter) {
+ $bytes = $filter->filter($bytes);
+ }
+
+ return $bytes;
+ }
+
+ /** Just write the bytes to the stream */
+ private function _doWrite($bytes)
+ {
+ $this->_commit($this->_filter($bytes));
+
+ foreach ($this->_mirrors as $stream) {
+ $stream->write($bytes);
+ }
+
+ $this->_writeBuffer = '';
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php
new file mode 100644
index 0000000..ef05a6d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php
@@ -0,0 +1,182 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Allows reading and writing of bytes to and from an array.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_ByteStream_ArrayByteStream implements Swift_InputByteStream, Swift_OutputByteStream
+{
+ /**
+ * The internal stack of bytes.
+ *
+ * @var string[]
+ */
+ private $_array = array();
+
+ /**
+ * The size of the stack.
+ *
+ * @var int
+ */
+ private $_arraySize = 0;
+
+ /**
+ * The internal pointer offset.
+ *
+ * @var int
+ */
+ private $_offset = 0;
+
+ /**
+ * Bound streams.
+ *
+ * @var Swift_InputByteStream[]
+ */
+ private $_mirrors = array();
+
+ /**
+ * Create a new ArrayByteStream.
+ *
+ * If $stack is given the stream will be populated with the bytes it contains.
+ *
+ * @param mixed $stack of bytes in string or array form, optional
+ */
+ public function __construct($stack = null)
+ {
+ if (is_array($stack)) {
+ $this->_array = $stack;
+ $this->_arraySize = count($stack);
+ } elseif (is_string($stack)) {
+ $this->write($stack);
+ } else {
+ $this->_array = array();
+ }
+ }
+
+ /**
+ * Reads $length bytes from the stream into a string and moves the pointer
+ * through the stream by $length.
+ *
+ * If less bytes exist than are requested the
+ * remaining bytes are given instead. If no bytes are remaining at all, boolean
+ * false is returned.
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public function read($length)
+ {
+ if ($this->_offset == $this->_arraySize) {
+ return false;
+ }
+
+ // Don't use array slice
+ $end = $length + $this->_offset;
+ $end = $this->_arraySize < $end ? $this->_arraySize : $end;
+ $ret = '';
+ for (; $this->_offset < $end; ++$this->_offset) {
+ $ret .= $this->_array[$this->_offset];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * @param string $bytes
+ */
+ public function write($bytes)
+ {
+ $to_add = str_split($bytes);
+ foreach ($to_add as $value) {
+ $this->_array[] = $value;
+ }
+ $this->_arraySize = count($this->_array);
+
+ foreach ($this->_mirrors as $stream) {
+ $stream->write($bytes);
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function commit()
+ {
+ }
+
+ /**
+ * Attach $is to this stream.
+ *
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ $this->_mirrors[] = $is;
+ }
+
+ /**
+ * Remove an already bound stream.
+ *
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ foreach ($this->_mirrors as $k => $stream) {
+ if ($is === $stream) {
+ unset($this->_mirrors[$k]);
+ }
+ }
+ }
+
+ /**
+ * Move the internal read pointer to $byteOffset in the stream.
+ *
+ * @param int $byteOffset
+ *
+ * @return bool
+ */
+ public function setReadPointer($byteOffset)
+ {
+ if ($byteOffset > $this->_arraySize) {
+ $byteOffset = $this->_arraySize;
+ } elseif ($byteOffset < 0) {
+ $byteOffset = 0;
+ }
+
+ $this->_offset = $byteOffset;
+ }
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ */
+ public function flushBuffers()
+ {
+ $this->_offset = 0;
+ $this->_array = array();
+ $this->_arraySize = 0;
+
+ foreach ($this->_mirrors as $stream) {
+ $stream->flushBuffers();
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php
new file mode 100644
index 0000000..9ed8523
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php
@@ -0,0 +1,231 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Allows reading and writing of bytes to and from a file.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_FileStream
+{
+ /** The internal pointer offset */
+ private $_offset = 0;
+
+ /** The path to the file */
+ private $_path;
+
+ /** The mode this file is opened in for writing */
+ private $_mode;
+
+ /** A lazy-loaded resource handle for reading the file */
+ private $_reader;
+
+ /** A lazy-loaded resource handle for writing the file */
+ private $_writer;
+
+ /** If magic_quotes_runtime is on, this will be true */
+ private $_quotes = false;
+
+ /** If stream is seekable true/false, or null if not known */
+ private $_seekable = null;
+
+ /**
+ * Create a new FileByteStream for $path.
+ *
+ * @param string $path
+ * @param bool $writable if true
+ */
+ public function __construct($path, $writable = false)
+ {
+ if (empty($path)) {
+ throw new Swift_IoException('The path cannot be empty');
+ }
+ $this->_path = $path;
+ $this->_mode = $writable ? 'w+b' : 'rb';
+
+ if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
+ $this->_quotes = true;
+ }
+ }
+
+ /**
+ * Get the complete path to the file.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->_path;
+ }
+
+ /**
+ * Reads $length bytes from the stream into a string and moves the pointer
+ * through the stream by $length.
+ *
+ * If less bytes exist than are requested the
+ * remaining bytes are given instead. If no bytes are remaining at all, boolean
+ * false is returned.
+ *
+ * @param int $length
+ *
+ * @throws Swift_IoException
+ *
+ * @return string|bool
+ */
+ public function read($length)
+ {
+ $fp = $this->_getReadHandle();
+ if (!feof($fp)) {
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 0);
+ }
+ $bytes = fread($fp, $length);
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 1);
+ }
+ $this->_offset = ftell($fp);
+
+ // If we read one byte after reaching the end of the file
+ // feof() will return false and an empty string is returned
+ if ($bytes === '' && feof($fp)) {
+ $this->_resetReadHandle();
+
+ return false;
+ }
+
+ return $bytes;
+ }
+
+ $this->_resetReadHandle();
+
+ return false;
+ }
+
+ /**
+ * Move the internal read pointer to $byteOffset in the stream.
+ *
+ * @param int $byteOffset
+ *
+ * @return bool
+ */
+ public function setReadPointer($byteOffset)
+ {
+ if (isset($this->_reader)) {
+ $this->_seekReadStreamToPosition($byteOffset);
+ }
+ $this->_offset = $byteOffset;
+ }
+
+ /** Just write the bytes to the file */
+ protected function _commit($bytes)
+ {
+ fwrite($this->_getWriteHandle(), $bytes);
+ $this->_resetReadHandle();
+ }
+
+ /** Not used */
+ protected function _flush()
+ {
+ }
+
+ /** Get the resource for reading */
+ private function _getReadHandle()
+ {
+ if (!isset($this->_reader)) {
+ $pointer = @fopen($this->_path, 'rb');
+ if (!$pointer) {
+ throw new Swift_IoException(
+ 'Unable to open file for reading ['.$this->_path.']'
+ );
+ }
+ $this->_reader = $pointer;
+ if ($this->_offset != 0) {
+ $this->_getReadStreamSeekableStatus();
+ $this->_seekReadStreamToPosition($this->_offset);
+ }
+ }
+
+ return $this->_reader;
+ }
+
+ /** Get the resource for writing */
+ private function _getWriteHandle()
+ {
+ if (!isset($this->_writer)) {
+ if (!$this->_writer = fopen($this->_path, $this->_mode)) {
+ throw new Swift_IoException(
+ 'Unable to open file for writing ['.$this->_path.']'
+ );
+ }
+ }
+
+ return $this->_writer;
+ }
+
+ /** Force a reload of the resource for reading */
+ private function _resetReadHandle()
+ {
+ if (isset($this->_reader)) {
+ fclose($this->_reader);
+ $this->_reader = null;
+ }
+ }
+
+ /** Check if ReadOnly Stream is seekable */
+ private function _getReadStreamSeekableStatus()
+ {
+ $metas = stream_get_meta_data($this->_reader);
+ $this->_seekable = $metas['seekable'];
+ }
+
+ /** Streams in a readOnly stream ensuring copy if needed */
+ private function _seekReadStreamToPosition($offset)
+ {
+ if ($this->_seekable === null) {
+ $this->_getReadStreamSeekableStatus();
+ }
+ if ($this->_seekable === false) {
+ $currentPos = ftell($this->_reader);
+ if ($currentPos < $offset) {
+ $toDiscard = $offset - $currentPos;
+ fread($this->_reader, $toDiscard);
+
+ return;
+ }
+ $this->_copyReadStream();
+ }
+ fseek($this->_reader, $offset, SEEK_SET);
+ }
+
+ /** Copy a readOnly Stream to ensure seekability */
+ private function _copyReadStream()
+ {
+ if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) {
+ /* We have opened a php:// Stream Should work without problem */
+ } elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) {
+ /* We have opened a tmpfile */
+ } else {
+ throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available');
+ }
+ $currentPos = ftell($this->_reader);
+ fclose($this->_reader);
+ $source = fopen($this->_path, 'rb');
+ if (!$source) {
+ throw new Swift_IoException('Unable to open file for copying ['.$this->_path.']');
+ }
+ fseek($tmpFile, 0, SEEK_SET);
+ while (!feof($source)) {
+ fwrite($tmpFile, fread($source, 4096));
+ }
+ fseek($tmpFile, $currentPos, SEEK_SET);
+ fclose($source);
+ $this->_reader = $tmpFile;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php
new file mode 100644
index 0000000..1c9a80c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+* This file is part of SwiftMailer.
+* (c) 2004-2009 Chris Corbyn
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+/**
+ * @author Romain-Geissler
+ */
+class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
+{
+ public function __construct()
+ {
+ $filePath = tempnam(sys_get_temp_dir(), 'FileByteStream');
+
+ if ($filePath === false) {
+ throw new Swift_IoException('Failed to retrieve temporary file name.');
+ }
+
+ parent::__construct($filePath, true);
+ }
+
+ public function getContent()
+ {
+ if (($content = file_get_contents($this->getPath())) === false) {
+ throw new Swift_IoException('Failed to get temporary file content.');
+ }
+
+ return $content;
+ }
+
+ public function __destruct()
+ {
+ if (file_exists($this->getPath())) {
+ @unlink($this->getPath());
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php
new file mode 100644
index 0000000..4267adb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Analyzes characters for a specific character set.
+ *
+ * @author Chris Corbyn
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+interface Swift_CharacterReader
+{
+ const MAP_TYPE_INVALID = 0x01;
+ const MAP_TYPE_FIXED_LEN = 0x02;
+ const MAP_TYPE_POSITIONS = 0x03;
+
+ /**
+ * Returns the complete character map.
+ *
+ * @param string $string
+ * @param int $startOffset
+ * @param array $currentMap
+ * @param mixed $ignoredChars
+ *
+ * @return int
+ */
+ public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars);
+
+ /**
+ * Returns the mapType, see constants.
+ *
+ * @return int
+ */
+ public function getMapType();
+
+ /**
+ * Returns an integer which specifies how many more bytes to read.
+ *
+ * A positive integer indicates the number of more bytes to fetch before invoking
+ * this method again.
+ *
+ * A value of zero means this is already a valid character.
+ * A value of -1 means this cannot possibly be a valid character.
+ *
+ * @param int[] $bytes
+ * @param int $size
+ *
+ * @return int
+ */
+ public function validateByteSequence($bytes, $size);
+
+ /**
+ * Returns the number of bytes which should be read to start each character.
+ *
+ * For fixed width character sets this should be the number of octets-per-character.
+ * For multibyte character sets this will probably be 1.
+ *
+ * @return int
+ */
+ public function getInitialByteSize();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php
new file mode 100644
index 0000000..6a18e1d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides fixed-width byte sizes for reading fixed-width character sets.
+ *
+ * @author Chris Corbyn
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader
+{
+ /**
+ * The number of bytes in a single character.
+ *
+ * @var int
+ */
+ private $_width;
+
+ /**
+ * Creates a new GenericFixedWidthReader using $width bytes per character.
+ *
+ * @param int $width
+ */
+ public function __construct($width)
+ {
+ $this->_width = $width;
+ }
+
+ /**
+ * Returns the complete character map.
+ *
+ * @param string $string
+ * @param int $startOffset
+ * @param array $currentMap
+ * @param mixed $ignoredChars
+ *
+ * @return int
+ */
+ public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
+ {
+ $strlen = strlen($string);
+ // % and / are CPU intensive, so, maybe find a better way
+ $ignored = $strlen % $this->_width;
+ $ignoredChars = $ignored ? substr($string, -$ignored) : '';
+ $currentMap = $this->_width;
+
+ return ($strlen - $ignored) / $this->_width;
+ }
+
+ /**
+ * Returns the mapType.
+ *
+ * @return int
+ */
+ public function getMapType()
+ {
+ return self::MAP_TYPE_FIXED_LEN;
+ }
+
+ /**
+ * Returns an integer which specifies how many more bytes to read.
+ *
+ * A positive integer indicates the number of more bytes to fetch before invoking
+ * this method again.
+ *
+ * A value of zero means this is already a valid character.
+ * A value of -1 means this cannot possibly be a valid character.
+ *
+ * @param string $bytes
+ * @param int $size
+ *
+ * @return int
+ */
+ public function validateByteSequence($bytes, $size)
+ {
+ $needed = $this->_width - $size;
+
+ return $needed > -1 ? $needed : -1;
+ }
+
+ /**
+ * Returns the number of bytes which should be read to start each character.
+ *
+ * @return int
+ */
+ public function getInitialByteSize()
+ {
+ return $this->_width;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php
new file mode 100644
index 0000000..67da48f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php
@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Analyzes US-ASCII characters.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_CharacterReader_UsAsciiReader implements Swift_CharacterReader
+{
+ /**
+ * Returns the complete character map.
+ *
+ * @param string $string
+ * @param int $startOffset
+ * @param array $currentMap
+ * @param string $ignoredChars
+ *
+ * @return int
+ */
+ public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
+ {
+ $strlen = strlen($string);
+ $ignoredChars = '';
+ for ($i = 0; $i < $strlen; ++$i) {
+ if ($string[$i] > "\x07F") {
+ // Invalid char
+ $currentMap[$i + $startOffset] = $string[$i];
+ }
+ }
+
+ return $strlen;
+ }
+
+ /**
+ * Returns mapType.
+ *
+ * @return int mapType
+ */
+ public function getMapType()
+ {
+ return self::MAP_TYPE_INVALID;
+ }
+
+ /**
+ * Returns an integer which specifies how many more bytes to read.
+ *
+ * A positive integer indicates the number of more bytes to fetch before invoking
+ * this method again.
+ * A value of zero means this is already a valid character.
+ * A value of -1 means this cannot possibly be a valid character.
+ *
+ * @param string $bytes
+ * @param int $size
+ *
+ * @return int
+ */
+ public function validateByteSequence($bytes, $size)
+ {
+ $byte = reset($bytes);
+ if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the number of bytes which should be read to start each character.
+ *
+ * @return int
+ */
+ public function getInitialByteSize()
+ {
+ return 1;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php
new file mode 100644
index 0000000..22746bd
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php
@@ -0,0 +1,176 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Analyzes UTF-8 characters.
+ *
+ * @author Chris Corbyn
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader
+{
+ /** Pre-computed for optimization */
+ private static $length_map = array(
+ // N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x4N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x5N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x6N
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x7N
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x8N
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x9N
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xAN
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xBN
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xCN
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xDN
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xEN
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // 0xFN
+ );
+
+ private static $s_length_map = array(
+ "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
+ "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
+ "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
+ "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
+ "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
+ "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
+ "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
+ "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
+ "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
+ "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
+ "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
+ "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
+ "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
+ "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
+ "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
+ "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
+ "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
+ "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
+ "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
+ "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
+ "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
+ "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
+ "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
+ "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
+ "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
+ "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
+ "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
+ "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
+ "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
+ "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
+ "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
+ "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
+ );
+
+ /**
+ * Returns the complete character map.
+ *
+ * @param string $string
+ * @param int $startOffset
+ * @param array $currentMap
+ * @param mixed $ignoredChars
+ *
+ * @return int
+ */
+ public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
+ {
+ if (!isset($currentMap['i']) || !isset($currentMap['p'])) {
+ $currentMap['p'] = $currentMap['i'] = array();
+ }
+
+ $strlen = strlen($string);
+ $charPos = count($currentMap['p']);
+ $foundChars = 0;
+ $invalid = false;
+ for ($i = 0; $i < $strlen; ++$i) {
+ $char = $string[$i];
+ $size = self::$s_length_map[$char];
+ if ($size == 0) {
+ /* char is invalid, we must wait for a resync */
+ $invalid = true;
+ continue;
+ } else {
+ if ($invalid == true) {
+ /* We mark the chars as invalid and start a new char */
+ $currentMap['p'][$charPos + $foundChars] = $startOffset + $i;
+ $currentMap['i'][$charPos + $foundChars] = true;
+ ++$foundChars;
+ $invalid = false;
+ }
+ if (($i + $size) > $strlen) {
+ $ignoredChars = substr($string, $i);
+ break;
+ }
+ for ($j = 1; $j < $size; ++$j) {
+ $char = $string[$i + $j];
+ if ($char > "\x7F" && $char < "\xC0") {
+ // Valid - continue parsing
+ } else {
+ /* char is invalid, we must wait for a resync */
+ $invalid = true;
+ continue 2;
+ }
+ }
+ /* Ok we got a complete char here */
+ $currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size;
+ $i += $j - 1;
+ ++$foundChars;
+ }
+ }
+
+ return $foundChars;
+ }
+
+ /**
+ * Returns mapType.
+ *
+ * @return int mapType
+ */
+ public function getMapType()
+ {
+ return self::MAP_TYPE_POSITIONS;
+ }
+
+ /**
+ * Returns an integer which specifies how many more bytes to read.
+ *
+ * A positive integer indicates the number of more bytes to fetch before invoking
+ * this method again.
+ * A value of zero means this is already a valid character.
+ * A value of -1 means this cannot possibly be a valid character.
+ *
+ * @param string $bytes
+ * @param int $size
+ *
+ * @return int
+ */
+ public function validateByteSequence($bytes, $size)
+ {
+ if ($size < 1) {
+ return -1;
+ }
+ $needed = self::$length_map[$bytes[0]] - $size;
+
+ return $needed > -1 ? $needed : -1;
+ }
+
+ /**
+ * Returns the number of bytes which should be read to start each character.
+ *
+ * @return int
+ */
+ public function getInitialByteSize()
+ {
+ return 1;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php
new file mode 100644
index 0000000..15b6c69
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A factory for creating CharacterReaders.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_CharacterReaderFactory
+{
+ /**
+ * Returns a CharacterReader suitable for the charset applied.
+ *
+ * @param string $charset
+ *
+ * @return Swift_CharacterReader
+ */
+ public function getReaderFor($charset);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php
new file mode 100644
index 0000000..9171a0b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Standard factory for creating CharacterReaders.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_CharacterReaderFactory_SimpleCharacterReaderFactory implements Swift_CharacterReaderFactory
+{
+ /**
+ * A map of charset patterns to their implementation classes.
+ *
+ * @var array
+ */
+ private static $_map = array();
+
+ /**
+ * Factories which have already been loaded.
+ *
+ * @var Swift_CharacterReaderFactory[]
+ */
+ private static $_loaded = array();
+
+ /**
+ * Creates a new CharacterReaderFactory.
+ */
+ public function __construct()
+ {
+ $this->init();
+ }
+
+ public function __wakeup()
+ {
+ $this->init();
+ }
+
+ public function init()
+ {
+ if (count(self::$_map) > 0) {
+ return;
+ }
+
+ $prefix = 'Swift_CharacterReader_';
+
+ $singleByte = array(
+ 'class' => $prefix.'GenericFixedWidthReader',
+ 'constructor' => array(1),
+ );
+
+ $doubleByte = array(
+ 'class' => $prefix.'GenericFixedWidthReader',
+ 'constructor' => array(2),
+ );
+
+ $fourBytes = array(
+ 'class' => $prefix.'GenericFixedWidthReader',
+ 'constructor' => array(4),
+ );
+
+ // Utf-8
+ self::$_map['utf-?8'] = array(
+ 'class' => $prefix.'Utf8Reader',
+ 'constructor' => array(),
+ );
+
+ //7-8 bit charsets
+ self::$_map['(us-)?ascii'] = $singleByte;
+ self::$_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte;
+ self::$_map['windows-?125[0-9]'] = $singleByte;
+ self::$_map['cp-?[0-9]+'] = $singleByte;
+ self::$_map['ansi'] = $singleByte;
+ self::$_map['macintosh'] = $singleByte;
+ self::$_map['koi-?7'] = $singleByte;
+ self::$_map['koi-?8-?.+'] = $singleByte;
+ self::$_map['mik'] = $singleByte;
+ self::$_map['(cork|t1)'] = $singleByte;
+ self::$_map['v?iscii'] = $singleByte;
+
+ //16 bits
+ self::$_map['(ucs-?2|utf-?16)'] = $doubleByte;
+
+ //32 bits
+ self::$_map['(ucs-?4|utf-?32)'] = $fourBytes;
+
+ // Fallback
+ self::$_map['.*'] = $singleByte;
+ }
+
+ /**
+ * Returns a CharacterReader suitable for the charset applied.
+ *
+ * @param string $charset
+ *
+ * @return Swift_CharacterReader
+ */
+ public function getReaderFor($charset)
+ {
+ $charset = trim(strtolower($charset));
+ foreach (self::$_map as $pattern => $spec) {
+ $re = '/^'.$pattern.'$/D';
+ if (preg_match($re, $charset)) {
+ if (!array_key_exists($pattern, self::$_loaded)) {
+ $reflector = new ReflectionClass($spec['class']);
+ if ($reflector->getConstructor()) {
+ $reader = $reflector->newInstanceArgs($spec['constructor']);
+ } else {
+ $reader = $reflector->newInstance();
+ }
+ self::$_loaded[$pattern] = $reader;
+ }
+
+ return self::$_loaded[$pattern];
+ }
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php
new file mode 100644
index 0000000..717924f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An abstract means of reading and writing data in terms of characters as opposed
+ * to bytes.
+ *
+ * Classes implementing this interface may use a subsystem which requires less
+ * memory than working with large strings of data.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_CharacterStream
+{
+ /**
+ * Set the character set used in this CharacterStream.
+ *
+ * @param string $charset
+ */
+ public function setCharacterSet($charset);
+
+ /**
+ * Set the CharacterReaderFactory for multi charset support.
+ *
+ * @param Swift_CharacterReaderFactory $factory
+ */
+ public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory);
+
+ /**
+ * Overwrite this character stream using the byte sequence in the byte stream.
+ *
+ * @param Swift_OutputByteStream $os output stream to read from
+ */
+ public function importByteStream(Swift_OutputByteStream $os);
+
+ /**
+ * Import a string a bytes into this CharacterStream, overwriting any existing
+ * data in the stream.
+ *
+ * @param string $string
+ */
+ public function importString($string);
+
+ /**
+ * Read $length characters from the stream and move the internal pointer
+ * $length further into the stream.
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public function read($length);
+
+ /**
+ * Read $length characters from the stream and return a 1-dimensional array
+ * containing there octet values.
+ *
+ * @param int $length
+ *
+ * @return int[]
+ */
+ public function readBytes($length);
+
+ /**
+ * Write $chars to the end of the stream.
+ *
+ * @param string $chars
+ */
+ public function write($chars);
+
+ /**
+ * Move the internal pointer to $charOffset in the stream.
+ *
+ * @param int $charOffset
+ */
+ public function setPointer($charOffset);
+
+ /**
+ * Empty the stream and reset the internal pointer.
+ */
+ public function flushContents();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php
new file mode 100644
index 0000000..7213a40
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php
@@ -0,0 +1,293 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A CharacterStream implementation which stores characters in an internal array.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_CharacterStream_ArrayCharacterStream implements Swift_CharacterStream
+{
+ /** A map of byte values and their respective characters */
+ private static $_charMap;
+
+ /** A map of characters and their derivative byte values */
+ private static $_byteMap;
+
+ /** The char reader (lazy-loaded) for the current charset */
+ private $_charReader;
+
+ /** A factory for creating CharacterReader instances */
+ private $_charReaderFactory;
+
+ /** The character set this stream is using */
+ private $_charset;
+
+ /** Array of characters */
+ private $_array = array();
+
+ /** Size of the array of character */
+ private $_array_size = array();
+
+ /** The current character offset in the stream */
+ private $_offset = 0;
+
+ /**
+ * Create a new CharacterStream with the given $chars, if set.
+ *
+ * @param Swift_CharacterReaderFactory $factory for loading validators
+ * @param string $charset used in the stream
+ */
+ public function __construct(Swift_CharacterReaderFactory $factory, $charset)
+ {
+ self::_initializeMaps();
+ $this->setCharacterReaderFactory($factory);
+ $this->setCharacterSet($charset);
+ }
+
+ /**
+ * Set the character set used in this CharacterStream.
+ *
+ * @param string $charset
+ */
+ public function setCharacterSet($charset)
+ {
+ $this->_charset = $charset;
+ $this->_charReader = null;
+ }
+
+ /**
+ * Set the CharacterReaderFactory for multi charset support.
+ *
+ * @param Swift_CharacterReaderFactory $factory
+ */
+ public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
+ {
+ $this->_charReaderFactory = $factory;
+ }
+
+ /**
+ * Overwrite this character stream using the byte sequence in the byte stream.
+ *
+ * @param Swift_OutputByteStream $os output stream to read from
+ */
+ public function importByteStream(Swift_OutputByteStream $os)
+ {
+ if (!isset($this->_charReader)) {
+ $this->_charReader = $this->_charReaderFactory
+ ->getReaderFor($this->_charset);
+ }
+
+ $startLength = $this->_charReader->getInitialByteSize();
+ while (false !== $bytes = $os->read($startLength)) {
+ $c = array();
+ for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
+ $c[] = self::$_byteMap[$bytes[$i]];
+ }
+ $size = count($c);
+ $need = $this->_charReader
+ ->validateByteSequence($c, $size);
+ if ($need > 0 &&
+ false !== $bytes = $os->read($need)) {
+ for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
+ $c[] = self::$_byteMap[$bytes[$i]];
+ }
+ }
+ $this->_array[] = $c;
+ ++$this->_array_size;
+ }
+ }
+
+ /**
+ * Import a string a bytes into this CharacterStream, overwriting any existing
+ * data in the stream.
+ *
+ * @param string $string
+ */
+ public function importString($string)
+ {
+ $this->flushContents();
+ $this->write($string);
+ }
+
+ /**
+ * Read $length characters from the stream and move the internal pointer
+ * $length further into the stream.
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public function read($length)
+ {
+ if ($this->_offset == $this->_array_size) {
+ return false;
+ }
+
+ // Don't use array slice
+ $arrays = array();
+ $end = $length + $this->_offset;
+ for ($i = $this->_offset; $i < $end; ++$i) {
+ if (!isset($this->_array[$i])) {
+ break;
+ }
+ $arrays[] = $this->_array[$i];
+ }
+ $this->_offset += $i - $this->_offset; // Limit function calls
+ $chars = false;
+ foreach ($arrays as $array) {
+ $chars .= implode('', array_map('chr', $array));
+ }
+
+ return $chars;
+ }
+
+ /**
+ * Read $length characters from the stream and return a 1-dimensional array
+ * containing there octet values.
+ *
+ * @param int $length
+ *
+ * @return int[]
+ */
+ public function readBytes($length)
+ {
+ if ($this->_offset == $this->_array_size) {
+ return false;
+ }
+ $arrays = array();
+ $end = $length + $this->_offset;
+ for ($i = $this->_offset; $i < $end; ++$i) {
+ if (!isset($this->_array[$i])) {
+ break;
+ }
+ $arrays[] = $this->_array[$i];
+ }
+ $this->_offset += ($i - $this->_offset); // Limit function calls
+
+ return call_user_func_array('array_merge', $arrays);
+ }
+
+ /**
+ * Write $chars to the end of the stream.
+ *
+ * @param string $chars
+ */
+ public function write($chars)
+ {
+ if (!isset($this->_charReader)) {
+ $this->_charReader = $this->_charReaderFactory->getReaderFor(
+ $this->_charset);
+ }
+
+ $startLength = $this->_charReader->getInitialByteSize();
+
+ $fp = fopen('php://memory', 'w+b');
+ fwrite($fp, $chars);
+ unset($chars);
+ fseek($fp, 0, SEEK_SET);
+
+ $buffer = array(0);
+ $buf_pos = 1;
+ $buf_len = 1;
+ $has_datas = true;
+ do {
+ $bytes = array();
+ // Buffer Filing
+ if ($buf_len - $buf_pos < $startLength) {
+ $buf = array_splice($buffer, $buf_pos);
+ $new = $this->_reloadBuffer($fp, 100);
+ if ($new) {
+ $buffer = array_merge($buf, $new);
+ $buf_len = count($buffer);
+ $buf_pos = 0;
+ } else {
+ $has_datas = false;
+ }
+ }
+ if ($buf_len - $buf_pos > 0) {
+ $size = 0;
+ for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) {
+ ++$size;
+ $bytes[] = $buffer[$buf_pos++];
+ }
+ $need = $this->_charReader->validateByteSequence(
+ $bytes, $size);
+ if ($need > 0) {
+ if ($buf_len - $buf_pos < $need) {
+ $new = $this->_reloadBuffer($fp, $need);
+
+ if ($new) {
+ $buffer = array_merge($buffer, $new);
+ $buf_len = count($buffer);
+ }
+ }
+ for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) {
+ $bytes[] = $buffer[$buf_pos++];
+ }
+ }
+ $this->_array[] = $bytes;
+ ++$this->_array_size;
+ }
+ } while ($has_datas);
+
+ fclose($fp);
+ }
+
+ /**
+ * Move the internal pointer to $charOffset in the stream.
+ *
+ * @param int $charOffset
+ */
+ public function setPointer($charOffset)
+ {
+ if ($charOffset > $this->_array_size) {
+ $charOffset = $this->_array_size;
+ } elseif ($charOffset < 0) {
+ $charOffset = 0;
+ }
+ $this->_offset = $charOffset;
+ }
+
+ /**
+ * Empty the stream and reset the internal pointer.
+ */
+ public function flushContents()
+ {
+ $this->_offset = 0;
+ $this->_array = array();
+ $this->_array_size = 0;
+ }
+
+ private function _reloadBuffer($fp, $len)
+ {
+ if (!feof($fp) && ($bytes = fread($fp, $len)) !== false) {
+ $buf = array();
+ for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
+ $buf[] = self::$_byteMap[$bytes[$i]];
+ }
+
+ return $buf;
+ }
+
+ return false;
+ }
+
+ private static function _initializeMaps()
+ {
+ if (!isset(self::$_charMap)) {
+ self::$_charMap = array();
+ for ($byte = 0; $byte < 256; ++$byte) {
+ self::$_charMap[$byte] = chr($byte);
+ }
+ self::$_byteMap = array_flip(self::$_charMap);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php
new file mode 100644
index 0000000..58bd140
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php
@@ -0,0 +1,267 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A CharacterStream implementation which stores characters in an internal array.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream
+{
+ /**
+ * The char reader (lazy-loaded) for the current charset.
+ *
+ * @var Swift_CharacterReader
+ */
+ private $_charReader;
+
+ /**
+ * A factory for creating CharacterReader instances.
+ *
+ * @var Swift_CharacterReaderFactory
+ */
+ private $_charReaderFactory;
+
+ /**
+ * The character set this stream is using.
+ *
+ * @var string
+ */
+ private $_charset;
+
+ /**
+ * The data's stored as-is.
+ *
+ * @var string
+ */
+ private $_datas = '';
+
+ /**
+ * Number of bytes in the stream.
+ *
+ * @var int
+ */
+ private $_datasSize = 0;
+
+ /**
+ * Map.
+ *
+ * @var mixed
+ */
+ private $_map;
+
+ /**
+ * Map Type.
+ *
+ * @var int
+ */
+ private $_mapType = 0;
+
+ /**
+ * Number of characters in the stream.
+ *
+ * @var int
+ */
+ private $_charCount = 0;
+
+ /**
+ * Position in the stream.
+ *
+ * @var int
+ */
+ private $_currentPos = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param Swift_CharacterReaderFactory $factory
+ * @param string $charset
+ */
+ public function __construct(Swift_CharacterReaderFactory $factory, $charset)
+ {
+ $this->setCharacterReaderFactory($factory);
+ $this->setCharacterSet($charset);
+ }
+
+ /* -- Changing parameters of the stream -- */
+
+ /**
+ * Set the character set used in this CharacterStream.
+ *
+ * @param string $charset
+ */
+ public function setCharacterSet($charset)
+ {
+ $this->_charset = $charset;
+ $this->_charReader = null;
+ $this->_mapType = 0;
+ }
+
+ /**
+ * Set the CharacterReaderFactory for multi charset support.
+ *
+ * @param Swift_CharacterReaderFactory $factory
+ */
+ public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
+ {
+ $this->_charReaderFactory = $factory;
+ }
+
+ /**
+ * @see Swift_CharacterStream::flushContents()
+ */
+ public function flushContents()
+ {
+ $this->_datas = null;
+ $this->_map = null;
+ $this->_charCount = 0;
+ $this->_currentPos = 0;
+ $this->_datasSize = 0;
+ }
+
+ /**
+ * @see Swift_CharacterStream::importByteStream()
+ *
+ * @param Swift_OutputByteStream $os
+ */
+ public function importByteStream(Swift_OutputByteStream $os)
+ {
+ $this->flushContents();
+ $blocks = 512;
+ $os->setReadPointer(0);
+ while (false !== ($read = $os->read($blocks))) {
+ $this->write($read);
+ }
+ }
+
+ /**
+ * @see Swift_CharacterStream::importString()
+ *
+ * @param string $string
+ */
+ public function importString($string)
+ {
+ $this->flushContents();
+ $this->write($string);
+ }
+
+ /**
+ * @see Swift_CharacterStream::read()
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public function read($length)
+ {
+ if ($this->_currentPos >= $this->_charCount) {
+ return false;
+ }
+ $ret = false;
+ $length = $this->_currentPos + $length > $this->_charCount ? $this->_charCount - $this->_currentPos : $length;
+ switch ($this->_mapType) {
+ case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
+ $len = $length * $this->_map;
+ $ret = substr($this->_datas,
+ $this->_currentPos * $this->_map,
+ $len);
+ $this->_currentPos += $length;
+ break;
+
+ case Swift_CharacterReader::MAP_TYPE_INVALID:
+ $ret = '';
+ for (; $this->_currentPos < $length; ++$this->_currentPos) {
+ if (isset($this->_map[$this->_currentPos])) {
+ $ret .= '?';
+ } else {
+ $ret .= $this->_datas[$this->_currentPos];
+ }
+ }
+ break;
+
+ case Swift_CharacterReader::MAP_TYPE_POSITIONS:
+ $end = $this->_currentPos + $length;
+ $end = $end > $this->_charCount ? $this->_charCount : $end;
+ $ret = '';
+ $start = 0;
+ if ($this->_currentPos > 0) {
+ $start = $this->_map['p'][$this->_currentPos - 1];
+ }
+ $to = $start;
+ for (; $this->_currentPos < $end; ++$this->_currentPos) {
+ if (isset($this->_map['i'][$this->_currentPos])) {
+ $ret .= substr($this->_datas, $start, $to - $start).'?';
+ $start = $this->_map['p'][$this->_currentPos];
+ } else {
+ $to = $this->_map['p'][$this->_currentPos];
+ }
+ }
+ $ret .= substr($this->_datas, $start, $to - $start);
+ break;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @see Swift_CharacterStream::readBytes()
+ *
+ * @param int $length
+ *
+ * @return int[]
+ */
+ public function readBytes($length)
+ {
+ $read = $this->read($length);
+ if ($read !== false) {
+ $ret = array_map('ord', str_split($read, 1));
+
+ return $ret;
+ }
+
+ return false;
+ }
+
+ /**
+ * @see Swift_CharacterStream::setPointer()
+ *
+ * @param int $charOffset
+ */
+ public function setPointer($charOffset)
+ {
+ if ($this->_charCount < $charOffset) {
+ $charOffset = $this->_charCount;
+ }
+ $this->_currentPos = $charOffset;
+ }
+
+ /**
+ * @see Swift_CharacterStream::write()
+ *
+ * @param string $chars
+ */
+ public function write($chars)
+ {
+ if (!isset($this->_charReader)) {
+ $this->_charReader = $this->_charReaderFactory->getReaderFor(
+ $this->_charset);
+ $this->_map = array();
+ $this->_mapType = $this->_charReader->getMapType();
+ }
+ $ignored = '';
+ $this->_datas .= $chars;
+ $this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored);
+ if ($ignored !== false) {
+ $this->_datasSize = strlen($this->_datas) - strlen($ignored);
+ } else {
+ $this->_datasSize = strlen($this->_datas);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php
new file mode 100644
index 0000000..4ae5bac
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Base class for Spools (implements time and message limits).
+ *
+ * @author Fabien Potencier
+ */
+abstract class Swift_ConfigurableSpool implements Swift_Spool
+{
+ /** The maximum number of messages to send per flush */
+ private $_message_limit;
+
+ /** The time limit per flush */
+ private $_time_limit;
+
+ /**
+ * Sets the maximum number of messages to send per flush.
+ *
+ * @param int $limit
+ */
+ public function setMessageLimit($limit)
+ {
+ $this->_message_limit = (int) $limit;
+ }
+
+ /**
+ * Gets the maximum number of messages to send per flush.
+ *
+ * @return int The limit
+ */
+ public function getMessageLimit()
+ {
+ return $this->_message_limit;
+ }
+
+ /**
+ * Sets the time limit (in seconds) per flush.
+ *
+ * @param int $limit The limit
+ */
+ public function setTimeLimit($limit)
+ {
+ $this->_time_limit = (int) $limit;
+ }
+
+ /**
+ * Gets the time limit (in seconds) per flush.
+ *
+ * @return int The limit
+ */
+ public function getTimeLimit()
+ {
+ return $this->_time_limit;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php
new file mode 100644
index 0000000..befec9a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php
@@ -0,0 +1,373 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Dependency Injection container.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_DependencyContainer
+{
+ /** Constant for literal value types */
+ const TYPE_VALUE = 0x0001;
+
+ /** Constant for new instance types */
+ const TYPE_INSTANCE = 0x0010;
+
+ /** Constant for shared instance types */
+ const TYPE_SHARED = 0x0100;
+
+ /** Constant for aliases */
+ const TYPE_ALIAS = 0x1000;
+
+ /** Singleton instance */
+ private static $_instance = null;
+
+ /** The data container */
+ private $_store = array();
+
+ /** The current endpoint in the data container */
+ private $_endPoint;
+
+ /**
+ * Constructor should not be used.
+ *
+ * Use {@link getInstance()} instead.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Returns a singleton of the DependencyContainer.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (!isset(self::$_instance)) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * List the names of all items stored in the Container.
+ *
+ * @return array
+ */
+ public function listItems()
+ {
+ return array_keys($this->_store);
+ }
+
+ /**
+ * Test if an item is registered in this container with the given name.
+ *
+ * @see register()
+ *
+ * @param string $itemName
+ *
+ * @return bool
+ */
+ public function has($itemName)
+ {
+ return array_key_exists($itemName, $this->_store)
+ && isset($this->_store[$itemName]['lookupType']);
+ }
+
+ /**
+ * Lookup the item with the given $itemName.
+ *
+ * @see register()
+ *
+ * @param string $itemName
+ *
+ * @throws Swift_DependencyException If the dependency is not found
+ *
+ * @return mixed
+ */
+ public function lookup($itemName)
+ {
+ if (!$this->has($itemName)) {
+ throw new Swift_DependencyException(
+ 'Cannot lookup dependency "'.$itemName.'" since it is not registered.'
+ );
+ }
+
+ switch ($this->_store[$itemName]['lookupType']) {
+ case self::TYPE_ALIAS:
+ return $this->_createAlias($itemName);
+ case self::TYPE_VALUE:
+ return $this->_getValue($itemName);
+ case self::TYPE_INSTANCE:
+ return $this->_createNewInstance($itemName);
+ case self::TYPE_SHARED:
+ return $this->_createSharedInstance($itemName);
+ }
+ }
+
+ /**
+ * Create an array of arguments passed to the constructor of $itemName.
+ *
+ * @param string $itemName
+ *
+ * @return array
+ */
+ public function createDependenciesFor($itemName)
+ {
+ $args = array();
+ if (isset($this->_store[$itemName]['args'])) {
+ $args = $this->_resolveArgs($this->_store[$itemName]['args']);
+ }
+
+ return $args;
+ }
+
+ /**
+ * Register a new dependency with $itemName.
+ *
+ * This method returns the current DependencyContainer instance because it
+ * requires the use of the fluid interface to set the specific details for the
+ * dependency.
+ *
+ * @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
+ *
+ * @param string $itemName
+ *
+ * @return $this
+ */
+ public function register($itemName)
+ {
+ $this->_store[$itemName] = array();
+ $this->_endPoint = &$this->_store[$itemName];
+
+ return $this;
+ }
+
+ /**
+ * Specify the previously registered item as a literal value.
+ *
+ * {@link register()} must be called before this will work.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function asValue($value)
+ {
+ $endPoint = &$this->_getEndPoint();
+ $endPoint['lookupType'] = self::TYPE_VALUE;
+ $endPoint['value'] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Specify the previously registered item as an alias of another item.
+ *
+ * @param string $lookup
+ *
+ * @return $this
+ */
+ public function asAliasOf($lookup)
+ {
+ $endPoint = &$this->_getEndPoint();
+ $endPoint['lookupType'] = self::TYPE_ALIAS;
+ $endPoint['ref'] = $lookup;
+
+ return $this;
+ }
+
+ /**
+ * Specify the previously registered item as a new instance of $className.
+ *
+ * {@link register()} must be called before this will work.
+ * Any arguments can be set with {@link withDependencies()},
+ * {@link addConstructorValue()} or {@link addConstructorLookup()}.
+ *
+ * @see withDependencies(), addConstructorValue(), addConstructorLookup()
+ *
+ * @param string $className
+ *
+ * @return $this
+ */
+ public function asNewInstanceOf($className)
+ {
+ $endPoint = &$this->_getEndPoint();
+ $endPoint['lookupType'] = self::TYPE_INSTANCE;
+ $endPoint['className'] = $className;
+
+ return $this;
+ }
+
+ /**
+ * Specify the previously registered item as a shared instance of $className.
+ *
+ * {@link register()} must be called before this will work.
+ *
+ * @param string $className
+ *
+ * @return $this
+ */
+ public function asSharedInstanceOf($className)
+ {
+ $endPoint = &$this->_getEndPoint();
+ $endPoint['lookupType'] = self::TYPE_SHARED;
+ $endPoint['className'] = $className;
+
+ return $this;
+ }
+
+ /**
+ * Specify a list of injected dependencies for the previously registered item.
+ *
+ * This method takes an array of lookup names.
+ *
+ * @see addConstructorValue(), addConstructorLookup()
+ *
+ * @param array $lookups
+ *
+ * @return $this
+ */
+ public function withDependencies(array $lookups)
+ {
+ $endPoint = &$this->_getEndPoint();
+ $endPoint['args'] = array();
+ foreach ($lookups as $lookup) {
+ $this->addConstructorLookup($lookup);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Specify a literal (non looked up) value for the constructor of the
+ * previously registered item.
+ *
+ * @see withDependencies(), addConstructorLookup()
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function addConstructorValue($value)
+ {
+ $endPoint = &$this->_getEndPoint();
+ if (!isset($endPoint['args'])) {
+ $endPoint['args'] = array();
+ }
+ $endPoint['args'][] = array('type' => 'value', 'item' => $value);
+
+ return $this;
+ }
+
+ /**
+ * Specify a dependency lookup for the constructor of the previously
+ * registered item.
+ *
+ * @see withDependencies(), addConstructorValue()
+ *
+ * @param string $lookup
+ *
+ * @return $this
+ */
+ public function addConstructorLookup($lookup)
+ {
+ $endPoint = &$this->_getEndPoint();
+ if (!isset($this->_endPoint['args'])) {
+ $endPoint['args'] = array();
+ }
+ $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);
+
+ return $this;
+ }
+
+ /** Get the literal value with $itemName */
+ private function _getValue($itemName)
+ {
+ return $this->_store[$itemName]['value'];
+ }
+
+ /** Resolve an alias to another item */
+ private function _createAlias($itemName)
+ {
+ return $this->lookup($this->_store[$itemName]['ref']);
+ }
+
+ /** Create a fresh instance of $itemName */
+ private function _createNewInstance($itemName)
+ {
+ $reflector = new ReflectionClass($this->_store[$itemName]['className']);
+ if ($reflector->getConstructor()) {
+ return $reflector->newInstanceArgs(
+ $this->createDependenciesFor($itemName)
+ );
+ }
+
+ return $reflector->newInstance();
+ }
+
+ /** Create and register a shared instance of $itemName */
+ private function _createSharedInstance($itemName)
+ {
+ if (!isset($this->_store[$itemName]['instance'])) {
+ $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
+ }
+
+ return $this->_store[$itemName]['instance'];
+ }
+
+ /** Get the current endpoint in the store */
+ private function &_getEndPoint()
+ {
+ if (!isset($this->_endPoint)) {
+ throw new BadMethodCallException(
+ 'Component must first be registered by calling register()'
+ );
+ }
+
+ return $this->_endPoint;
+ }
+
+ /** Get an argument list with dependencies resolved */
+ private function _resolveArgs(array $args)
+ {
+ $resolved = array();
+ foreach ($args as $argDefinition) {
+ switch ($argDefinition['type']) {
+ case 'lookup':
+ $resolved[] = $this->_lookupRecursive($argDefinition['item']);
+ break;
+ case 'value':
+ $resolved[] = $argDefinition['item'];
+ break;
+ }
+ }
+
+ return $resolved;
+ }
+
+ /** Resolve a single dependency with an collections */
+ private function _lookupRecursive($item)
+ {
+ if (is_array($item)) {
+ $collection = array();
+ foreach ($item as $k => $v) {
+ $collection[$k] = $this->_lookupRecursive($v);
+ }
+
+ return $collection;
+ }
+
+ return $this->lookup($item);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php
new file mode 100644
index 0000000..799d38d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * DependencyException gets thrown when a requested dependency is missing.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_DependencyException extends Swift_SwiftException
+{
+ /**
+ * Create a new DependencyException with $message.
+ *
+ * @param string $message
+ */
+ public function __construct($message)
+ {
+ parent::__construct($message);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php
new file mode 100644
index 0000000..d8c72ad
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An embedded file, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_EmbeddedFile extends Swift_Mime_EmbeddedFile
+{
+ /**
+ * Create a new EmbeddedFile.
+ *
+ * Details may be optionally provided to the constructor.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ */
+ public function __construct($data = null, $filename = null, $contentType = null)
+ {
+ call_user_func_array(
+ array($this, 'Swift_Mime_EmbeddedFile::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('mime.embeddedfile')
+ );
+
+ $this->setBody($data);
+ $this->setFilename($filename);
+ if ($contentType) {
+ $this->setContentType($contentType);
+ }
+ }
+
+ /**
+ * Create a new EmbeddedFile.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ *
+ * @return Swift_Mime_EmbeddedFile
+ */
+ public static function newInstance($data = null, $filename = null, $contentType = null)
+ {
+ return new self($data, $filename, $contentType);
+ }
+
+ /**
+ * Create a new EmbeddedFile from a filesystem path.
+ *
+ * @param string $path
+ *
+ * @return Swift_Mime_EmbeddedFile
+ */
+ public static function fromPath($path)
+ {
+ return self::newInstance()->setFile(
+ new Swift_ByteStream_FileByteStream($path)
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php
new file mode 100644
index 0000000..2073abc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for all Encoder schemes.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Encoder extends Swift_Mime_CharsetObserver
+{
+ /**
+ * Encode a given string to produce an encoded string.
+ *
+ * @param string $string
+ * @param int $firstLineOffset if first line needs to be shorter
+ * @param int $maxLineLength - 0 indicates the default length for this encoding
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php
new file mode 100644
index 0000000..0e7b2a1
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Base 64 Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Encoder_Base64Encoder implements Swift_Encoder
+{
+ /**
+ * Takes an unencoded string and produces a Base64 encoded string from it.
+ *
+ * Base64 encoded strings have a maximum line length of 76 characters.
+ * If the first line needs to be shorter, indicate the difference with
+ * $firstLineOffset.
+ *
+ * @param string $string to encode
+ * @param int $firstLineOffset
+ * @param int $maxLineLength optional, 0 indicates the default of 76 bytes
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if (0 >= $maxLineLength || 76 < $maxLineLength) {
+ $maxLineLength = 76;
+ }
+
+ $encodedString = base64_encode($string);
+ $firstLine = '';
+
+ if (0 != $firstLineOffset) {
+ $firstLine = substr(
+ $encodedString, 0, $maxLineLength - $firstLineOffset
+ )."\r\n";
+ $encodedString = substr(
+ $encodedString, $maxLineLength - $firstLineOffset
+ );
+ }
+
+ return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
+ }
+
+ /**
+ * Does nothing.
+ */
+ public function charsetChanged($charset)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php
new file mode 100644
index 0000000..edec10c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php
@@ -0,0 +1,300 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Quoted Printable (QP) Encoding in Swift Mailer.
+ *
+ * Possibly the most accurate RFC 2045 QP implementation found in PHP.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Encoder_QpEncoder implements Swift_Encoder
+{
+ /**
+ * The CharacterStream used for reading characters (as opposed to bytes).
+ *
+ * @var Swift_CharacterStream
+ */
+ protected $_charStream;
+
+ /**
+ * A filter used if input should be canonicalized.
+ *
+ * @var Swift_StreamFilter
+ */
+ protected $_filter;
+
+ /**
+ * Pre-computed QP for HUGE optimization.
+ *
+ * @var string[]
+ */
+ protected static $_qpMap = array(
+ 0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
+ 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
+ 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
+ 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
+ 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
+ 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
+ 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
+ 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
+ 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
+ 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
+ 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
+ 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
+ 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
+ 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
+ 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
+ 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
+ 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
+ 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
+ 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
+ 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
+ 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
+ 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
+ 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
+ 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
+ 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
+ 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
+ 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
+ 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
+ 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
+ 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
+ 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
+ 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
+ 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
+ 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
+ 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
+ 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
+ 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
+ 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
+ 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
+ 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
+ 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
+ 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
+ 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
+ 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
+ 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
+ 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
+ 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
+ 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
+ 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
+ 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
+ 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
+ 255 => '=FF',
+ );
+
+ protected static $_safeMapShare = array();
+
+ /**
+ * A map of non-encoded ascii characters.
+ *
+ * @var string[]
+ */
+ protected $_safeMap = array();
+
+ /**
+ * Creates a new QpEncoder for the given CharacterStream.
+ *
+ * @param Swift_CharacterStream $charStream to use for reading characters
+ * @param Swift_StreamFilter $filter if input should be canonicalized
+ */
+ public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null)
+ {
+ $this->_charStream = $charStream;
+ if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
+ $this->initSafeMap();
+ self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
+ } else {
+ $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
+ }
+ $this->_filter = $filter;
+ }
+
+ public function __sleep()
+ {
+ return array('_charStream', '_filter');
+ }
+
+ public function __wakeup()
+ {
+ if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
+ $this->initSafeMap();
+ self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
+ } else {
+ $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
+ }
+ }
+
+ protected function getSafeMapShareId()
+ {
+ return get_class($this);
+ }
+
+ protected function initSafeMap()
+ {
+ foreach (array_merge(
+ array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) {
+ $this->_safeMap[$byte] = chr($byte);
+ }
+ }
+
+ /**
+ * Takes an unencoded string and produces a QP encoded string from it.
+ *
+ * QP encoded strings have a maximum line length of 76 characters.
+ * If the first line needs to be shorter, indicate the difference with
+ * $firstLineOffset.
+ *
+ * @param string $string to encode
+ * @param int $firstLineOffset, optional
+ * @param int $maxLineLength, optional 0 indicates the default of 76 chars
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if ($maxLineLength > 76 || $maxLineLength <= 0) {
+ $maxLineLength = 76;
+ }
+
+ $thisLineLength = $maxLineLength - $firstLineOffset;
+
+ $lines = array();
+ $lNo = 0;
+ $lines[$lNo] = '';
+ $currentLine = &$lines[$lNo++];
+ $size = $lineLen = 0;
+
+ $this->_charStream->flushContents();
+ $this->_charStream->importString($string);
+
+ // Fetching more than 4 chars at one is slower, as is fetching fewer bytes
+ // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
+ // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
+ while (false !== $bytes = $this->_nextSequence()) {
+ // If we're filtering the input
+ if (isset($this->_filter)) {
+ // If we can't filter because we need more bytes
+ while ($this->_filter->shouldBuffer($bytes)) {
+ // Then collect bytes into the buffer
+ if (false === $moreBytes = $this->_nextSequence(1)) {
+ break;
+ }
+
+ foreach ($moreBytes as $b) {
+ $bytes[] = $b;
+ }
+ }
+ // And filter them
+ $bytes = $this->_filter->filter($bytes);
+ }
+
+ $enc = $this->_encodeByteSequence($bytes, $size);
+
+ $i = strpos($enc, '=0D=0A');
+ $newLineLength = $lineLen + ($i === false ? $size : $i);
+
+ if ($currentLine && $newLineLength >= $thisLineLength) {
+ $lines[$lNo] = '';
+ $currentLine = &$lines[$lNo++];
+ $thisLineLength = $maxLineLength;
+ $lineLen = 0;
+ }
+
+ $currentLine .= $enc;
+
+ if ($i === false) {
+ $lineLen += $size;
+ } else {
+ // 6 is the length of '=0D=0A'.
+ $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
+ }
+ }
+
+ return $this->_standardize(implode("=\r\n", $lines));
+ }
+
+ /**
+ * Updates the charset used.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_charStream->setCharacterSet($charset);
+ }
+
+ /**
+ * Encode the given byte array into a verbatim QP form.
+ *
+ * @param int[] $bytes
+ * @param int $size
+ *
+ * @return string
+ */
+ protected function _encodeByteSequence(array $bytes, &$size)
+ {
+ $ret = '';
+ $size = 0;
+ foreach ($bytes as $b) {
+ if (isset($this->_safeMap[$b])) {
+ $ret .= $this->_safeMap[$b];
+ ++$size;
+ } else {
+ $ret .= self::$_qpMap[$b];
+ $size += 3;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get the next sequence of bytes to read from the char stream.
+ *
+ * @param int $size number of bytes to read
+ *
+ * @return int[]
+ */
+ protected function _nextSequence($size = 4)
+ {
+ return $this->_charStream->readBytes($size);
+ }
+
+ /**
+ * Make sure CRLF is correct and HT/SPACE are in valid places.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ protected function _standardize($string)
+ {
+ $string = str_replace(array("\t=0D=0A", ' =0D=0A', '=0D=0A'),
+ array("=09\r\n", "=20\r\n", "\r\n"), $string
+ );
+ switch ($end = ord(substr($string, -1))) {
+ case 0x09:
+ case 0x20:
+ $string = substr_replace($string, self::$_qpMap[$end], -1);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->_charStream = clone $this->_charStream;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php
new file mode 100644
index 0000000..b0215e8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles RFC 2231 specified Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Encoder_Rfc2231Encoder implements Swift_Encoder
+{
+ /**
+ * A character stream to use when reading a string as characters instead of bytes.
+ *
+ * @var Swift_CharacterStream
+ */
+ private $_charStream;
+
+ /**
+ * Creates a new Rfc2231Encoder using the given character stream instance.
+ *
+ * @param Swift_CharacterStream
+ */
+ public function __construct(Swift_CharacterStream $charStream)
+ {
+ $this->_charStream = $charStream;
+ }
+
+ /**
+ * Takes an unencoded string and produces a string encoded according to
+ * RFC 2231 from it.
+ *
+ * @param string $string
+ * @param int $firstLineOffset
+ * @param int $maxLineLength optional, 0 indicates the default of 75 bytes
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ $lines = array();
+ $lineCount = 0;
+ $lines[] = '';
+ $currentLine = &$lines[$lineCount++];
+
+ if (0 >= $maxLineLength) {
+ $maxLineLength = 75;
+ }
+
+ $this->_charStream->flushContents();
+ $this->_charStream->importString($string);
+
+ $thisLineLength = $maxLineLength - $firstLineOffset;
+
+ while (false !== $char = $this->_charStream->read(4)) {
+ $encodedChar = rawurlencode($char);
+ if (0 != strlen($currentLine)
+ && strlen($currentLine.$encodedChar) > $thisLineLength) {
+ $lines[] = '';
+ $currentLine = &$lines[$lineCount++];
+ $thisLineLength = $maxLineLength;
+ }
+ $currentLine .= $encodedChar;
+ }
+
+ return implode("\r\n", $lines);
+ }
+
+ /**
+ * Updates the charset used.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_charStream->setCharacterSet($charset);
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->_charStream = clone $this->_charStream;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php
new file mode 100644
index 0000000..2458787
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides quick access to each encoding type.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Encoding
+{
+ /**
+ * Get the Encoder that provides 7-bit encoding.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public static function get7BitEncoding()
+ {
+ return self::_lookup('mime.7bitcontentencoder');
+ }
+
+ /**
+ * Get the Encoder that provides 8-bit encoding.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public static function get8BitEncoding()
+ {
+ return self::_lookup('mime.8bitcontentencoder');
+ }
+
+ /**
+ * Get the Encoder that provides Quoted-Printable (QP) encoding.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public static function getQpEncoding()
+ {
+ return self::_lookup('mime.qpcontentencoder');
+ }
+
+ /**
+ * Get the Encoder that provides Base64 encoding.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public static function getBase64Encoding()
+ {
+ return self::_lookup('mime.base64contentencoder');
+ }
+
+ private static function _lookup($key)
+ {
+ return Swift_DependencyContainer::getInstance()->lookup($key);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php
new file mode 100644
index 0000000..674e6b5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Generated when a command is sent over an SMTP connection.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_CommandEvent extends Swift_Events_EventObject
+{
+ /**
+ * The command sent to the server.
+ *
+ * @var string
+ */
+ private $_command;
+
+ /**
+ * An array of codes which a successful response will contain.
+ *
+ * @var int[]
+ */
+ private $_successCodes = array();
+
+ /**
+ * Create a new CommandEvent for $source with $command.
+ *
+ * @param Swift_Transport $source
+ * @param string $command
+ * @param array $successCodes
+ */
+ public function __construct(Swift_Transport $source, $command, $successCodes = array())
+ {
+ parent::__construct($source);
+ $this->_command = $command;
+ $this->_successCodes = $successCodes;
+ }
+
+ /**
+ * Get the command which was sent to the server.
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->_command;
+ }
+
+ /**
+ * Get the numeric response codes which indicate success for this command.
+ *
+ * @return int[]
+ */
+ public function getSuccessCodes()
+ {
+ return $this->_successCodes;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php
new file mode 100644
index 0000000..7545404
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Listens for Transports to send commands to the server.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_CommandListener extends Swift_Events_EventListener
+{
+ /**
+ * Invoked immediately following a command being sent.
+ *
+ * @param Swift_Events_CommandEvent $evt
+ */
+ public function commandSent(Swift_Events_CommandEvent $evt);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php
new file mode 100644
index 0000000..720b156
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * The minimum interface for an Event.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_Event
+{
+ /**
+ * Get the source object of this event.
+ *
+ * @return object
+ */
+ public function getSource();
+
+ /**
+ * Prevent this Event from bubbling any further up the stack.
+ *
+ * @param bool $cancel, optional
+ */
+ public function cancelBubble($cancel = true);
+
+ /**
+ * Returns true if this Event will not bubble any further up the stack.
+ *
+ * @return bool
+ */
+ public function bubbleCancelled();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php
new file mode 100644
index 0000000..aac36aa
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php
@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for the EventDispatcher which handles the event dispatching layer.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_EventDispatcher
+{
+ /**
+ * Create a new SendEvent for $source and $message.
+ *
+ * @param Swift_Transport $source
+ * @param Swift_Mime_Message
+ *
+ * @return Swift_Events_SendEvent
+ */
+ public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message);
+
+ /**
+ * Create a new CommandEvent for $source and $command.
+ *
+ * @param Swift_Transport $source
+ * @param string $command That will be executed
+ * @param array $successCodes That are needed
+ *
+ * @return Swift_Events_CommandEvent
+ */
+ public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array());
+
+ /**
+ * Create a new ResponseEvent for $source and $response.
+ *
+ * @param Swift_Transport $source
+ * @param string $response
+ * @param bool $valid If the response is valid
+ *
+ * @return Swift_Events_ResponseEvent
+ */
+ public function createResponseEvent(Swift_Transport $source, $response, $valid);
+
+ /**
+ * Create a new TransportChangeEvent for $source.
+ *
+ * @param Swift_Transport $source
+ *
+ * @return Swift_Events_TransportChangeEvent
+ */
+ public function createTransportChangeEvent(Swift_Transport $source);
+
+ /**
+ * Create a new TransportExceptionEvent for $source.
+ *
+ * @param Swift_Transport $source
+ * @param Swift_TransportException $ex
+ *
+ * @return Swift_Events_TransportExceptionEvent
+ */
+ public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex);
+
+ /**
+ * Bind an event listener to this dispatcher.
+ *
+ * @param Swift_Events_EventListener $listener
+ */
+ public function bindEventListener(Swift_Events_EventListener $listener);
+
+ /**
+ * Dispatch the given Event to all suitable listeners.
+ *
+ * @param Swift_Events_EventObject $evt
+ * @param string $target method
+ */
+ public function dispatchEvent(Swift_Events_EventObject $evt, $target);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php
new file mode 100644
index 0000000..5129095
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An identity interface which all EventListeners must extend.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_EventListener
+{
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php
new file mode 100644
index 0000000..90694a9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A base Event which all Event classes inherit from.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_EventObject implements Swift_Events_Event
+{
+ /** The source of this Event */
+ private $_source;
+
+ /** The state of this Event (should it bubble up the stack?) */
+ private $_bubbleCancelled = false;
+
+ /**
+ * Create a new EventObject originating at $source.
+ *
+ * @param object $source
+ */
+ public function __construct($source)
+ {
+ $this->_source = $source;
+ }
+
+ /**
+ * Get the source object of this event.
+ *
+ * @return object
+ */
+ public function getSource()
+ {
+ return $this->_source;
+ }
+
+ /**
+ * Prevent this Event from bubbling any further up the stack.
+ *
+ * @param bool $cancel, optional
+ */
+ public function cancelBubble($cancel = true)
+ {
+ $this->_bubbleCancelled = $cancel;
+ }
+
+ /**
+ * Returns true if this Event will not bubble any further up the stack.
+ *
+ * @return bool
+ */
+ public function bubbleCancelled()
+ {
+ return $this->_bubbleCancelled;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php
new file mode 100644
index 0000000..2e92ba9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Generated when a response is received on a SMTP connection.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_ResponseEvent extends Swift_Events_EventObject
+{
+ /**
+ * The overall result.
+ *
+ * @var bool
+ */
+ private $_valid;
+
+ /**
+ * The response received from the server.
+ *
+ * @var string
+ */
+ private $_response;
+
+ /**
+ * Create a new ResponseEvent for $source and $response.
+ *
+ * @param Swift_Transport $source
+ * @param string $response
+ * @param bool $valid
+ */
+ public function __construct(Swift_Transport $source, $response, $valid = false)
+ {
+ parent::__construct($source);
+ $this->_response = $response;
+ $this->_valid = $valid;
+ }
+
+ /**
+ * Get the response which was received from the server.
+ *
+ * @return string
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Get the success status of this Event.
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ return $this->_valid;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php
new file mode 100644
index 0000000..c40919d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Listens for responses from a remote SMTP server.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_ResponseListener extends Swift_Events_EventListener
+{
+ /**
+ * Invoked immediately following a response coming back.
+ *
+ * @param Swift_Events_ResponseEvent $evt
+ */
+ public function responseReceived(Swift_Events_ResponseEvent $evt);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php
new file mode 100644
index 0000000..10da808
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php
@@ -0,0 +1,129 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Generated when a message is being sent.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_SendEvent extends Swift_Events_EventObject
+{
+ /** Sending has yet to occur */
+ const RESULT_PENDING = 0x0001;
+
+ /** Email is spooled, ready to be sent */
+ const RESULT_SPOOLED = 0x0011;
+
+ /** Sending was successful */
+ const RESULT_SUCCESS = 0x0010;
+
+ /** Sending worked, but there were some failures */
+ const RESULT_TENTATIVE = 0x0100;
+
+ /** Sending failed */
+ const RESULT_FAILED = 0x1000;
+
+ /**
+ * The Message being sent.
+ *
+ * @var Swift_Mime_Message
+ */
+ private $_message;
+
+ /**
+ * Any recipients which failed after sending.
+ *
+ * @var string[]
+ */
+ private $_failedRecipients = array();
+
+ /**
+ * The overall result as a bitmask from the class constants.
+ *
+ * @var int
+ */
+ private $_result;
+
+ /**
+ * Create a new SendEvent for $source and $message.
+ *
+ * @param Swift_Transport $source
+ * @param Swift_Mime_Message $message
+ */
+ public function __construct(Swift_Transport $source, Swift_Mime_Message $message)
+ {
+ parent::__construct($source);
+ $this->_message = $message;
+ $this->_result = self::RESULT_PENDING;
+ }
+
+ /**
+ * Get the Transport used to send the Message.
+ *
+ * @return Swift_Transport
+ */
+ public function getTransport()
+ {
+ return $this->getSource();
+ }
+
+ /**
+ * Get the Message being sent.
+ *
+ * @return Swift_Mime_Message
+ */
+ public function getMessage()
+ {
+ return $this->_message;
+ }
+
+ /**
+ * Set the array of addresses that failed in sending.
+ *
+ * @param array $recipients
+ */
+ public function setFailedRecipients($recipients)
+ {
+ $this->_failedRecipients = $recipients;
+ }
+
+ /**
+ * Get an recipient addresses which were not accepted for delivery.
+ *
+ * @return string[]
+ */
+ public function getFailedRecipients()
+ {
+ return $this->_failedRecipients;
+ }
+
+ /**
+ * Set the result of sending.
+ *
+ * @param int $result
+ */
+ public function setResult($result)
+ {
+ $this->_result = $result;
+ }
+
+ /**
+ * Get the result of this Event.
+ *
+ * The return value is a bitmask from
+ * {@see RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED}
+ *
+ * @return int
+ */
+ public function getResult()
+ {
+ return $this->_result;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php
new file mode 100644
index 0000000..d922e1b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Listens for Messages being sent from within the Transport system.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_SendListener extends Swift_Events_EventListener
+{
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt);
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php
new file mode 100644
index 0000000..e8aca75
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php
@@ -0,0 +1,156 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * The EventDispatcher which handles the event dispatching layer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_SimpleEventDispatcher implements Swift_Events_EventDispatcher
+{
+ /** A map of event types to their associated listener types */
+ private $_eventMap = array();
+
+ /** Event listeners bound to this dispatcher */
+ private $_listeners = array();
+
+ /** Listeners queued to have an Event bubbled up the stack to them */
+ private $_bubbleQueue = array();
+
+ /**
+ * Create a new EventDispatcher.
+ */
+ public function __construct()
+ {
+ $this->_eventMap = array(
+ 'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener',
+ 'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener',
+ 'Swift_Events_SendEvent' => 'Swift_Events_SendListener',
+ 'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener',
+ 'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener',
+ );
+ }
+
+ /**
+ * Create a new SendEvent for $source and $message.
+ *
+ * @param Swift_Transport $source
+ * @param Swift_Mime_Message
+ *
+ * @return Swift_Events_SendEvent
+ */
+ public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message)
+ {
+ return new Swift_Events_SendEvent($source, $message);
+ }
+
+ /**
+ * Create a new CommandEvent for $source and $command.
+ *
+ * @param Swift_Transport $source
+ * @param string $command That will be executed
+ * @param array $successCodes That are needed
+ *
+ * @return Swift_Events_CommandEvent
+ */
+ public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array())
+ {
+ return new Swift_Events_CommandEvent($source, $command, $successCodes);
+ }
+
+ /**
+ * Create a new ResponseEvent for $source and $response.
+ *
+ * @param Swift_Transport $source
+ * @param string $response
+ * @param bool $valid If the response is valid
+ *
+ * @return Swift_Events_ResponseEvent
+ */
+ public function createResponseEvent(Swift_Transport $source, $response, $valid)
+ {
+ return new Swift_Events_ResponseEvent($source, $response, $valid);
+ }
+
+ /**
+ * Create a new TransportChangeEvent for $source.
+ *
+ * @param Swift_Transport $source
+ *
+ * @return Swift_Events_TransportChangeEvent
+ */
+ public function createTransportChangeEvent(Swift_Transport $source)
+ {
+ return new Swift_Events_TransportChangeEvent($source);
+ }
+
+ /**
+ * Create a new TransportExceptionEvent for $source.
+ *
+ * @param Swift_Transport $source
+ * @param Swift_TransportException $ex
+ *
+ * @return Swift_Events_TransportExceptionEvent
+ */
+ public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex)
+ {
+ return new Swift_Events_TransportExceptionEvent($source, $ex);
+ }
+
+ /**
+ * Bind an event listener to this dispatcher.
+ *
+ * @param Swift_Events_EventListener $listener
+ */
+ public function bindEventListener(Swift_Events_EventListener $listener)
+ {
+ foreach ($this->_listeners as $l) {
+ // Already loaded
+ if ($l === $listener) {
+ return;
+ }
+ }
+ $this->_listeners[] = $listener;
+ }
+
+ /**
+ * Dispatch the given Event to all suitable listeners.
+ *
+ * @param Swift_Events_EventObject $evt
+ * @param string $target method
+ */
+ public function dispatchEvent(Swift_Events_EventObject $evt, $target)
+ {
+ $this->_prepareBubbleQueue($evt);
+ $this->_bubble($evt, $target);
+ }
+
+ /** Queue listeners on a stack ready for $evt to be bubbled up it */
+ private function _prepareBubbleQueue(Swift_Events_EventObject $evt)
+ {
+ $this->_bubbleQueue = array();
+ $evtClass = get_class($evt);
+ foreach ($this->_listeners as $listener) {
+ if (array_key_exists($evtClass, $this->_eventMap)
+ && ($listener instanceof $this->_eventMap[$evtClass])) {
+ $this->_bubbleQueue[] = $listener;
+ }
+ }
+ }
+
+ /** Bubble $evt up the stack calling $target() on each listener */
+ private function _bubble(Swift_Events_EventObject $evt, $target)
+ {
+ if (!$evt->bubbleCancelled() && $listener = array_shift($this->_bubbleQueue)) {
+ $listener->$target($evt);
+ $this->_bubble($evt, $target);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php
new file mode 100644
index 0000000..a8972fd
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Generated when the state of a Transport is changed (i.e. stopped/started).
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_TransportChangeEvent extends Swift_Events_EventObject
+{
+ /**
+ * Get the Transport.
+ *
+ * @return Swift_Transport
+ */
+ public function getTransport()
+ {
+ return $this->getSource();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php
new file mode 100644
index 0000000..253165d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Listens for changes within the Transport system.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_TransportChangeListener extends Swift_Events_EventListener
+{
+ /**
+ * Invoked just before a Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt);
+
+ /**
+ * Invoked immediately after the Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStarted(Swift_Events_TransportChangeEvent $evt);
+
+ /**
+ * Invoked just before a Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt);
+
+ /**
+ * Invoked immediately after the Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStopped(Swift_Events_TransportChangeEvent $evt);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php
new file mode 100644
index 0000000..f87154f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Generated when a TransportException is thrown from the Transport system.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Events_TransportExceptionEvent extends Swift_Events_EventObject
+{
+ /**
+ * The Exception thrown.
+ *
+ * @var Swift_TransportException
+ */
+ private $_exception;
+
+ /**
+ * Create a new TransportExceptionEvent for $transport.
+ *
+ * @param Swift_Transport $transport
+ * @param Swift_TransportException $ex
+ */
+ public function __construct(Swift_Transport $transport, Swift_TransportException $ex)
+ {
+ parent::__construct($transport);
+ $this->_exception = $ex;
+ }
+
+ /**
+ * Get the TransportException thrown.
+ *
+ * @return Swift_TransportException
+ */
+ public function getException()
+ {
+ return $this->_exception;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php
new file mode 100644
index 0000000..cc3c099
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Listens for Exceptions thrown from within the Transport system.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Events_TransportExceptionListener extends Swift_Events_EventListener
+{
+ /**
+ * Invoked as a TransportException is thrown in the Transport system.
+ *
+ * @param Swift_Events_TransportExceptionEvent $evt
+ */
+ public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php
new file mode 100644
index 0000000..9951c59
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Contains a list of redundant Transports so when one fails, the next is used.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_FailoverTransport extends Swift_Transport_FailoverTransport
+{
+ /**
+ * Creates a new FailoverTransport with $transports.
+ *
+ * @param Swift_Transport[] $transports
+ */
+ public function __construct($transports = array())
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_FailoverTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.failover')
+ );
+
+ $this->setTransports($transports);
+ }
+
+ /**
+ * Create a new FailoverTransport instance.
+ *
+ * @param Swift_Transport[] $transports
+ *
+ * @return self
+ */
+ public static function newInstance($transports = array())
+ {
+ return new self($transports);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php
new file mode 100644
index 0000000..c82c5db
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php
@@ -0,0 +1,208 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Stores Messages on the filesystem.
+ *
+ * @author Fabien Potencier
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_FileSpool extends Swift_ConfigurableSpool
+{
+ /** The spool directory */
+ private $_path;
+
+ /**
+ * File WriteRetry Limit.
+ *
+ * @var int
+ */
+ private $_retryLimit = 10;
+
+ /**
+ * Create a new FileSpool.
+ *
+ * @param string $path
+ *
+ * @throws Swift_IoException
+ */
+ public function __construct($path)
+ {
+ $this->_path = $path;
+
+ if (!file_exists($this->_path)) {
+ if (!mkdir($this->_path, 0777, true)) {
+ throw new Swift_IoException(sprintf('Unable to create path "%s".', $this->_path));
+ }
+ }
+ }
+
+ /**
+ * Tests if this Spool mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return true;
+ }
+
+ /**
+ * Starts this Spool mechanism.
+ */
+ public function start()
+ {
+ }
+
+ /**
+ * Stops this Spool mechanism.
+ */
+ public function stop()
+ {
+ }
+
+ /**
+ * Allow to manage the enqueuing retry limit.
+ *
+ * Default, is ten and allows over 64^20 different fileNames
+ *
+ * @param int $limit
+ */
+ public function setRetryLimit($limit)
+ {
+ $this->_retryLimit = $limit;
+ }
+
+ /**
+ * Queues a message.
+ *
+ * @param Swift_Mime_Message $message The message to store
+ *
+ * @throws Swift_IoException
+ *
+ * @return bool
+ */
+ public function queueMessage(Swift_Mime_Message $message)
+ {
+ $ser = serialize($message);
+ $fileName = $this->_path.'/'.$this->getRandomString(10);
+ for ($i = 0; $i < $this->_retryLimit; ++$i) {
+ /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */
+ $fp = @fopen($fileName.'.message', 'x');
+ if (false !== $fp) {
+ if (false === fwrite($fp, $ser)) {
+ return false;
+ }
+
+ return fclose($fp);
+ } else {
+ /* The file already exists, we try a longer fileName */
+ $fileName .= $this->getRandomString(1);
+ }
+ }
+
+ throw new Swift_IoException(sprintf('Unable to create a file for enqueuing Message in "%s".', $this->_path));
+ }
+
+ /**
+ * Execute a recovery if for any reason a process is sending for too long.
+ *
+ * @param int $timeout in second Defaults is for very slow smtp responses
+ */
+ public function recover($timeout = 900)
+ {
+ foreach (new DirectoryIterator($this->_path) as $file) {
+ $file = $file->getRealPath();
+
+ if (substr($file, -16) == '.message.sending') {
+ $lockedtime = filectime($file);
+ if ((time() - $lockedtime) > $timeout) {
+ rename($file, substr($file, 0, -8));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends messages using the given transport instance.
+ *
+ * @param Swift_Transport $transport A transport instance
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of sent e-mail's
+ */
+ public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
+ {
+ $directoryIterator = new DirectoryIterator($this->_path);
+
+ /* Start the transport only if there are queued files to send */
+ if (!$transport->isStarted()) {
+ foreach ($directoryIterator as $file) {
+ if (substr($file->getRealPath(), -8) == '.message') {
+ $transport->start();
+ break;
+ }
+ }
+ }
+
+ $failedRecipients = (array) $failedRecipients;
+ $count = 0;
+ $time = time();
+ foreach ($directoryIterator as $file) {
+ $file = $file->getRealPath();
+
+ if (substr($file, -8) != '.message') {
+ continue;
+ }
+
+ /* We try a rename, it's an atomic operation, and avoid locking the file */
+ if (rename($file, $file.'.sending')) {
+ $message = unserialize(file_get_contents($file.'.sending'));
+
+ $count += $transport->send($message, $failedRecipients);
+
+ unlink($file.'.sending');
+ } else {
+ /* This message has just been catched by another process */
+ continue;
+ }
+
+ if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) {
+ break;
+ }
+
+ if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) {
+ break;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Returns a random string needed to generate a fileName for the queue.
+ *
+ * @param int $count
+ *
+ * @return string
+ */
+ protected function getRandomString($count)
+ {
+ // This string MUST stay FS safe, avoid special chars
+ $base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
+ $ret = '';
+ $strlen = strlen($base);
+ for ($i = 0; $i < $count; ++$i) {
+ $ret .= $base[((int) rand(0, $strlen - 1))];
+ }
+
+ return $ret;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php
new file mode 100644
index 0000000..0b24db1
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An OutputByteStream which specifically reads from a file.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_FileStream extends Swift_OutputByteStream
+{
+ /**
+ * Get the complete path to the file.
+ *
+ * @return string
+ */
+ public function getPath();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php
new file mode 100644
index 0000000..6b75b52
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Allows StreamFilters to operate on a stream.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Filterable
+{
+ /**
+ * Add a new StreamFilter, referenced by $key.
+ *
+ * @param Swift_StreamFilter $filter
+ * @param string $key
+ */
+ public function addFilter(Swift_StreamFilter $filter, $key);
+
+ /**
+ * Remove an existing filter using $key.
+ *
+ * @param string $key
+ */
+ public function removeFilter($key);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php
new file mode 100644
index 0000000..4213ee2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An image, embedded in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Image extends Swift_EmbeddedFile
+{
+ /**
+ * Create a new EmbeddedFile.
+ *
+ * Details may be optionally provided to the constructor.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ */
+ public function __construct($data = null, $filename = null, $contentType = null)
+ {
+ parent::__construct($data, $filename, $contentType);
+ }
+
+ /**
+ * Create a new Image.
+ *
+ * @param string|Swift_OutputByteStream $data
+ * @param string $filename
+ * @param string $contentType
+ *
+ * @return self
+ */
+ public static function newInstance($data = null, $filename = null, $contentType = null)
+ {
+ return new self($data, $filename, $contentType);
+ }
+
+ /**
+ * Create a new Image from a filesystem path.
+ *
+ * @param string $path
+ *
+ * @return self
+ */
+ public static function fromPath($path)
+ {
+ return self::newInstance()->setFile(new Swift_ByteStream_FileByteStream($path));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php
new file mode 100644
index 0000000..56efc75
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An abstract means of writing data.
+ *
+ * Classes implementing this interface may use a subsystem which requires less
+ * memory than working with large strings of data.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_InputByteStream
+{
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * Writing may not happen immediately if the stream chooses to buffer. If
+ * you want to write these bytes with immediate effect, call {@link commit()}
+ * after calling write().
+ *
+ * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
+ * second, etc etc).
+ *
+ * @param string $bytes
+ *
+ * @throws Swift_IoException
+ *
+ * @return int
+ */
+ public function write($bytes);
+
+ /**
+ * For any bytes that are currently buffered inside the stream, force them
+ * off the buffer.
+ *
+ * @throws Swift_IoException
+ */
+ public function commit();
+
+ /**
+ * Attach $is to this stream.
+ *
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is);
+
+ /**
+ * Remove an already bound stream.
+ *
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is);
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ *
+ * @throws Swift_IoException
+ */
+ public function flushBuffers();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php
new file mode 100644
index 0000000..c405f35
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * I/O Exception class.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_IoException extends Swift_SwiftException
+{
+ /**
+ * Create a new IoException with $message.
+ *
+ * @param string $message
+ * @param int $code
+ * @param Exception $previous
+ */
+ public function __construct($message, $code = 0, Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.php
new file mode 100644
index 0000000..cd6f786
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.php
@@ -0,0 +1,105 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides a mechanism for storing data using two keys.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_KeyCache
+{
+ /** Mode for replacing existing cached data */
+ const MODE_WRITE = 1;
+
+ /** Mode for appending data to the end of existing cached data */
+ const MODE_APPEND = 2;
+
+ /**
+ * Set a string into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param string $string
+ * @param int $mode
+ */
+ public function setString($nsKey, $itemKey, $string, $mode);
+
+ /**
+ * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_OutputByteStream $os
+ * @param int $mode
+ */
+ public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode);
+
+ /**
+ * Provides a ByteStream which when written to, writes data to $itemKey.
+ *
+ * NOTE: The stream will always write in append mode.
+ * If the optional third parameter is passed all writes will go through $is.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $is optional input stream
+ *
+ * @return Swift_InputByteStream
+ */
+ public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $is = null);
+
+ /**
+ * Get data back out of the cache as a string.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return string
+ */
+ public function getString($nsKey, $itemKey);
+
+ /**
+ * Get data back out of the cache as a ByteStream.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $is stream to write the data to
+ */
+ public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is);
+
+ /**
+ * Check if the given $itemKey exists in the namespace $nsKey.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return bool
+ */
+ public function hasKey($nsKey, $itemKey);
+
+ /**
+ * Clear data for $itemKey in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ */
+ public function clearKey($nsKey, $itemKey);
+
+ /**
+ * Clear all data in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ */
+ public function clearAll($nsKey);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php
new file mode 100644
index 0000000..b37f07f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php
@@ -0,0 +1,206 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A basic KeyCache backed by an array.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_KeyCache_ArrayKeyCache implements Swift_KeyCache
+{
+ /**
+ * Cache contents.
+ *
+ * @var array
+ */
+ private $_contents = array();
+
+ /**
+ * An InputStream for cloning.
+ *
+ * @var Swift_KeyCache_KeyCacheInputStream
+ */
+ private $_stream;
+
+ /**
+ * Create a new ArrayKeyCache with the given $stream for cloning to make
+ * InputByteStreams.
+ *
+ * @param Swift_KeyCache_KeyCacheInputStream $stream
+ */
+ public function __construct(Swift_KeyCache_KeyCacheInputStream $stream)
+ {
+ $this->_stream = $stream;
+ }
+
+ /**
+ * Set a string into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param string $string
+ * @param int $mode
+ */
+ public function setString($nsKey, $itemKey, $string, $mode)
+ {
+ $this->_prepareCache($nsKey);
+ switch ($mode) {
+ case self::MODE_WRITE:
+ $this->_contents[$nsKey][$itemKey] = $string;
+ break;
+ case self::MODE_APPEND:
+ if (!$this->hasKey($nsKey, $itemKey)) {
+ $this->_contents[$nsKey][$itemKey] = '';
+ }
+ $this->_contents[$nsKey][$itemKey] .= $string;
+ break;
+ default:
+ throw new Swift_SwiftException(
+ 'Invalid mode ['.$mode.'] used to set nsKey='.
+ $nsKey.', itemKey='.$itemKey
+ );
+ }
+ }
+
+ /**
+ * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_OutputByteStream $os
+ * @param int $mode
+ */
+ public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
+ {
+ $this->_prepareCache($nsKey);
+ switch ($mode) {
+ case self::MODE_WRITE:
+ $this->clearKey($nsKey, $itemKey);
+ case self::MODE_APPEND:
+ if (!$this->hasKey($nsKey, $itemKey)) {
+ $this->_contents[$nsKey][$itemKey] = '';
+ }
+ while (false !== $bytes = $os->read(8192)) {
+ $this->_contents[$nsKey][$itemKey] .= $bytes;
+ }
+ break;
+ default:
+ throw new Swift_SwiftException(
+ 'Invalid mode ['.$mode.'] used to set nsKey='.
+ $nsKey.', itemKey='.$itemKey
+ );
+ }
+ }
+
+ /**
+ * Provides a ByteStream which when written to, writes data to $itemKey.
+ *
+ * NOTE: The stream will always write in append mode.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $writeThrough
+ *
+ * @return Swift_InputByteStream
+ */
+ public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
+ {
+ $is = clone $this->_stream;
+ $is->setKeyCache($this);
+ $is->setNsKey($nsKey);
+ $is->setItemKey($itemKey);
+ if (isset($writeThrough)) {
+ $is->setWriteThroughStream($writeThrough);
+ }
+
+ return $is;
+ }
+
+ /**
+ * Get data back out of the cache as a string.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return string
+ */
+ public function getString($nsKey, $itemKey)
+ {
+ $this->_prepareCache($nsKey);
+ if ($this->hasKey($nsKey, $itemKey)) {
+ return $this->_contents[$nsKey][$itemKey];
+ }
+ }
+
+ /**
+ * Get data back out of the cache as a ByteStream.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $is to write the data to
+ */
+ public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
+ {
+ $this->_prepareCache($nsKey);
+ $is->write($this->getString($nsKey, $itemKey));
+ }
+
+ /**
+ * Check if the given $itemKey exists in the namespace $nsKey.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return bool
+ */
+ public function hasKey($nsKey, $itemKey)
+ {
+ $this->_prepareCache($nsKey);
+
+ return array_key_exists($itemKey, $this->_contents[$nsKey]);
+ }
+
+ /**
+ * Clear data for $itemKey in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ */
+ public function clearKey($nsKey, $itemKey)
+ {
+ unset($this->_contents[$nsKey][$itemKey]);
+ }
+
+ /**
+ * Clear all data in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ */
+ public function clearAll($nsKey)
+ {
+ unset($this->_contents[$nsKey]);
+ }
+
+ /**
+ * Initialize the namespace of $nsKey if needed.
+ *
+ * @param string $nsKey
+ */
+ private function _prepareCache($nsKey)
+ {
+ if (!array_key_exists($nsKey, $this->_contents)) {
+ $this->_contents[$nsKey] = array();
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php
new file mode 100644
index 0000000..453f50a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php
@@ -0,0 +1,321 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A KeyCache which streams to and from disk.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_KeyCache_DiskKeyCache implements Swift_KeyCache
+{
+ /** Signal to place pointer at start of file */
+ const POSITION_START = 0;
+
+ /** Signal to place pointer at end of file */
+ const POSITION_END = 1;
+
+ /** Signal to leave pointer in whatever position it currently is */
+ const POSITION_CURRENT = 2;
+
+ /**
+ * An InputStream for cloning.
+ *
+ * @var Swift_KeyCache_KeyCacheInputStream
+ */
+ private $_stream;
+
+ /**
+ * A path to write to.
+ *
+ * @var string
+ */
+ private $_path;
+
+ /**
+ * Stored keys.
+ *
+ * @var array
+ */
+ private $_keys = array();
+
+ /**
+ * Will be true if magic_quotes_runtime is turned on.
+ *
+ * @var bool
+ */
+ private $_quotes = false;
+
+ /**
+ * Create a new DiskKeyCache with the given $stream for cloning to make
+ * InputByteStreams, and the given $path to save to.
+ *
+ * @param Swift_KeyCache_KeyCacheInputStream $stream
+ * @param string $path to save to
+ */
+ public function __construct(Swift_KeyCache_KeyCacheInputStream $stream, $path)
+ {
+ $this->_stream = $stream;
+ $this->_path = $path;
+
+ if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
+ $this->_quotes = true;
+ }
+ }
+
+ /**
+ * Set a string into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param string $string
+ * @param int $mode
+ *
+ * @throws Swift_IoException
+ */
+ public function setString($nsKey, $itemKey, $string, $mode)
+ {
+ $this->_prepareCache($nsKey);
+ switch ($mode) {
+ case self::MODE_WRITE:
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
+ break;
+ case self::MODE_APPEND:
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END);
+ break;
+ default:
+ throw new Swift_SwiftException(
+ 'Invalid mode ['.$mode.'] used to set nsKey='.
+ $nsKey.', itemKey='.$itemKey
+ );
+ break;
+ }
+ fwrite($fp, $string);
+ $this->_freeHandle($nsKey, $itemKey);
+ }
+
+ /**
+ * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_OutputByteStream $os
+ * @param int $mode
+ *
+ * @throws Swift_IoException
+ */
+ public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
+ {
+ $this->_prepareCache($nsKey);
+ switch ($mode) {
+ case self::MODE_WRITE:
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
+ break;
+ case self::MODE_APPEND:
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END);
+ break;
+ default:
+ throw new Swift_SwiftException(
+ 'Invalid mode ['.$mode.'] used to set nsKey='.
+ $nsKey.', itemKey='.$itemKey
+ );
+ break;
+ }
+ while (false !== $bytes = $os->read(8192)) {
+ fwrite($fp, $bytes);
+ }
+ $this->_freeHandle($nsKey, $itemKey);
+ }
+
+ /**
+ * Provides a ByteStream which when written to, writes data to $itemKey.
+ *
+ * NOTE: The stream will always write in append mode.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $writeThrough
+ *
+ * @return Swift_InputByteStream
+ */
+ public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
+ {
+ $is = clone $this->_stream;
+ $is->setKeyCache($this);
+ $is->setNsKey($nsKey);
+ $is->setItemKey($itemKey);
+ if (isset($writeThrough)) {
+ $is->setWriteThroughStream($writeThrough);
+ }
+
+ return $is;
+ }
+
+ /**
+ * Get data back out of the cache as a string.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @throws Swift_IoException
+ *
+ * @return string
+ */
+ public function getString($nsKey, $itemKey)
+ {
+ $this->_prepareCache($nsKey);
+ if ($this->hasKey($nsKey, $itemKey)) {
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 0);
+ }
+ $str = '';
+ while (!feof($fp) && false !== $bytes = fread($fp, 8192)) {
+ $str .= $bytes;
+ }
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 1);
+ }
+ $this->_freeHandle($nsKey, $itemKey);
+
+ return $str;
+ }
+ }
+
+ /**
+ * Get data back out of the cache as a ByteStream.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $is to write the data to
+ */
+ public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
+ {
+ if ($this->hasKey($nsKey, $itemKey)) {
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 0);
+ }
+ while (!feof($fp) && false !== $bytes = fread($fp, 8192)) {
+ $is->write($bytes);
+ }
+ if ($this->_quotes) {
+ ini_set('magic_quotes_runtime', 1);
+ }
+ $this->_freeHandle($nsKey, $itemKey);
+ }
+ }
+
+ /**
+ * Check if the given $itemKey exists in the namespace $nsKey.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return bool
+ */
+ public function hasKey($nsKey, $itemKey)
+ {
+ return is_file($this->_path.'/'.$nsKey.'/'.$itemKey);
+ }
+
+ /**
+ * Clear data for $itemKey in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ */
+ public function clearKey($nsKey, $itemKey)
+ {
+ if ($this->hasKey($nsKey, $itemKey)) {
+ $this->_freeHandle($nsKey, $itemKey);
+ unlink($this->_path.'/'.$nsKey.'/'.$itemKey);
+ }
+ }
+
+ /**
+ * Clear all data in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ */
+ public function clearAll($nsKey)
+ {
+ if (array_key_exists($nsKey, $this->_keys)) {
+ foreach ($this->_keys[$nsKey] as $itemKey => $null) {
+ $this->clearKey($nsKey, $itemKey);
+ }
+ if (is_dir($this->_path.'/'.$nsKey)) {
+ rmdir($this->_path.'/'.$nsKey);
+ }
+ unset($this->_keys[$nsKey]);
+ }
+ }
+
+ /**
+ * Initialize the namespace of $nsKey if needed.
+ *
+ * @param string $nsKey
+ */
+ private function _prepareCache($nsKey)
+ {
+ $cacheDir = $this->_path.'/'.$nsKey;
+ if (!is_dir($cacheDir)) {
+ if (!mkdir($cacheDir)) {
+ throw new Swift_IoException('Failed to create cache directory '.$cacheDir);
+ }
+ $this->_keys[$nsKey] = array();
+ }
+ }
+
+ /**
+ * Get a file handle on the cache item.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param int $position
+ *
+ * @return resource
+ */
+ private function _getHandle($nsKey, $itemKey, $position)
+ {
+ if (!isset($this->_keys[$nsKey][$itemKey])) {
+ $openMode = $this->hasKey($nsKey, $itemKey) ? 'r+b' : 'w+b';
+ $fp = fopen($this->_path.'/'.$nsKey.'/'.$itemKey, $openMode);
+ $this->_keys[$nsKey][$itemKey] = $fp;
+ }
+ if (self::POSITION_START == $position) {
+ fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_SET);
+ } elseif (self::POSITION_END == $position) {
+ fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_END);
+ }
+
+ return $this->_keys[$nsKey][$itemKey];
+ }
+
+ private function _freeHandle($nsKey, $itemKey)
+ {
+ $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_CURRENT);
+ fclose($fp);
+ $this->_keys[$nsKey][$itemKey] = null;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ foreach ($this->_keys as $nsKey => $null) {
+ $this->clearAll($nsKey);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php
new file mode 100644
index 0000000..af80bdc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Writes data to a KeyCache using a stream.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_KeyCache_KeyCacheInputStream extends Swift_InputByteStream
+{
+ /**
+ * Set the KeyCache to wrap.
+ *
+ * @param Swift_KeyCache $keyCache
+ */
+ public function setKeyCache(Swift_KeyCache $keyCache);
+
+ /**
+ * Set the nsKey which will be written to.
+ *
+ * @param string $nsKey
+ */
+ public function setNsKey($nsKey);
+
+ /**
+ * Set the itemKey which will be written to.
+ *
+ * @param string $itemKey
+ */
+ public function setItemKey($itemKey);
+
+ /**
+ * Specify a stream to write through for each write().
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function setWriteThroughStream(Swift_InputByteStream $is);
+
+ /**
+ * Any implementation should be cloneable, allowing the clone to access a
+ * separate $nsKey and $itemKey.
+ */
+ public function __clone();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php
new file mode 100644
index 0000000..4efe785
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php
@@ -0,0 +1,115 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A null KeyCache that does not cache at all.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_KeyCache_NullKeyCache implements Swift_KeyCache
+{
+ /**
+ * Set a string into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param string $string
+ * @param int $mode
+ */
+ public function setString($nsKey, $itemKey, $string, $mode)
+ {
+ }
+
+ /**
+ * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
+ *
+ * @see MODE_WRITE, MODE_APPEND
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_OutputByteStream $os
+ * @param int $mode
+ */
+ public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
+ {
+ }
+
+ /**
+ * Provides a ByteStream which when written to, writes data to $itemKey.
+ *
+ * NOTE: The stream will always write in append mode.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $writeThrough
+ *
+ * @return Swift_InputByteStream
+ */
+ public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
+ {
+ }
+
+ /**
+ * Get data back out of the cache as a string.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return string
+ */
+ public function getString($nsKey, $itemKey)
+ {
+ }
+
+ /**
+ * Get data back out of the cache as a ByteStream.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ * @param Swift_InputByteStream $is to write the data to
+ */
+ public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
+ {
+ }
+
+ /**
+ * Check if the given $itemKey exists in the namespace $nsKey.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ *
+ * @return bool
+ */
+ public function hasKey($nsKey, $itemKey)
+ {
+ return false;
+ }
+
+ /**
+ * Clear data for $itemKey in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ * @param string $itemKey
+ */
+ public function clearKey($nsKey, $itemKey)
+ {
+ }
+
+ /**
+ * Clear all data in the namespace $nsKey if it exists.
+ *
+ * @param string $nsKey
+ */
+ public function clearAll($nsKey)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php
new file mode 100644
index 0000000..b00d458
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Writes data to a KeyCache using a stream.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_KeyCache_SimpleKeyCacheInputStream implements Swift_KeyCache_KeyCacheInputStream
+{
+ /** The KeyCache being written to */
+ private $_keyCache;
+
+ /** The nsKey of the KeyCache being written to */
+ private $_nsKey;
+
+ /** The itemKey of the KeyCache being written to */
+ private $_itemKey;
+
+ /** A stream to write through on each write() */
+ private $_writeThrough = null;
+
+ /**
+ * Set the KeyCache to wrap.
+ *
+ * @param Swift_KeyCache $keyCache
+ */
+ public function setKeyCache(Swift_KeyCache $keyCache)
+ {
+ $this->_keyCache = $keyCache;
+ }
+
+ /**
+ * Specify a stream to write through for each write().
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function setWriteThroughStream(Swift_InputByteStream $is)
+ {
+ $this->_writeThrough = $is;
+ }
+
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * @param string $bytes
+ * @param Swift_InputByteStream $is optional
+ */
+ public function write($bytes, Swift_InputByteStream $is = null)
+ {
+ $this->_keyCache->setString(
+ $this->_nsKey, $this->_itemKey, $bytes, Swift_KeyCache::MODE_APPEND
+ );
+ if (isset($is)) {
+ $is->write($bytes);
+ }
+ if (isset($this->_writeThrough)) {
+ $this->_writeThrough->write($bytes);
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function commit()
+ {
+ }
+
+ /**
+ * Not used.
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ }
+
+ /**
+ * Not used.
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ }
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ */
+ public function flushBuffers()
+ {
+ $this->_keyCache->clearKey($this->_nsKey, $this->_itemKey);
+ }
+
+ /**
+ * Set the nsKey which will be written to.
+ *
+ * @param string $nsKey
+ */
+ public function setNsKey($nsKey)
+ {
+ $this->_nsKey = $nsKey;
+ }
+
+ /**
+ * Set the itemKey which will be written to.
+ *
+ * @param string $itemKey
+ */
+ public function setItemKey($itemKey)
+ {
+ $this->_itemKey = $itemKey;
+ }
+
+ /**
+ * Any implementation should be cloneable, allowing the clone to access a
+ * separate $nsKey and $itemKey.
+ */
+ public function __clone()
+ {
+ $this->_writeThrough = null;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php
new file mode 100644
index 0000000..e151b8a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Redundantly and rotationally uses several Transport implementations when sending.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_LoadBalancedTransport extends Swift_Transport_LoadBalancedTransport
+{
+ /**
+ * Creates a new LoadBalancedTransport with $transports.
+ *
+ * @param array $transports
+ */
+ public function __construct($transports = array())
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_LoadBalancedTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.loadbalanced')
+ );
+
+ $this->setTransports($transports);
+ }
+
+ /**
+ * Create a new LoadBalancedTransport instance.
+ *
+ * @param array $transports
+ *
+ * @return self
+ */
+ public static function newInstance($transports = array())
+ {
+ return new self($transports);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
new file mode 100644
index 0000000..1855698
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages using the mail() function.
+ *
+ * @author Chris Corbyn
+ *
+ * @deprecated since 5.4.5 (to be removed in 6.0)
+ */
+class Swift_MailTransport extends Swift_Transport_MailTransport
+{
+ /**
+ * Create a new MailTransport, optionally specifying $extraParams.
+ *
+ * @param string $extraParams
+ */
+ public function __construct($extraParams = '-f%s')
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_MailTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.mail')
+ );
+
+ $this->setExtraParams($extraParams);
+ }
+
+ /**
+ * Create a new MailTransport instance.
+ *
+ * @param string $extraParams To be passed to mail()
+ *
+ * @return self
+ */
+ public static function newInstance($extraParams = '-f%s')
+ {
+ return new self($extraParams);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php
new file mode 100644
index 0000000..8314fe8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Swift Mailer class.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mailer
+{
+ /** The Transport used to send messages */
+ private $_transport;
+
+ /**
+ * Create a new Mailer using $transport for delivery.
+ *
+ * @param Swift_Transport $transport
+ */
+ public function __construct(Swift_Transport $transport)
+ {
+ $this->_transport = $transport;
+ }
+
+ /**
+ * Create a new Mailer instance.
+ *
+ * @param Swift_Transport $transport
+ *
+ * @return self
+ */
+ public static function newInstance(Swift_Transport $transport)
+ {
+ return new self($transport);
+ }
+
+ /**
+ * Create a new class instance of one of the message services.
+ *
+ * For example 'mimepart' would create a 'message.mimepart' instance
+ *
+ * @param string $service
+ *
+ * @return object
+ */
+ public function createMessage($service = 'message')
+ {
+ return Swift_DependencyContainer::getInstance()
+ ->lookup('message.'.$service);
+ }
+
+ /**
+ * Send the given Message like it would be sent in a mail client.
+ *
+ * All recipients (with the exception of Bcc) will be able to see the other
+ * recipients this message was sent to.
+ *
+ * Recipient/sender data will be retrieved from the Message object.
+ *
+ * The return value is the number of recipients who were accepted for
+ * delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param array $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of successful recipients. Can be 0 which indicates failure
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $failedRecipients = (array) $failedRecipients;
+
+ if (!$this->_transport->isStarted()) {
+ $this->_transport->start();
+ }
+
+ $sent = 0;
+
+ try {
+ $sent = $this->_transport->send($message, $failedRecipients);
+ } catch (Swift_RfcComplianceException $e) {
+ foreach ($message->getTo() as $address => $name) {
+ $failedRecipients[] = $address;
+ }
+ }
+
+ return $sent;
+ }
+
+ /**
+ * Register a plugin using a known unique key (e.g. myPlugin).
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ $this->_transport->registerPlugin($plugin);
+ }
+
+ /**
+ * The Transport used to send messages.
+ *
+ * @return Swift_Transport
+ */
+ public function getTransport()
+ {
+ return $this->_transport;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php
new file mode 100644
index 0000000..e3e6cad
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Wraps a standard PHP array in an iterator.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mailer_ArrayRecipientIterator implements Swift_Mailer_RecipientIterator
+{
+ /**
+ * The list of recipients.
+ *
+ * @var array
+ */
+ private $_recipients = array();
+
+ /**
+ * Create a new ArrayRecipientIterator from $recipients.
+ *
+ * @param array $recipients
+ */
+ public function __construct(array $recipients)
+ {
+ $this->_recipients = $recipients;
+ }
+
+ /**
+ * Returns true only if there are more recipients to send to.
+ *
+ * @return bool
+ */
+ public function hasNext()
+ {
+ return !empty($this->_recipients);
+ }
+
+ /**
+ * Returns an array where the keys are the addresses of recipients and the
+ * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL).
+ *
+ * @return array
+ */
+ public function nextRecipient()
+ {
+ return array_splice($this->_recipients, 0, 1);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php
new file mode 100644
index 0000000..650f3ec
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides an abstract way of specifying recipients for batch sending.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mailer_RecipientIterator
+{
+ /**
+ * Returns true only if there are more recipients to send to.
+ *
+ * @return bool
+ */
+ public function hasNext();
+
+ /**
+ * Returns an array where the keys are the addresses of recipients and the
+ * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL).
+ *
+ * @return array
+ */
+ public function nextRecipient();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php
new file mode 100644
index 0000000..2cafb67
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2011 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Stores Messages in memory.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_MemorySpool implements Swift_Spool
+{
+ protected $messages = array();
+ private $flushRetries = 3;
+
+ /**
+ * Tests if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return true;
+ }
+
+ /**
+ * Starts this Transport mechanism.
+ */
+ public function start()
+ {
+ }
+
+ /**
+ * Stops this Transport mechanism.
+ */
+ public function stop()
+ {
+ }
+
+ /**
+ * @param int $retries
+ */
+ public function setFlushRetries($retries)
+ {
+ $this->flushRetries = $retries;
+ }
+
+ /**
+ * Stores a message in the queue.
+ *
+ * @param Swift_Mime_Message $message The message to store
+ *
+ * @return bool Whether the operation has succeeded
+ */
+ public function queueMessage(Swift_Mime_Message $message)
+ {
+ //clone the message to make sure it is not changed while in the queue
+ $this->messages[] = clone $message;
+
+ return true;
+ }
+
+ /**
+ * Sends messages using the given transport instance.
+ *
+ * @param Swift_Transport $transport A transport instance
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of sent emails
+ */
+ public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
+ {
+ if (!$this->messages) {
+ return 0;
+ }
+
+ if (!$transport->isStarted()) {
+ $transport->start();
+ }
+
+ $count = 0;
+ $retries = $this->flushRetries;
+ while ($retries--) {
+ try {
+ while ($message = array_pop($this->messages)) {
+ $count += $transport->send($message, $failedRecipients);
+ }
+ } catch (Swift_TransportException $exception) {
+ if ($retries) {
+ // re-queue the message at the end of the queue to give a chance
+ // to the other messages to be sent, in case the failure was due to
+ // this message and not just the transport failing
+ array_unshift($this->messages, $message);
+
+ // wait half a second before we try again
+ usleep(500000);
+ } else {
+ throw $exception;
+ }
+ }
+ }
+
+ return $count;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php
new file mode 100644
index 0000000..242cbf3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php
@@ -0,0 +1,289 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * The Message class for building emails.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Message extends Swift_Mime_SimpleMessage
+{
+ /**
+ * @var Swift_Signers_HeaderSigner[]
+ */
+ private $headerSigners = array();
+
+ /**
+ * @var Swift_Signers_BodySigner[]
+ */
+ private $bodySigners = array();
+
+ /**
+ * @var array
+ */
+ private $savedMessage = array();
+
+ /**
+ * Create a new Message.
+ *
+ * Details may be optionally passed into the constructor.
+ *
+ * @param string $subject
+ * @param string $body
+ * @param string $contentType
+ * @param string $charset
+ */
+ public function __construct($subject = null, $body = null, $contentType = null, $charset = null)
+ {
+ call_user_func_array(
+ array($this, 'Swift_Mime_SimpleMessage::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('mime.message')
+ );
+
+ if (!isset($charset)) {
+ $charset = Swift_DependencyContainer::getInstance()
+ ->lookup('properties.charset');
+ }
+ $this->setSubject($subject);
+ $this->setBody($body);
+ $this->setCharset($charset);
+ if ($contentType) {
+ $this->setContentType($contentType);
+ }
+ }
+
+ /**
+ * Create a new Message.
+ *
+ * @param string $subject
+ * @param string $body
+ * @param string $contentType
+ * @param string $charset
+ *
+ * @return $this
+ */
+ public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null)
+ {
+ return new self($subject, $body, $contentType, $charset);
+ }
+
+ /**
+ * Add a MimePart to this Message.
+ *
+ * @param string|Swift_OutputByteStream $body
+ * @param string $contentType
+ * @param string $charset
+ *
+ * @return $this
+ */
+ public function addPart($body, $contentType = null, $charset = null)
+ {
+ return $this->attach(Swift_MimePart::newInstance($body, $contentType, $charset)->setEncoder($this->getEncoder()));
+ }
+
+ /**
+ * Detach a signature handler from a message.
+ *
+ * @param Swift_Signer $signer
+ *
+ * @return $this
+ */
+ public function attachSigner(Swift_Signer $signer)
+ {
+ if ($signer instanceof Swift_Signers_HeaderSigner) {
+ $this->headerSigners[] = $signer;
+ } elseif ($signer instanceof Swift_Signers_BodySigner) {
+ $this->bodySigners[] = $signer;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Attach a new signature handler to the message.
+ *
+ * @param Swift_Signer $signer
+ *
+ * @return $this
+ */
+ public function detachSigner(Swift_Signer $signer)
+ {
+ if ($signer instanceof Swift_Signers_HeaderSigner) {
+ foreach ($this->headerSigners as $k => $headerSigner) {
+ if ($headerSigner === $signer) {
+ unset($this->headerSigners[$k]);
+
+ return $this;
+ }
+ }
+ } elseif ($signer instanceof Swift_Signers_BodySigner) {
+ foreach ($this->bodySigners as $k => $bodySigner) {
+ if ($bodySigner === $signer) {
+ unset($this->bodySigners[$k]);
+
+ return $this;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get this message as a complete string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ if (empty($this->headerSigners) && empty($this->bodySigners)) {
+ return parent::toString();
+ }
+
+ $this->saveMessage();
+
+ $this->doSign();
+
+ $string = parent::toString();
+
+ $this->restoreMessage();
+
+ return $string;
+ }
+
+ /**
+ * Write this message to a {@link Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ if (empty($this->headerSigners) && empty($this->bodySigners)) {
+ parent::toByteStream($is);
+
+ return;
+ }
+
+ $this->saveMessage();
+
+ $this->doSign();
+
+ parent::toByteStream($is);
+
+ $this->restoreMessage();
+ }
+
+ public function __wakeup()
+ {
+ Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message');
+ }
+
+ /**
+ * loops through signers and apply the signatures.
+ */
+ protected function doSign()
+ {
+ foreach ($this->bodySigners as $signer) {
+ $altered = $signer->getAlteredHeaders();
+ $this->saveHeaders($altered);
+ $signer->signMessage($this);
+ }
+
+ foreach ($this->headerSigners as $signer) {
+ $altered = $signer->getAlteredHeaders();
+ $this->saveHeaders($altered);
+ $signer->reset();
+
+ $signer->setHeaders($this->getHeaders());
+
+ $signer->startBody();
+ $this->_bodyToByteStream($signer);
+ $signer->endBody();
+
+ $signer->addSignature($this->getHeaders());
+ }
+ }
+
+ /**
+ * save the message before any signature is applied.
+ */
+ protected function saveMessage()
+ {
+ $this->savedMessage = array('headers' => array());
+ $this->savedMessage['body'] = $this->getBody();
+ $this->savedMessage['children'] = $this->getChildren();
+ if (count($this->savedMessage['children']) > 0 && $this->getBody() != '') {
+ $this->setChildren(array_merge(array($this->_becomeMimePart()), $this->savedMessage['children']));
+ $this->setBody('');
+ }
+ }
+
+ /**
+ * save the original headers.
+ *
+ * @param array $altered
+ */
+ protected function saveHeaders(array $altered)
+ {
+ foreach ($altered as $head) {
+ $lc = strtolower($head);
+
+ if (!isset($this->savedMessage['headers'][$lc])) {
+ $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head);
+ }
+ }
+ }
+
+ /**
+ * Remove or restore altered headers.
+ */
+ protected function restoreHeaders()
+ {
+ foreach ($this->savedMessage['headers'] as $name => $savedValue) {
+ $headers = $this->getHeaders()->getAll($name);
+
+ foreach ($headers as $key => $value) {
+ if (!isset($savedValue[$key])) {
+ $this->getHeaders()->remove($name, $key);
+ }
+ }
+ }
+ }
+
+ /**
+ * Restore message body.
+ */
+ protected function restoreMessage()
+ {
+ $this->setBody($this->savedMessage['body']);
+ $this->setChildren($this->savedMessage['children']);
+
+ $this->restoreHeaders();
+ $this->savedMessage = array();
+ }
+
+ /**
+ * Clone Message Signers.
+ *
+ * @see Swift_Mime_SimpleMimeEntity::__clone()
+ */
+ public function __clone()
+ {
+ parent::__clone();
+ foreach ($this->bodySigners as $key => $bodySigner) {
+ $this->bodySigners[$key] = clone $bodySigner;
+ }
+
+ foreach ($this->headerSigners as $key => $headerSigner) {
+ $this->headerSigners[$key] = clone $headerSigner;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php
new file mode 100644
index 0000000..d5ba14b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php
@@ -0,0 +1,149 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An attachment, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Attachment extends Swift_Mime_SimpleMimeEntity
+{
+ /** Recognized MIME types */
+ private $_mimeTypes = array();
+
+ /**
+ * Create a new Attachment with $headers, $encoder and $cache.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ * @param array $mimeTypes optional
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $mimeTypes = array())
+ {
+ parent::__construct($headers, $encoder, $cache, $grammar);
+ $this->setDisposition('attachment');
+ $this->setContentType('application/octet-stream');
+ $this->_mimeTypes = $mimeTypes;
+ }
+
+ /**
+ * Get the nesting level used for this attachment.
+ *
+ * Always returns {@link LEVEL_MIXED}.
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return self::LEVEL_MIXED;
+ }
+
+ /**
+ * Get the Content-Disposition of this attachment.
+ *
+ * By default attachments have a disposition of "attachment".
+ *
+ * @return string
+ */
+ public function getDisposition()
+ {
+ return $this->_getHeaderFieldModel('Content-Disposition');
+ }
+
+ /**
+ * Set the Content-Disposition of this attachment.
+ *
+ * @param string $disposition
+ *
+ * @return $this
+ */
+ public function setDisposition($disposition)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Disposition', $disposition)) {
+ $this->getHeaders()->addParameterizedHeader('Content-Disposition', $disposition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the filename of this attachment when downloaded.
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_getHeaderParameter('Content-Disposition', 'filename');
+ }
+
+ /**
+ * Set the filename of this attachment.
+ *
+ * @param string $filename
+ *
+ * @return $this
+ */
+ public function setFilename($filename)
+ {
+ $this->_setHeaderParameter('Content-Disposition', 'filename', $filename);
+ $this->_setHeaderParameter('Content-Type', 'name', $filename);
+
+ return $this;
+ }
+
+ /**
+ * Get the file size of this attachment.
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return $this->_getHeaderParameter('Content-Disposition', 'size');
+ }
+
+ /**
+ * Set the file size of this attachment.
+ *
+ * @param int $size
+ *
+ * @return $this
+ */
+ public function setSize($size)
+ {
+ $this->_setHeaderParameter('Content-Disposition', 'size', $size);
+
+ return $this;
+ }
+
+ /**
+ * Set the file that this attachment is for.
+ *
+ * @param Swift_FileStream $file
+ * @param string $contentType optional
+ *
+ * @return $this
+ */
+ public function setFile(Swift_FileStream $file, $contentType = null)
+ {
+ $this->setFilename(basename($file->getPath()));
+ $this->setBody($file, $contentType);
+ if (!isset($contentType)) {
+ $extension = strtolower(substr($file->getPath(), strrpos($file->getPath(), '.') + 1));
+
+ if (array_key_exists($extension, $this->_mimeTypes)) {
+ $this->setContentType($this->_mimeTypes[$extension]);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php
new file mode 100644
index 0000000..b49c3a8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Observes changes in an Mime entity's character set.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_CharsetObserver
+{
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php
new file mode 100644
index 0000000..d43ea9f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for all Transfer Encoding schemes.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_ContentEncoder extends Swift_Encoder
+{
+ /**
+ * Encode $in to $out.
+ *
+ * @param Swift_OutputByteStream $os to read from
+ * @param Swift_InputByteStream $is to write to
+ * @param int $firstLineOffset
+ * @param int $maxLineLength - 0 indicates the default length for this encoding
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0);
+
+ /**
+ * Get the MIME name of this content encoding scheme.
+ *
+ * @return string
+ */
+ public function getName();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php
new file mode 100644
index 0000000..8f76d70
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php
@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Base 64 Transfer Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_ContentEncoder_Base64ContentEncoder extends Swift_Encoder_Base64Encoder implements Swift_Mime_ContentEncoder
+{
+ /**
+ * Encode stream $in to stream $out.
+ *
+ * @param Swift_OutputByteStream $os
+ * @param Swift_InputByteStream $is
+ * @param int $firstLineOffset
+ * @param int $maxLineLength, optional, 0 indicates the default of 76 bytes
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if (0 >= $maxLineLength || 76 < $maxLineLength) {
+ $maxLineLength = 76;
+ }
+
+ $remainder = 0;
+ $base64ReadBufferRemainderBytes = null;
+
+ // To reduce memory usage, the output buffer is streamed to the input buffer like so:
+ // Output Stream => base64encode => wrap line length => Input Stream
+ // HOWEVER it's important to note that base64_encode() should only be passed whole triplets of data (except for the final chunk of data)
+ // otherwise it will assume the input data has *ended* and it will incorrectly pad/terminate the base64 data mid-stream.
+ // We use $base64ReadBufferRemainderBytes to carry over 1-2 "remainder" bytes from the each chunk from OutputStream and pre-pend those onto the
+ // chunk of bytes read in the next iteration.
+ // When the OutputStream is empty, we must flush any remainder bytes.
+ while (true) {
+ $readBytes = $os->read(8192);
+ $atEOF = ($readBytes === false);
+
+ if ($atEOF) {
+ $streamTheseBytes = $base64ReadBufferRemainderBytes;
+ } else {
+ $streamTheseBytes = $base64ReadBufferRemainderBytes.$readBytes;
+ }
+ $base64ReadBufferRemainderBytes = null;
+ $bytesLength = strlen($streamTheseBytes);
+
+ if ($bytesLength === 0) { // no data left to encode
+ break;
+ }
+
+ // if we're not on the last block of the ouput stream, make sure $streamTheseBytes ends with a complete triplet of data
+ // and carry over remainder 1-2 bytes to the next loop iteration
+ if (!$atEOF) {
+ $excessBytes = $bytesLength % 3;
+ if ($excessBytes !== 0) {
+ $base64ReadBufferRemainderBytes = substr($streamTheseBytes, -$excessBytes);
+ $streamTheseBytes = substr($streamTheseBytes, 0, $bytesLength - $excessBytes);
+ }
+ }
+
+ $encoded = base64_encode($streamTheseBytes);
+ $encodedTransformed = '';
+ $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset;
+
+ while ($thisMaxLineLength < strlen($encoded)) {
+ $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength)."\r\n";
+ $firstLineOffset = 0;
+ $encoded = substr($encoded, $thisMaxLineLength);
+ $thisMaxLineLength = $maxLineLength;
+ $remainder = 0;
+ }
+
+ if (0 < $remainingLength = strlen($encoded)) {
+ $remainder += $remainingLength;
+ $encodedTransformed .= $encoded;
+ $encoded = null;
+ }
+
+ $is->write($encodedTransformed);
+
+ if ($atEOF) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get the name of this encoding scheme.
+ * Returns the string 'base64'.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'base64';
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php
new file mode 100644
index 0000000..710b5ac
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php
@@ -0,0 +1,123 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Quoted Printable (QP) Transfer Encoding in Swift Mailer using the PHP core function.
+ *
+ * @author Lars Strojny
+ */
+class Swift_Mime_ContentEncoder_NativeQpContentEncoder implements Swift_Mime_ContentEncoder
+{
+ /**
+ * @var null|string
+ */
+ private $charset;
+
+ /**
+ * @param null|string $charset
+ */
+ public function __construct($charset = null)
+ {
+ $this->charset = $charset ? $charset : 'utf-8';
+ }
+
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Encode $in to $out.
+ *
+ * @param Swift_OutputByteStream $os to read from
+ * @param Swift_InputByteStream $is to write to
+ * @param int $firstLineOffset
+ * @param int $maxLineLength 0 indicates the default length for this encoding
+ *
+ * @throws RuntimeException
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if ($this->charset !== 'utf-8') {
+ throw new RuntimeException(
+ sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset));
+ }
+
+ $string = '';
+
+ while (false !== $bytes = $os->read(8192)) {
+ $string .= $bytes;
+ }
+
+ $is->write($this->encodeString($string));
+ }
+
+ /**
+ * Get the MIME name of this content encoding scheme.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'quoted-printable';
+ }
+
+ /**
+ * Encode a given string to produce an encoded string.
+ *
+ * @param string $string
+ * @param int $firstLineOffset if first line needs to be shorter
+ * @param int $maxLineLength 0 indicates the default length for this encoding
+ *
+ * @throws RuntimeException
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if ($this->charset !== 'utf-8') {
+ throw new RuntimeException(
+ sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset));
+ }
+
+ return $this->_standardize(quoted_printable_encode($string));
+ }
+
+ /**
+ * Make sure CRLF is correct and HT/SPACE are in valid places.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ protected function _standardize($string)
+ {
+ // transform CR or LF to CRLF
+ $string = preg_replace('~=0D(?!=0A)|(?<!=0D)=0A~', '=0D=0A', $string);
+ // transform =0D=0A to CRLF
+ $string = str_replace(array("\t=0D=0A", ' =0D=0A', '=0D=0A'), array("=09\r\n", "=20\r\n", "\r\n"), $string);
+
+ switch ($end = ord(substr($string, -1))) {
+ case 0x09:
+ $string = substr_replace($string, '=09', -1);
+ break;
+ case 0x20:
+ $string = substr_replace($string, '=20', -1);
+ break;
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php
new file mode 100644
index 0000000..219f482
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php
@@ -0,0 +1,162 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles binary/7/8-bit Transfer Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_ContentEncoder_PlainContentEncoder implements Swift_Mime_ContentEncoder
+{
+ /**
+ * The name of this encoding scheme (probably 7bit or 8bit).
+ *
+ * @var string
+ */
+ private $_name;
+
+ /**
+ * True if canonical transformations should be done.
+ *
+ * @var bool
+ */
+ private $_canonical;
+
+ /**
+ * Creates a new PlainContentEncoder with $name (probably 7bit or 8bit).
+ *
+ * @param string $name
+ * @param bool $canonical If canonicalization transformation should be done.
+ */
+ public function __construct($name, $canonical = false)
+ {
+ $this->_name = $name;
+ $this->_canonical = $canonical;
+ }
+
+ /**
+ * Encode a given string to produce an encoded string.
+ *
+ * @param string $string
+ * @param int $firstLineOffset ignored
+ * @param int $maxLineLength - 0 means no wrapping will occur
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if ($this->_canonical) {
+ $string = $this->_canonicalize($string);
+ }
+
+ return $this->_safeWordWrap($string, $maxLineLength, "\r\n");
+ }
+
+ /**
+ * Encode stream $in to stream $out.
+ *
+ * @param Swift_OutputByteStream $os
+ * @param Swift_InputByteStream $is
+ * @param int $firstLineOffset ignored
+ * @param int $maxLineLength optional, 0 means no wrapping will occur
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ $leftOver = '';
+ while (false !== $bytes = $os->read(8192)) {
+ $toencode = $leftOver.$bytes;
+ if ($this->_canonical) {
+ $toencode = $this->_canonicalize($toencode);
+ }
+ $wrapped = $this->_safeWordWrap($toencode, $maxLineLength, "\r\n");
+ $lastLinePos = strrpos($wrapped, "\r\n");
+ $leftOver = substr($wrapped, $lastLinePos);
+ $wrapped = substr($wrapped, 0, $lastLinePos);
+
+ $is->write($wrapped);
+ }
+ if (strlen($leftOver)) {
+ $is->write($leftOver);
+ }
+ }
+
+ /**
+ * Get the name of this encoding scheme.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Not used.
+ */
+ public function charsetChanged($charset)
+ {
+ }
+
+ /**
+ * A safer (but weaker) wordwrap for unicode.
+ *
+ * @param string $string
+ * @param int $length
+ * @param string $le
+ *
+ * @return string
+ */
+ private function _safeWordwrap($string, $length = 75, $le = "\r\n")
+ {
+ if (0 >= $length) {
+ return $string;
+ }
+
+ $originalLines = explode($le, $string);
+
+ $lines = array();
+ $lineCount = 0;
+
+ foreach ($originalLines as $originalLine) {
+ $lines[] = '';
+ $currentLine = &$lines[$lineCount++];
+
+ //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine);
+ $chunks = preg_split('/(?<=\s)/', $originalLine);
+
+ foreach ($chunks as $chunk) {
+ if (0 != strlen($currentLine)
+ && strlen($currentLine.$chunk) > $length) {
+ $lines[] = '';
+ $currentLine = &$lines[$lineCount++];
+ }
+ $currentLine .= $chunk;
+ }
+ }
+
+ return implode("\r\n", $lines);
+ }
+
+ /**
+ * Canonicalize string input (fix CRLF).
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ private function _canonicalize($string)
+ {
+ return str_replace(
+ array("\r\n", "\r", "\n"),
+ array("\n", "\n", "\r\n"),
+ $string
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php
new file mode 100644
index 0000000..5cc907b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Quoted Printable (QP) Transfer Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_ContentEncoder_QpContentEncoder extends Swift_Encoder_QpEncoder implements Swift_Mime_ContentEncoder
+{
+ protected $_dotEscape;
+
+ /**
+ * Creates a new QpContentEncoder for the given CharacterStream.
+ *
+ * @param Swift_CharacterStream $charStream to use for reading characters
+ * @param Swift_StreamFilter $filter if canonicalization should occur
+ * @param bool $dotEscape if dot stuffing workaround must be enabled
+ */
+ public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null, $dotEscape = false)
+ {
+ $this->_dotEscape = $dotEscape;
+ parent::__construct($charStream, $filter);
+ }
+
+ public function __sleep()
+ {
+ return array('_charStream', '_filter', '_dotEscape');
+ }
+
+ protected function getSafeMapShareId()
+ {
+ return get_class($this).($this->_dotEscape ? '.dotEscape' : '');
+ }
+
+ protected function initSafeMap()
+ {
+ parent::initSafeMap();
+ if ($this->_dotEscape) {
+ /* Encode . as =2e for buggy remote servers */
+ unset($this->_safeMap[0x2e]);
+ }
+ }
+
+ /**
+ * Encode stream $in to stream $out.
+ *
+ * QP encoded strings have a maximum line length of 76 characters.
+ * If the first line needs to be shorter, indicate the difference with
+ * $firstLineOffset.
+ *
+ * @param Swift_OutputByteStream $os output stream
+ * @param Swift_InputByteStream $is input stream
+ * @param int $firstLineOffset
+ * @param int $maxLineLength
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ if ($maxLineLength > 76 || $maxLineLength <= 0) {
+ $maxLineLength = 76;
+ }
+
+ $thisLineLength = $maxLineLength - $firstLineOffset;
+
+ $this->_charStream->flushContents();
+ $this->_charStream->importByteStream($os);
+
+ $currentLine = '';
+ $prepend = '';
+ $size = $lineLen = 0;
+
+ while (false !== $bytes = $this->_nextSequence()) {
+ // If we're filtering the input
+ if (isset($this->_filter)) {
+ // If we can't filter because we need more bytes
+ while ($this->_filter->shouldBuffer($bytes)) {
+ // Then collect bytes into the buffer
+ if (false === $moreBytes = $this->_nextSequence(1)) {
+ break;
+ }
+
+ foreach ($moreBytes as $b) {
+ $bytes[] = $b;
+ }
+ }
+ // And filter them
+ $bytes = $this->_filter->filter($bytes);
+ }
+
+ $enc = $this->_encodeByteSequence($bytes, $size);
+
+ $i = strpos($enc, '=0D=0A');
+ $newLineLength = $lineLen + ($i === false ? $size : $i);
+
+ if ($currentLine && $newLineLength >= $thisLineLength) {
+ $is->write($prepend.$this->_standardize($currentLine));
+ $currentLine = '';
+ $prepend = "=\r\n";
+ $thisLineLength = $maxLineLength;
+ $lineLen = 0;
+ }
+
+ $currentLine .= $enc;
+
+ if ($i === false) {
+ $lineLen += $size;
+ } else {
+ // 6 is the length of '=0D=0A'.
+ $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
+ }
+ }
+ if (strlen($currentLine)) {
+ $is->write($prepend.$this->_standardize($currentLine));
+ }
+ }
+
+ /**
+ * Get the name of this encoding scheme.
+ * Returns the string 'quoted-printable'.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'quoted-printable';
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php
new file mode 100644
index 0000000..3214e1c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Proxy for quoted-printable content encoders.
+ *
+ * Switches on the best QP encoder implementation for current charset.
+ *
+ * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
+ */
+class Swift_Mime_ContentEncoder_QpContentEncoderProxy implements Swift_Mime_ContentEncoder
+{
+ /**
+ * @var Swift_Mime_ContentEncoder_QpContentEncoder
+ */
+ private $safeEncoder;
+
+ /**
+ * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder
+ */
+ private $nativeEncoder;
+
+ /**
+ * @var null|string
+ */
+ private $charset;
+
+ /**
+ * Constructor.
+ *
+ * @param Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder
+ * @param Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder
+ * @param string|null $charset
+ */
+ public function __construct(Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder, $charset)
+ {
+ $this->safeEncoder = $safeEncoder;
+ $this->nativeEncoder = $nativeEncoder;
+ $this->charset = $charset;
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->safeEncoder = clone $this->safeEncoder;
+ $this->nativeEncoder = clone $this->nativeEncoder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function charsetChanged($charset)
+ {
+ $this->charset = $charset;
+ $this->safeEncoder->charsetChanged($charset);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ $this->getEncoder()->encodeByteStream($os, $is, $firstLineOffset, $maxLineLength);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'quoted-printable';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ return $this->getEncoder()->encodeString($string, $firstLineOffset, $maxLineLength);
+ }
+
+ /**
+ * @return Swift_Mime_ContentEncoder
+ */
+ private function getEncoder()
+ {
+ return 'utf-8' === $this->charset ? $this->nativeEncoder : $this->safeEncoder;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php
new file mode 100644
index 0000000..0b8526e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles raw Transfer Encoding in Swift Mailer.
+ *
+ *
+ * @author Sebastiaan Stok <s.stok@rollerscapes.net>
+ */
+class Swift_Mime_ContentEncoder_RawContentEncoder implements Swift_Mime_ContentEncoder
+{
+ /**
+ * Encode a given string to produce an encoded string.
+ *
+ * @param string $string
+ * @param int $firstLineOffset ignored
+ * @param int $maxLineLength ignored
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ return $string;
+ }
+
+ /**
+ * Encode stream $in to stream $out.
+ *
+ * @param Swift_OutputByteStream $in
+ * @param Swift_InputByteStream $out
+ * @param int $firstLineOffset ignored
+ * @param int $maxLineLength ignored
+ */
+ public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ while (false !== ($bytes = $os->read(8192))) {
+ $is->write($bytes);
+ }
+ }
+
+ /**
+ * Get the name of this encoding scheme.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'raw';
+ }
+
+ /**
+ * Not used.
+ */
+ public function charsetChanged($charset)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php
new file mode 100644
index 0000000..6af7571
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An embedded file, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_EmbeddedFile extends Swift_Mime_Attachment
+{
+ /**
+ * Creates a new Attachment with $headers and $encoder.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ * @param array $mimeTypes optional
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $mimeTypes = array())
+ {
+ parent::__construct($headers, $encoder, $cache, $grammar, $mimeTypes);
+ $this->setDisposition('inline');
+ $this->setId($this->getId());
+ }
+
+ /**
+ * Get the nesting level of this EmbeddedFile.
+ *
+ * Returns {@see LEVEL_RELATED}.
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return self::LEVEL_RELATED;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php
new file mode 100644
index 0000000..cc44a6e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Observes changes for a Mime entity's ContentEncoder.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_EncodingObserver
+{
+ /**
+ * Notify this observer that the observed entity's ContentEncoder has changed.
+ *
+ * @param Swift_Mime_ContentEncoder $encoder
+ */
+ public function encoderChanged(Swift_Mime_ContentEncoder $encoder);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Grammar.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Grammar.php
new file mode 100644
index 0000000..a09f338
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Grammar.php
@@ -0,0 +1,176 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Defines the grammar to use for validation, implements the RFC 2822 (and friends) ABNF grammar definitions.
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Grammar
+{
+ /**
+ * Special characters used in the syntax which need to be escaped.
+ *
+ * @var string[]
+ */
+ private static $_specials = array();
+
+ /**
+ * Tokens defined in RFC 2822 (and some related RFCs).
+ *
+ * @var string[]
+ */
+ private static $_grammar = array();
+
+ /**
+ * Initialize some RFC 2822 (and friends) ABNF grammar definitions.
+ */
+ public function __construct()
+ {
+ $this->init();
+ }
+
+ public function __wakeup()
+ {
+ $this->init();
+ }
+
+ protected function init()
+ {
+ if (count(self::$_specials) > 0) {
+ return;
+ }
+
+ self::$_specials = array(
+ '(', ')', '<', '>', '[', ']',
+ ':', ';', '@', ',', '.', '"',
+ );
+
+ /*** Refer to RFC 2822 for ABNF grammar ***/
+
+ // All basic building blocks
+ self::$_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]';
+ self::$_grammar['WSP'] = '[ \t]';
+ self::$_grammar['CRLF'] = '(?:\r\n)';
+ self::$_grammar['FWS'] = '(?:(?:'.self::$_grammar['WSP'].'*'.
+ self::$_grammar['CRLF'].')?'.self::$_grammar['WSP'].')';
+ self::$_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]';
+ self::$_grammar['quoted-pair'] = '(?:\\\\'.self::$_grammar['text'].')';
+ self::$_grammar['ctext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
+ '|[\x21-\x27\x2A-\x5B\x5D-\x7E])';
+ // Uses recursive PCRE (?1) -- could be a weak point??
+ self::$_grammar['ccontent'] = '(?:'.self::$_grammar['ctext'].'|'.
+ self::$_grammar['quoted-pair'].'|(?1))';
+ self::$_grammar['comment'] = '(\((?:'.self::$_grammar['FWS'].'|'.
+ self::$_grammar['ccontent'].')*'.self::$_grammar['FWS'].'?\))';
+ self::$_grammar['CFWS'] = '(?:(?:'.self::$_grammar['FWS'].'?'.
+ self::$_grammar['comment'].')*(?:(?:'.self::$_grammar['FWS'].'?'.
+ self::$_grammar['comment'].')|'.self::$_grammar['FWS'].'))';
+ self::$_grammar['qtext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
+ '|[\x21\x23-\x5B\x5D-\x7E])';
+ self::$_grammar['qcontent'] = '(?:'.self::$_grammar['qtext'].'|'.
+ self::$_grammar['quoted-pair'].')';
+ self::$_grammar['quoted-string'] = '(?:'.self::$_grammar['CFWS'].'?"'.
+ '('.self::$_grammar['FWS'].'?'.self::$_grammar['qcontent'].')*'.
+ self::$_grammar['FWS'].'?"'.self::$_grammar['CFWS'].'?)';
+ self::$_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]';
+ self::$_grammar['atom'] = '(?:'.self::$_grammar['CFWS'].'?'.
+ self::$_grammar['atext'].'+'.self::$_grammar['CFWS'].'?)';
+ self::$_grammar['dot-atom-text'] = '(?:'.self::$_grammar['atext'].'+'.
+ '(\.'.self::$_grammar['atext'].'+)*)';
+ self::$_grammar['dot-atom'] = '(?:'.self::$_grammar['CFWS'].'?'.
+ self::$_grammar['dot-atom-text'].'+'.self::$_grammar['CFWS'].'?)';
+ self::$_grammar['word'] = '(?:'.self::$_grammar['atom'].'|'.
+ self::$_grammar['quoted-string'].')';
+ self::$_grammar['phrase'] = '(?:'.self::$_grammar['word'].'+?)';
+ self::$_grammar['no-fold-quote'] = '(?:"(?:'.self::$_grammar['qtext'].
+ '|'.self::$_grammar['quoted-pair'].')*")';
+ self::$_grammar['dtext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
+ '|[\x21-\x5A\x5E-\x7E])';
+ self::$_grammar['no-fold-literal'] = '(?:\[(?:'.self::$_grammar['dtext'].
+ '|'.self::$_grammar['quoted-pair'].')*\])';
+
+ // Message IDs
+ self::$_grammar['id-left'] = '(?:'.self::$_grammar['dot-atom-text'].'|'.
+ self::$_grammar['no-fold-quote'].')';
+ self::$_grammar['id-right'] = '(?:'.self::$_grammar['dot-atom-text'].'|'.
+ self::$_grammar['no-fold-literal'].')';
+
+ // Addresses, mailboxes and paths
+ self::$_grammar['local-part'] = '(?:'.self::$_grammar['dot-atom'].'|'.
+ self::$_grammar['quoted-string'].')';
+ self::$_grammar['dcontent'] = '(?:'.self::$_grammar['dtext'].'|'.
+ self::$_grammar['quoted-pair'].')';
+ self::$_grammar['domain-literal'] = '(?:'.self::$_grammar['CFWS'].'?\[('.
+ self::$_grammar['FWS'].'?'.self::$_grammar['dcontent'].')*?'.
+ self::$_grammar['FWS'].'?\]'.self::$_grammar['CFWS'].'?)';
+ self::$_grammar['domain'] = '(?:'.self::$_grammar['dot-atom'].'|'.
+ self::$_grammar['domain-literal'].')';
+ self::$_grammar['addr-spec'] = '(?:'.self::$_grammar['local-part'].'@'.
+ self::$_grammar['domain'].')';
+ }
+
+ /**
+ * Get the grammar defined for $name token.
+ *
+ * @param string $name exactly as written in the RFC
+ *
+ * @return string
+ */
+ public function getDefinition($name)
+ {
+ if (array_key_exists($name, self::$_grammar)) {
+ return self::$_grammar[$name];
+ }
+
+ throw new Swift_RfcComplianceException(
+ "No such grammar '".$name."' defined."
+ );
+ }
+
+ /**
+ * Returns the tokens defined in RFC 2822 (and some related RFCs).
+ *
+ * @return array
+ */
+ public function getGrammarDefinitions()
+ {
+ return self::$_grammar;
+ }
+
+ /**
+ * Returns the current special characters used in the syntax which need to be escaped.
+ *
+ * @return array
+ */
+ public function getSpecials()
+ {
+ return self::$_specials;
+ }
+
+ /**
+ * Escape special characters in a string (convert to quoted-pairs).
+ *
+ * @param string $token
+ * @param string[] $include additional chars to escape
+ * @param string[] $exclude chars from escaping
+ *
+ * @return string
+ */
+ public function escapeSpecials($token, $include = array(), $exclude = array())
+ {
+ foreach (array_merge(array('\\'), array_diff(self::$_specials, $exclude), $include) as $char) {
+ $token = str_replace($char, '\\'.$char, $token);
+ }
+
+ return $token;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php
new file mode 100644
index 0000000..a8ddd27
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME Header.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_Header
+{
+ /** Text headers */
+ const TYPE_TEXT = 2;
+
+ /** headers (text + params) */
+ const TYPE_PARAMETERIZED = 6;
+
+ /** Mailbox and address headers */
+ const TYPE_MAILBOX = 8;
+
+ /** Date and time headers */
+ const TYPE_DATE = 16;
+
+ /** Identification headers */
+ const TYPE_ID = 32;
+
+ /** Address path headers */
+ const TYPE_PATH = 64;
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType();
+
+ /**
+ * Set the model for the field body.
+ *
+ * The actual types needed will vary depending upon the type of Header.
+ *
+ * @param mixed $model
+ */
+ public function setFieldBodyModel($model);
+
+ /**
+ * Set the charset used when rendering the Header.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset);
+
+ /**
+ * Get the model for the field body.
+ *
+ * The return type depends on the specifics of the Header.
+ *
+ * @return mixed
+ */
+ public function getFieldBodyModel();
+
+ /**
+ * Get the name of this header (e.g. Subject).
+ *
+ * The name is an identifier and as such will be immutable.
+ *
+ * @return string
+ */
+ public function getFieldName();
+
+ /**
+ * Get the field body, prepared for folding into a final header value.
+ *
+ * @return string
+ */
+ public function getFieldBody();
+
+ /**
+ * Get this Header rendered as a compliant string.
+ *
+ * @return string
+ */
+ public function toString();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php
new file mode 100644
index 0000000..08fd453
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for all Header Encoding schemes.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_HeaderEncoder extends Swift_Encoder
+{
+ /**
+ * Get the MIME name of this content encoding scheme.
+ *
+ * @return string
+ */
+ public function getName();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php
new file mode 100644
index 0000000..83a4f2f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Base64 (B) Header Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_HeaderEncoder_Base64HeaderEncoder extends Swift_Encoder_Base64Encoder implements Swift_Mime_HeaderEncoder
+{
+ /**
+ * Get the name of this encoding scheme.
+ * Returns the string 'B'.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'B';
+ }
+
+ /**
+ * Takes an unencoded string and produces a Base64 encoded string from it.
+ *
+ * If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of
+ * default encodeString, otherwise pass to the parent method.
+ *
+ * @param string $string string to encode
+ * @param int $firstLineOffset
+ * @param int $maxLineLength optional, 0 indicates the default of 76 bytes
+ * @param string $charset
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0, $charset = 'utf-8')
+ {
+ if (strtolower($charset) === 'iso-2022-jp') {
+ $old = mb_internal_encoding();
+ mb_internal_encoding('utf-8');
+ $newstring = mb_encode_mimeheader($string, $charset, $this->getName(), "\r\n");
+ mb_internal_encoding($old);
+
+ return $newstring;
+ }
+
+ return parent::encodeString($string, $firstLineOffset, $maxLineLength);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php
new file mode 100644
index 0000000..510dd66
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles Quoted Printable (Q) Header Encoding in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_HeaderEncoder_QpHeaderEncoder extends Swift_Encoder_QpEncoder implements Swift_Mime_HeaderEncoder
+{
+ /**
+ * Creates a new QpHeaderEncoder for the given CharacterStream.
+ *
+ * @param Swift_CharacterStream $charStream to use for reading characters
+ */
+ public function __construct(Swift_CharacterStream $charStream)
+ {
+ parent::__construct($charStream);
+ }
+
+ protected function initSafeMap()
+ {
+ foreach (array_merge(
+ range(0x61, 0x7A), range(0x41, 0x5A),
+ range(0x30, 0x39), array(0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F)
+ ) as $byte) {
+ $this->_safeMap[$byte] = chr($byte);
+ }
+ }
+
+ /**
+ * Get the name of this encoding scheme.
+ *
+ * Returns the string 'Q'.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'Q';
+ }
+
+ /**
+ * Takes an unencoded string and produces a QP encoded string from it.
+ *
+ * @param string $string string to encode
+ * @param int $firstLineOffset optional
+ * @param int $maxLineLength optional, 0 indicates the default of 76 chars
+ *
+ * @return string
+ */
+ public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
+ {
+ return str_replace(array(' ', '=20', "=\r\n"), array('_', '_', "\r\n"),
+ parent::encodeString($string, $firstLineOffset, $maxLineLength)
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php
new file mode 100644
index 0000000..c65f26d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php
@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Creates MIME headers.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_HeaderFactory extends Swift_Mime_CharsetObserver
+{
+ /**
+ * Create a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string $addresses
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createMailboxHeader($name, $addresses = null);
+
+ /**
+ * Create a new Date header using $timestamp (UNIX time).
+ *
+ * @param string $name
+ * @param int $timestamp
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createDateHeader($name, $timestamp = null);
+
+ /**
+ * Create a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createTextHeader($name, $value = null);
+
+ /**
+ * Create a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ *
+ * @return Swift_Mime_ParameterizedHeader
+ */
+ public function createParameterizedHeader($name, $value = null, $params = array());
+
+ /**
+ * Create a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createIdHeader($name, $ids = null);
+
+ /**
+ * Create a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createPathHeader($name, $path = null);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderSet.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderSet.php
new file mode 100644
index 0000000..1768709
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderSet.php
@@ -0,0 +1,169 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A collection of MIME headers.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_HeaderSet extends Swift_Mime_CharsetObserver
+{
+ /**
+ * Add a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string $addresses
+ */
+ public function addMailboxHeader($name, $addresses = null);
+
+ /**
+ * Add a new Date header using $timestamp (UNIX time).
+ *
+ * @param string $name
+ * @param int $timestamp
+ */
+ public function addDateHeader($name, $timestamp = null);
+
+ /**
+ * Add a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function addTextHeader($name, $value = null);
+
+ /**
+ * Add a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ */
+ public function addParameterizedHeader($name, $value = null, $params = array());
+
+ /**
+ * Add a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ */
+ public function addIdHeader($name, $ids = null);
+
+ /**
+ * Add a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ */
+ public function addPathHeader($name, $path = null);
+
+ /**
+ * Returns true if at least one header with the given $name exists.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return bool
+ */
+ public function has($name, $index = 0);
+
+ /**
+ * Set a header in the HeaderSet.
+ *
+ * The header may be a previously fetched header via {@link get()} or it may
+ * be one that has been created separately.
+ *
+ * If $index is specified, the header will be inserted into the set at this
+ * offset.
+ *
+ * @param Swift_Mime_Header $header
+ * @param int $index
+ */
+ public function set(Swift_Mime_Header $header, $index = 0);
+
+ /**
+ * Get the header with the given $name.
+ * If multiple headers match, the actual one may be specified by $index.
+ * Returns NULL if none present.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return Swift_Mime_Header
+ */
+ public function get($name, $index = 0);
+
+ /**
+ * Get all headers with the given $name.
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getAll($name = null);
+
+ /**
+ * Return the name of all Headers.
+ *
+ * @return array
+ */
+ public function listAll();
+
+ /**
+ * Remove the header with the given $name if it's set.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ */
+ public function remove($name, $index = 0);
+
+ /**
+ * Remove all headers with the given $name.
+ *
+ * @param string $name
+ */
+ public function removeAll($name);
+
+ /**
+ * Create a new instance of this HeaderSet.
+ *
+ * @return self
+ */
+ public function newInstance();
+
+ /**
+ * Define a list of Header names as an array in the correct order.
+ *
+ * These Headers will be output in the given order where present.
+ *
+ * @param array $sequence
+ */
+ public function defineOrdering(array $sequence);
+
+ /**
+ * Set a list of header names which must always be displayed when set.
+ *
+ * Usually headers without a field value won't be output unless set here.
+ *
+ * @param array $names
+ */
+ public function setAlwaysDisplayed(array $names);
+
+ /**
+ * Returns a string with a representation of all headers.
+ *
+ * @return string
+ */
+ public function toString();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php
new file mode 100644
index 0000000..3a6d7b3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php
@@ -0,0 +1,501 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An abstract base MIME Header.
+ *
+ * @author Chris Corbyn
+ */
+abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
+{
+ /**
+ * The name of this Header.
+ *
+ * @var string
+ */
+ private $_name;
+
+ /**
+ * The Grammar used for this Header.
+ *
+ * @var Swift_Mime_Grammar
+ */
+ private $_grammar;
+
+ /**
+ * The Encoder used to encode this Header.
+ *
+ * @var Swift_Encoder
+ */
+ private $_encoder;
+
+ /**
+ * The maximum length of a line in the header.
+ *
+ * @var int
+ */
+ private $_lineLength = 78;
+
+ /**
+ * The language used in this Header.
+ *
+ * @var string
+ */
+ private $_lang;
+
+ /**
+ * The character set of the text in this Header.
+ *
+ * @var string
+ */
+ private $_charset = 'utf-8';
+
+ /**
+ * The value of this Header, cached.
+ *
+ * @var string
+ */
+ private $_cachedValue = null;
+
+ /**
+ * Creates a new Header.
+ *
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct(Swift_Mime_Grammar $grammar)
+ {
+ $this->setGrammar($grammar);
+ }
+
+ /**
+ * Set the character set used in this Header.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset)
+ {
+ $this->clearCachedValueIf($charset != $this->_charset);
+ $this->_charset = $charset;
+ if (isset($this->_encoder)) {
+ $this->_encoder->charsetChanged($charset);
+ }
+ }
+
+ /**
+ * Get the character set used in this Header.
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->_charset;
+ }
+
+ /**
+ * Set the language used in this Header.
+ *
+ * For example, for US English, 'en-us'.
+ * This can be unspecified.
+ *
+ * @param string $lang
+ */
+ public function setLanguage($lang)
+ {
+ $this->clearCachedValueIf($this->_lang != $lang);
+ $this->_lang = $lang;
+ }
+
+ /**
+ * Get the language used in this Header.
+ *
+ * @return string
+ */
+ public function getLanguage()
+ {
+ return $this->_lang;
+ }
+
+ /**
+ * Set the encoder used for encoding the header.
+ *
+ * @param Swift_Mime_HeaderEncoder $encoder
+ */
+ public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
+ {
+ $this->_encoder = $encoder;
+ $this->setCachedValue(null);
+ }
+
+ /**
+ * Get the encoder used for encoding this Header.
+ *
+ * @return Swift_Mime_HeaderEncoder
+ */
+ public function getEncoder()
+ {
+ return $this->_encoder;
+ }
+
+ /**
+ * Set the grammar used for the header.
+ *
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function setGrammar(Swift_Mime_Grammar $grammar)
+ {
+ $this->_grammar = $grammar;
+ $this->setCachedValue(null);
+ }
+
+ /**
+ * Get the grammar used for this Header.
+ *
+ * @return Swift_Mime_Grammar
+ */
+ public function getGrammar()
+ {
+ return $this->_grammar;
+ }
+
+ /**
+ * Get the name of this header (e.g. charset).
+ *
+ * @return string
+ */
+ public function getFieldName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Set the maximum length of lines in the header (excluding EOL).
+ *
+ * @param int $lineLength
+ */
+ public function setMaxLineLength($lineLength)
+ {
+ $this->clearCachedValueIf($this->_lineLength != $lineLength);
+ $this->_lineLength = $lineLength;
+ }
+
+ /**
+ * Get the maximum permitted length of lines in this Header.
+ *
+ * @return int
+ */
+ public function getMaxLineLength()
+ {
+ return $this->_lineLength;
+ }
+
+ /**
+ * Get this Header rendered as a RFC 2822 compliant string.
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->_tokensToString($this->toTokens());
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ *
+ * @see toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Set the name of this Header field.
+ *
+ * @param string $name
+ */
+ protected function setFieldName($name)
+ {
+ $this->_name = $name;
+ }
+
+ /**
+ * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
+ *
+ * @param Swift_Mime_Header $header
+ * @param string $string as displayed
+ * @param string $charset of the text
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param bool $shorten the first line to make remove for header name
+ *
+ * @return string
+ */
+ protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false)
+ {
+ // Treat token as exactly what was given
+ $phraseStr = $string;
+ // If it's not valid
+ if (!preg_match('/^'.$this->getGrammar()->getDefinition('phrase').'$/D', $phraseStr)) {
+ // .. but it is just ascii text, try escaping some characters
+ // and make it a quoted-string
+ if (preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $phraseStr)) {
+ $phraseStr = $this->getGrammar()->escapeSpecials(
+ $phraseStr, array('"'), $this->getGrammar()->getSpecials()
+ );
+ $phraseStr = '"'.$phraseStr.'"';
+ } else {
+ // ... otherwise it needs encoding
+ // Determine space remaining on line if first line
+ if ($shorten) {
+ $usedLength = strlen($header->getFieldName().': ');
+ } else {
+ $usedLength = 0;
+ }
+ $phraseStr = $this->encodeWords($header, $string, $usedLength);
+ }
+ }
+
+ return $phraseStr;
+ }
+
+ /**
+ * Encode needed word tokens within a string of input.
+ *
+ * @param Swift_Mime_Header $header
+ * @param string $input
+ * @param string $usedLength optional
+ *
+ * @return string
+ */
+ protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1)
+ {
+ $value = '';
+
+ $tokens = $this->getEncodableWordTokens($input);
+
+ foreach ($tokens as $token) {
+ // See RFC 2822, Sect 2.2 (really 2.2 ??)
+ if ($this->tokenNeedsEncoding($token)) {
+ // Don't encode starting WSP
+ $firstChar = substr($token, 0, 1);
+ switch ($firstChar) {
+ case ' ':
+ case "\t":
+ $value .= $firstChar;
+ $token = substr($token, 1);
+ }
+
+ if (-1 == $usedLength) {
+ $usedLength = strlen($header->getFieldName().': ') + strlen($value);
+ }
+ $value .= $this->getTokenAsEncodedWord($token, $usedLength);
+
+ $header->setMaxLineLength(76); // Forcefully override
+ } else {
+ $value .= $token;
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Test if a token needs to be encoded or not.
+ *
+ * @param string $token
+ *
+ * @return bool
+ */
+ protected function tokenNeedsEncoding($token)
+ {
+ return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
+ }
+
+ /**
+ * Splits a string into tokens in blocks of words which can be encoded quickly.
+ *
+ * @param string $string
+ *
+ * @return string[]
+ */
+ protected function getEncodableWordTokens($string)
+ {
+ $tokens = array();
+
+ $encodedToken = '';
+ // Split at all whitespace boundaries
+ foreach (preg_split('~(?=[\t ])~', $string) as $token) {
+ if ($this->tokenNeedsEncoding($token)) {
+ $encodedToken .= $token;
+ } else {
+ if (strlen($encodedToken) > 0) {
+ $tokens[] = $encodedToken;
+ $encodedToken = '';
+ }
+ $tokens[] = $token;
+ }
+ }
+ if (strlen($encodedToken)) {
+ $tokens[] = $encodedToken;
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Get a token as an encoded word for safe insertion into headers.
+ *
+ * @param string $token token to encode
+ * @param int $firstLineOffset optional
+ *
+ * @return string
+ */
+ protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
+ {
+ // Adjust $firstLineOffset to account for space needed for syntax
+ $charsetDecl = $this->_charset;
+ if (isset($this->_lang)) {
+ $charsetDecl .= '*'.$this->_lang;
+ }
+ $encodingWrapperLength = strlen(
+ '=?'.$charsetDecl.'?'.$this->_encoder->getName().'??='
+ );
+
+ if ($firstLineOffset >= 75) {
+ //Does this logic need to be here?
+ $firstLineOffset = 0;
+ }
+
+ $encodedTextLines = explode("\r\n",
+ $this->_encoder->encodeString(
+ $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset
+ )
+ );
+
+ if (strtolower($this->_charset) !== 'iso-2022-jp') {
+ // special encoding for iso-2022-jp using mb_encode_mimeheader
+ foreach ($encodedTextLines as $lineNum => $line) {
+ $encodedTextLines[$lineNum] = '=?'.$charsetDecl.
+ '?'.$this->_encoder->getName().
+ '?'.$line.'?=';
+ }
+ }
+
+ return implode("\r\n ", $encodedTextLines);
+ }
+
+ /**
+ * Generates tokens from the given string which include CRLF as individual tokens.
+ *
+ * @param string $token
+ *
+ * @return string[]
+ */
+ protected function generateTokenLines($token)
+ {
+ return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
+ }
+
+ /**
+ * Set a value into the cache.
+ *
+ * @param string $value
+ */
+ protected function setCachedValue($value)
+ {
+ $this->_cachedValue = $value;
+ }
+
+ /**
+ * Get the value in the cache.
+ *
+ * @return string
+ */
+ protected function getCachedValue()
+ {
+ return $this->_cachedValue;
+ }
+
+ /**
+ * Clear the cached value if $condition is met.
+ *
+ * @param bool $condition
+ */
+ protected function clearCachedValueIf($condition)
+ {
+ if ($condition) {
+ $this->setCachedValue(null);
+ }
+ }
+
+ /**
+ * Generate a list of all tokens in the final header.
+ *
+ * @param string $string The string to tokenize
+ *
+ * @return array An array of tokens as strings
+ */
+ protected function toTokens($string = null)
+ {
+ if (null === $string) {
+ $string = $this->getFieldBody();
+ }
+
+ $tokens = array();
+
+ // Generate atoms; split at all invisible boundaries followed by WSP
+ foreach (preg_split('~(?=[ \t])~', $string) as $token) {
+ $newTokens = $this->generateTokenLines($token);
+ foreach ($newTokens as $newToken) {
+ $tokens[] = $newToken;
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Takes an array of tokens which appear in the header and turns them into
+ * an RFC 2822 compliant string, adding FWSP where needed.
+ *
+ * @param string[] $tokens
+ *
+ * @return string
+ */
+ private function _tokensToString(array $tokens)
+ {
+ $lineCount = 0;
+ $headerLines = array();
+ $headerLines[] = $this->_name.': ';
+ $currentLine = &$headerLines[$lineCount++];
+
+ // Build all tokens back into compliant header
+ foreach ($tokens as $i => $token) {
+ // Line longer than specified maximum or token was just a new line
+ if (("\r\n" == $token) ||
+ ($i > 0 && strlen($currentLine.$token) > $this->_lineLength)
+ && 0 < strlen($currentLine)) {
+ $headerLines[] = '';
+ $currentLine = &$headerLines[$lineCount++];
+ }
+
+ // Append token to the line
+ if ("\r\n" != $token) {
+ $currentLine .= $token;
+ }
+ }
+
+ // Implode with FWS (RFC 2822, 2.2.3)
+ return implode("\r\n", $headerLines)."\r\n";
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php
new file mode 100644
index 0000000..4075cbf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php
@@ -0,0 +1,125 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A Date MIME Header for Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_DateHeader extends Swift_Mime_Headers_AbstractHeader
+{
+ /**
+ * The UNIX timestamp value of this Header.
+ *
+ * @var int
+ */
+ private $_timestamp;
+
+ /**
+ * Creates a new DateHeader with $name and $timestamp.
+ *
+ * Example:
+ * <code>
+ * <?php
+ * $header = new Swift_Mime_Headers_DateHeader('Date', time());
+ * ?>
+ * </code>
+ *
+ * @param string $name of Header
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_Grammar $grammar)
+ {
+ $this->setFieldName($name);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_DATE;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a UNIX timestamp.
+ *
+ * @param int $model
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setTimestamp($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns a UNIX timestamp.
+ *
+ * @return mixed
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getTimestamp();
+ }
+
+ /**
+ * Get the UNIX timestamp of the Date in this Header.
+ *
+ * @return int
+ */
+ public function getTimestamp()
+ {
+ return $this->_timestamp;
+ }
+
+ /**
+ * Set the UNIX timestamp of the Date in this Header.
+ *
+ * @param int $timestamp
+ */
+ public function setTimestamp($timestamp)
+ {
+ if (null !== $timestamp) {
+ $timestamp = (int) $timestamp;
+ }
+ $this->clearCachedValueIf($this->_timestamp != $timestamp);
+ $this->_timestamp = $timestamp;
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@link toString()} for that).
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ if (isset($this->_timestamp)) {
+ $this->setCachedValue(date('r', $this->_timestamp));
+ }
+ }
+
+ return $this->getCachedValue();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php
new file mode 100644
index 0000000..b114506
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An ID MIME Header for something like Message-ID or Content-ID.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_IdentificationHeader extends Swift_Mime_Headers_AbstractHeader
+{
+ /**
+ * The IDs used in the value of this Header.
+ *
+ * This may hold multiple IDs or just a single ID.
+ *
+ * @var string[]
+ */
+ private $_ids = array();
+
+ /**
+ * Creates a new IdentificationHeader with the given $name and $id.
+ *
+ * @param string $name
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_Grammar $grammar)
+ {
+ $this->setFieldName($name);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_ID;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string ID, or an array of IDs.
+ *
+ * @param mixed $model
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setId($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns an array of IDs
+ *
+ * @return array
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getIds();
+ }
+
+ /**
+ * Set the ID used in the value of this header.
+ *
+ * @param string|array $id
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setId($id)
+ {
+ $this->setIds(is_array($id) ? $id : array($id));
+ }
+
+ /**
+ * Get the ID used in the value of this Header.
+ *
+ * If multiple IDs are set only the first is returned.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (count($this->_ids) > 0) {
+ return $this->_ids[0];
+ }
+ }
+
+ /**
+ * Set a collection of IDs to use in the value of this Header.
+ *
+ * @param string[] $ids
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setIds(array $ids)
+ {
+ $actualIds = array();
+
+ foreach ($ids as $id) {
+ $this->_assertValidId($id);
+ $actualIds[] = $id;
+ }
+
+ $this->clearCachedValueIf($this->_ids != $actualIds);
+ $this->_ids = $actualIds;
+ }
+
+ /**
+ * Get the list of IDs used in this Header.
+ *
+ * @return string[]
+ */
+ public function getIds()
+ {
+ return $this->_ids;
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@see toString()} for that).
+ *
+ * @see toString()
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ $angleAddrs = array();
+
+ foreach ($this->_ids as $id) {
+ $angleAddrs[] = '<'.$id.'>';
+ }
+
+ $this->setCachedValue(implode(' ', $angleAddrs));
+ }
+
+ return $this->getCachedValue();
+ }
+
+ /**
+ * Throws an Exception if the id passed does not comply with RFC 2822.
+ *
+ * @param string $id
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ private function _assertValidId($id)
+ {
+ if (!preg_match(
+ '/^'.$this->getGrammar()->getDefinition('id-left').'@'.
+ $this->getGrammar()->getDefinition('id-right').'$/D',
+ $id
+ )) {
+ throw new Swift_RfcComplianceException(
+ 'Invalid ID given <'.$id.'>'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php
new file mode 100644
index 0000000..e4567fc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php
@@ -0,0 +1,351 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A Mailbox Address MIME Header for something like From or Sender.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_MailboxHeader extends Swift_Mime_Headers_AbstractHeader
+{
+ /**
+ * The mailboxes used in this Header.
+ *
+ * @var string[]
+ */
+ private $_mailboxes = array();
+
+ /**
+ * Creates a new MailboxHeader with $name.
+ *
+ * @param string $name of Header
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Mime_Grammar $grammar)
+ {
+ $this->setFieldName($name);
+ $this->setEncoder($encoder);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_MAILBOX;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string, or an array of addresses.
+ *
+ * @param mixed $model
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setNameAddresses($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns an associative array like {@link getNameAddresses()}
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return array
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getNameAddresses();
+ }
+
+ /**
+ * Set a list of mailboxes to be shown in this Header.
+ *
+ * The mailboxes can be a simple array of addresses, or an array of
+ * key=>value pairs where (email => personalName).
+ * Example:
+ * <code>
+ * <?php
+ * //Sets two mailboxes in the Header, one with a personal name
+ * $header->setNameAddresses(array(
+ * 'chris@swiftmailer.org' => 'Chris Corbyn',
+ * 'mark@swiftmailer.org' //No associated personal name
+ * ));
+ * ?>
+ * </code>
+ *
+ * @see __construct()
+ * @see setAddresses()
+ * @see setValue()
+ *
+ * @param string|string[] $mailboxes
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setNameAddresses($mailboxes)
+ {
+ $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes);
+ $this->setCachedValue(null); //Clear any cached value
+ }
+
+ /**
+ * Get the full mailbox list of this Header as an array of valid RFC 2822 strings.
+ *
+ * Example:
+ * <code>
+ * <?php
+ * $header = new Swift_Mime_Headers_MailboxHeader('From',
+ * array('chris@swiftmailer.org' => 'Chris Corbyn',
+ * 'mark@swiftmailer.org' => 'Mark Corbyn')
+ * );
+ * print_r($header->getNameAddressStrings());
+ * // array (
+ * // 0 => Chris Corbyn <chris@swiftmailer.org>,
+ * // 1 => Mark Corbyn <mark@swiftmailer.org>
+ * // )
+ * ?>
+ * </code>
+ *
+ * @see getNameAddresses()
+ * @see toString()
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return string[]
+ */
+ public function getNameAddressStrings()
+ {
+ return $this->_createNameAddressStrings($this->getNameAddresses());
+ }
+
+ /**
+ * Get all mailboxes in this Header as key=>value pairs.
+ *
+ * The key is the address and the value is the name (or null if none set).
+ * Example:
+ * <code>
+ * <?php
+ * $header = new Swift_Mime_Headers_MailboxHeader('From',
+ * array('chris@swiftmailer.org' => 'Chris Corbyn',
+ * 'mark@swiftmailer.org' => 'Mark Corbyn')
+ * );
+ * print_r($header->getNameAddresses());
+ * // array (
+ * // chris@swiftmailer.org => Chris Corbyn,
+ * // mark@swiftmailer.org => Mark Corbyn
+ * // )
+ * ?>
+ * </code>
+ *
+ * @see getAddresses()
+ * @see getNameAddressStrings()
+ *
+ * @return string[]
+ */
+ public function getNameAddresses()
+ {
+ return $this->_mailboxes;
+ }
+
+ /**
+ * Makes this Header represent a list of plain email addresses with no names.
+ *
+ * Example:
+ * <code>
+ * <?php
+ * //Sets three email addresses as the Header data
+ * $header->setAddresses(
+ * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld')
+ * );
+ * ?>
+ * </code>
+ *
+ * @see setNameAddresses()
+ * @see setValue()
+ *
+ * @param string[] $addresses
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setAddresses($addresses)
+ {
+ $this->setNameAddresses(array_values((array) $addresses));
+ }
+
+ /**
+ * Get all email addresses in this Header.
+ *
+ * @see getNameAddresses()
+ *
+ * @return string[]
+ */
+ public function getAddresses()
+ {
+ return array_keys($this->_mailboxes);
+ }
+
+ /**
+ * Remove one or more addresses from this Header.
+ *
+ * @param string|string[] $addresses
+ */
+ public function removeAddresses($addresses)
+ {
+ $this->setCachedValue(null);
+ foreach ((array) $addresses as $address) {
+ unset($this->_mailboxes[$address]);
+ }
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@link toString()} for that).
+ *
+ * @see toString()
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ // Compute the string value of the header only if needed
+ if (null === $this->getCachedValue()) {
+ $this->setCachedValue($this->createMailboxListString($this->_mailboxes));
+ }
+
+ return $this->getCachedValue();
+ }
+
+ /**
+ * Normalizes a user-input list of mailboxes into consistent key=>value pairs.
+ *
+ * @param string[] $mailboxes
+ *
+ * @return string[]
+ */
+ protected function normalizeMailboxes(array $mailboxes)
+ {
+ $actualMailboxes = array();
+
+ foreach ($mailboxes as $key => $value) {
+ if (is_string($key)) {
+ //key is email addr
+ $address = $key;
+ $name = $value;
+ } else {
+ $address = $value;
+ $name = null;
+ }
+ $this->_assertValidAddress($address);
+ $actualMailboxes[$address] = $name;
+ }
+
+ return $actualMailboxes;
+ }
+
+ /**
+ * Produces a compliant, formatted display-name based on the string given.
+ *
+ * @param string $displayName as displayed
+ * @param bool $shorten the first line to make remove for header name
+ *
+ * @return string
+ */
+ protected function createDisplayNameString($displayName, $shorten = false)
+ {
+ return $this->createPhrase($this, $displayName, $this->getCharset(), $this->getEncoder(), $shorten);
+ }
+
+ /**
+ * Creates a string form of all the mailboxes in the passed array.
+ *
+ * @param string[] $mailboxes
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return string
+ */
+ protected function createMailboxListString(array $mailboxes)
+ {
+ return implode(', ', $this->_createNameAddressStrings($mailboxes));
+ }
+
+ /**
+ * Redefine the encoding requirements for mailboxes.
+ *
+ * All "specials" must be encoded as the full header value will not be quoted
+ *
+ * @see RFC 2822 3.2.1
+ *
+ * @param string $token
+ *
+ * @return bool
+ */
+ protected function tokenNeedsEncoding($token)
+ {
+ return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
+ }
+
+ /**
+ * Return an array of strings conforming the the name-addr spec of RFC 2822.
+ *
+ * @param string[] $mailboxes
+ *
+ * @return string[]
+ */
+ private function _createNameAddressStrings(array $mailboxes)
+ {
+ $strings = array();
+
+ foreach ($mailboxes as $email => $name) {
+ $mailboxStr = $email;
+ if (null !== $name) {
+ $nameStr = $this->createDisplayNameString($name, empty($strings));
+ $mailboxStr = $nameStr.' <'.$mailboxStr.'>';
+ }
+ $strings[] = $mailboxStr;
+ }
+
+ return $strings;
+ }
+
+ /**
+ * Throws an Exception if the address passed does not comply with RFC 2822.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException If invalid.
+ */
+ private function _assertValidAddress($address)
+ {
+ if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D',
+ $address)) {
+ throw new Swift_RfcComplianceException(
+ 'Address in mailbox given ['.$address.
+ '] does not comply with RFC 2822, 3.6.2.'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php
new file mode 100644
index 0000000..d749550
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An OpenDKIM Specific Header using only raw header datas without encoding.
+ *
+ * @author De Cock Xavier <xdecock@gmail.com>
+ */
+class Swift_Mime_Headers_OpenDKIMHeader implements Swift_Mime_Header
+{
+ /**
+ * The value of this Header.
+ *
+ * @var string
+ */
+ private $_value;
+
+ /**
+ * The name of this Header.
+ *
+ * @var string
+ */
+ private $_fieldName;
+
+ /**
+ * @param string $name
+ */
+ public function __construct($name)
+ {
+ $this->_fieldName = $name;
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_TEXT;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string for the field value.
+ *
+ * @param string $model
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setValue($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns a string.
+ *
+ * @return string
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Get the (unencoded) value of this header.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set the (unencoded) value of this header.
+ *
+ * @param string $value
+ */
+ public function setValue($value)
+ {
+ $this->_value = $value;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Get this Header rendered as a RFC 2822 compliant string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->_fieldName.': '.$this->_value;
+ }
+
+ /**
+ * Set the Header FieldName.
+ *
+ * @see Swift_Mime_Header::getFieldName()
+ */
+ public function getFieldName()
+ {
+ return $this->_fieldName;
+ }
+
+ /**
+ * Ignored.
+ */
+ public function setCharset($charset)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php
new file mode 100644
index 0000000..c1777d3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php
@@ -0,0 +1,258 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An abstract base MIME Header.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_ParameterizedHeader extends Swift_Mime_Headers_UnstructuredHeader implements Swift_Mime_ParameterizedHeader
+{
+ /**
+ * RFC 2231's definition of a token.
+ *
+ * @var string
+ */
+ const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';
+
+ /**
+ * The Encoder used to encode the parameters.
+ *
+ * @var Swift_Encoder
+ */
+ private $_paramEncoder;
+
+ /**
+ * The parameters as an associative array.
+ *
+ * @var string[]
+ */
+ private $_params = array();
+
+ /**
+ * Creates a new ParameterizedHeader with $name.
+ *
+ * @param string $name
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param Swift_Encoder $paramEncoder, optional
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder = null, Swift_Mime_Grammar $grammar)
+ {
+ parent::__construct($name, $encoder, $grammar);
+ $this->_paramEncoder = $paramEncoder;
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_PARAMETERIZED;
+ }
+
+ /**
+ * Set the character set used in this Header.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset)
+ {
+ parent::setCharset($charset);
+ if (isset($this->_paramEncoder)) {
+ $this->_paramEncoder->charsetChanged($charset);
+ }
+ }
+
+ /**
+ * Set the value of $parameter.
+ *
+ * @param string $parameter
+ * @param string $value
+ */
+ public function setParameter($parameter, $value)
+ {
+ $this->setParameters(array_merge($this->getParameters(), array($parameter => $value)));
+ }
+
+ /**
+ * Get the value of $parameter.
+ *
+ * @param string $parameter
+ *
+ * @return string
+ */
+ public function getParameter($parameter)
+ {
+ $params = $this->getParameters();
+
+ return array_key_exists($parameter, $params) ? $params[$parameter] : null;
+ }
+
+ /**
+ * Set an associative array of parameter names mapped to values.
+ *
+ * @param string[] $parameters
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->clearCachedValueIf($this->_params != $parameters);
+ $this->_params = $parameters;
+ }
+
+ /**
+ * Returns an associative array of parameter names mapped to values.
+ *
+ * @return string[]
+ */
+ public function getParameters()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody() //TODO: Check caching here
+ {
+ $body = parent::getFieldBody();
+ foreach ($this->_params as $name => $value) {
+ if (null !== $value) {
+ // Add the parameter
+ $body .= '; '.$this->_createParameter($name, $value);
+ }
+ }
+
+ return $body;
+ }
+
+ /**
+ * Generate a list of all tokens in the final header.
+ *
+ * This doesn't need to be overridden in theory, but it is for implementation
+ * reasons to prevent potential breakage of attributes.
+ *
+ * @param string $string The string to tokenize
+ *
+ * @return array An array of tokens as strings
+ */
+ protected function toTokens($string = null)
+ {
+ $tokens = parent::toTokens(parent::getFieldBody());
+
+ // Try creating any parameters
+ foreach ($this->_params as $name => $value) {
+ if (null !== $value) {
+ // Add the semi-colon separator
+ $tokens[count($tokens) - 1] .= ';';
+ $tokens = array_merge($tokens, $this->generateTokenLines(
+ ' '.$this->_createParameter($name, $value)
+ ));
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Render a RFC 2047 compliant header parameter from the $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return string
+ */
+ private function _createParameter($name, $value)
+ {
+ $origValue = $value;
+
+ $encoded = false;
+ // Allow room for parameter name, indices, "=" and DQUOTEs
+ $maxValueLength = $this->getMaxLineLength() - strlen($name.'=*N"";') - 1;
+ $firstLineOffset = 0;
+
+ // If it's not already a valid parameter value...
+ if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
+ // TODO: text, or something else??
+ // ... and it's not ascii
+ if (!preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $value)) {
+ $encoded = true;
+ // Allow space for the indices, charset and language
+ $maxValueLength = $this->getMaxLineLength() - strlen($name.'*N*="";') - 1;
+ $firstLineOffset = strlen(
+ $this->getCharset()."'".$this->getLanguage()."'"
+ );
+ }
+ }
+
+ // Encode if we need to
+ if ($encoded || strlen($value) > $maxValueLength) {
+ if (isset($this->_paramEncoder)) {
+ $value = $this->_paramEncoder->encodeString(
+ $origValue, $firstLineOffset, $maxValueLength, $this->getCharset()
+ );
+ } else {
+ // We have to go against RFC 2183/2231 in some areas for interoperability
+ $value = $this->getTokenAsEncodedWord($origValue);
+ $encoded = false;
+ }
+ }
+
+ $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value);
+
+ // Need to add indices
+ if (count($valueLines) > 1) {
+ $paramLines = array();
+ foreach ($valueLines as $i => $line) {
+ $paramLines[] = $name.'*'.$i.
+ $this->_getEndOfParameterValue($line, true, $i == 0);
+ }
+
+ return implode(";\r\n ", $paramLines);
+ } else {
+ return $name.$this->_getEndOfParameterValue(
+ $valueLines[0], $encoded, true
+ );
+ }
+ }
+
+ /**
+ * Returns the parameter value from the "=" and beyond.
+ *
+ * @param string $value to append
+ * @param bool $encoded
+ * @param bool $firstLine
+ *
+ * @return string
+ */
+ private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false)
+ {
+ if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
+ $value = '"'.$value.'"';
+ }
+ $prepend = '=';
+ if ($encoded) {
+ $prepend = '*=';
+ if ($firstLine) {
+ $prepend = '*='.$this->getCharset()."'".$this->getLanguage().
+ "'";
+ }
+ }
+
+ return $prepend.$value;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php
new file mode 100644
index 0000000..4a814b1
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php
@@ -0,0 +1,143 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A Path Header in Swift Mailer, such a Return-Path.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_PathHeader extends Swift_Mime_Headers_AbstractHeader
+{
+ /**
+ * The address in this Header (if specified).
+ *
+ * @var string
+ */
+ private $_address;
+
+ /**
+ * Creates a new PathHeader with the given $name.
+ *
+ * @param string $name
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_Grammar $grammar)
+ {
+ $this->setFieldName($name);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_PATH;
+ }
+
+ /**
+ * Set the model for the field body.
+ * This method takes a string for an address.
+ *
+ * @param string $model
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setAddress($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ * This method returns a string email address.
+ *
+ * @return mixed
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getAddress();
+ }
+
+ /**
+ * Set the Address which should appear in this Header.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setAddress($address)
+ {
+ if (null === $address) {
+ $this->_address = null;
+ } elseif ('' == $address) {
+ $this->_address = '';
+ } else {
+ $this->_assertValidAddress($address);
+ $this->_address = $address;
+ }
+ $this->setCachedValue(null);
+ }
+
+ /**
+ * Get the address which is used in this Header (if any).
+ *
+ * Null is returned if no address is set.
+ *
+ * @return string
+ */
+ public function getAddress()
+ {
+ return $this->_address;
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@link toString()} for that).
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ if (isset($this->_address)) {
+ $this->setCachedValue('<'.$this->_address.'>');
+ }
+ }
+
+ return $this->getCachedValue();
+ }
+
+ /**
+ * Throws an Exception if the address passed does not comply with RFC 2822.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException If address is invalid
+ */
+ private function _assertValidAddress($address)
+ {
+ if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D',
+ $address)) {
+ throw new Swift_RfcComplianceException(
+ 'Address set in PathHeader does not comply with addr-spec of RFC 2822.'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php
new file mode 100644
index 0000000..86177f1
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A Simple MIME Header.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_Headers_UnstructuredHeader extends Swift_Mime_Headers_AbstractHeader
+{
+ /**
+ * The value of this Header.
+ *
+ * @var string
+ */
+ private $_value;
+
+ /**
+ * Creates a new SimpleHeader with $name.
+ *
+ * @param string $name
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Mime_Grammar $grammar)
+ {
+ $this->setFieldName($name);
+ $this->setEncoder($encoder);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_TEXT;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string for the field value.
+ *
+ * @param string $model
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setValue($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns a string.
+ *
+ * @return string
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Get the (unencoded) value of this header.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set the (unencoded) value of this header.
+ *
+ * @param string $value
+ */
+ public function setValue($value)
+ {
+ $this->clearCachedValueIf($this->_value != $value);
+ $this->_value = $value;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ $this->setCachedValue(
+ $this->encodeWords($this, $this->_value)
+ );
+ }
+
+ return $this->getCachedValue();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php
new file mode 100644
index 0000000..9b36d21
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php
@@ -0,0 +1,223 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A Message (RFC 2822) object.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_Message extends Swift_Mime_MimeEntity
+{
+ /**
+ * Generates a valid Message-ID and switches to it.
+ *
+ * @return string
+ */
+ public function generateId();
+
+ /**
+ * Set the subject of the message.
+ *
+ * @param string $subject
+ */
+ public function setSubject($subject);
+
+ /**
+ * Get the subject of the message.
+ *
+ * @return string
+ */
+ public function getSubject();
+
+ /**
+ * Set the origination date of the message as a UNIX timestamp.
+ *
+ * @param int $date
+ */
+ public function setDate($date);
+
+ /**
+ * Get the origination date of the message as a UNIX timestamp.
+ *
+ * @return int
+ */
+ public function getDate();
+
+ /**
+ * Set the return-path (bounce-detect) address.
+ *
+ * @param string $address
+ */
+ public function setReturnPath($address);
+
+ /**
+ * Get the return-path (bounce-detect) address.
+ *
+ * @return string
+ */
+ public function getReturnPath();
+
+ /**
+ * Set the sender of this message.
+ *
+ * If multiple addresses are present in the From field, this SHOULD be set.
+ *
+ * According to RFC 2822 it is a requirement when there are multiple From
+ * addresses, but Swift itself does not require it directly.
+ *
+ * An associative array (with one element!) can be used to provide a display-
+ * name: i.e. array('email@address' => 'Real Name').
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $address
+ * @param string $name optional
+ */
+ public function setSender($address, $name = null);
+
+ /**
+ * Get the sender address for this message.
+ *
+ * This has a higher significance than the From address.
+ *
+ * @return string
+ */
+ public function getSender();
+
+ /**
+ * Set the From address of this message.
+ *
+ * It is permissible for multiple From addresses to be set using an array.
+ *
+ * If multiple From addresses are used, you SHOULD set the Sender address and
+ * according to RFC 2822, MUST set the sender address.
+ *
+ * An array can be used if display names are to be provided: i.e.
+ * array('email@address.com' => 'Real Name').
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setFrom($addresses, $name = null);
+
+ /**
+ * Get the From address(es) of this message.
+ *
+ * This method always returns an associative array where the keys are the
+ * addresses.
+ *
+ * @return string[]
+ */
+ public function getFrom();
+
+ /**
+ * Set the Reply-To address(es).
+ *
+ * Any replies from the receiver will be sent to this address.
+ *
+ * It is permissible for multiple reply-to addresses to be set using an array.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setReplyTo($addresses, $name = null);
+
+ /**
+ * Get the Reply-To addresses for this message.
+ *
+ * This method always returns an associative array where the keys provide the
+ * email addresses.
+ *
+ * @return string[]
+ */
+ public function getReplyTo();
+
+ /**
+ * Set the To address(es).
+ *
+ * Recipients set in this field will receive a copy of this message.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setCc()}.
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setTo($addresses, $name = null);
+
+ /**
+ * Get the To addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getTo();
+
+ /**
+ * Set the Cc address(es).
+ *
+ * Recipients set in this field will receive a 'carbon-copy' of this message.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setCc($addresses, $name = null);
+
+ /**
+ * Get the Cc addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getCc();
+
+ /**
+ * Set the Bcc address(es).
+ *
+ * Recipients set in this field will receive a 'blind-carbon-copy' of this
+ * message.
+ *
+ * In other words, they will get the message, but any other recipients of the
+ * message will have no such knowledge of their receipt of it.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setBcc($addresses, $name = null);
+
+ /**
+ * Get the Bcc addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getBcc();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php
new file mode 100644
index 0000000..30f460c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME entity, such as an attachment.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_MimeEntity extends Swift_Mime_CharsetObserver, Swift_Mime_EncodingObserver
+{
+ /** Main message document; there can only be one of these */
+ const LEVEL_TOP = 16;
+
+ /** An entity which nests with the same precedence as an attachment */
+ const LEVEL_MIXED = 256;
+
+ /** An entity which nests with the same precedence as a mime part */
+ const LEVEL_ALTERNATIVE = 4096;
+
+ /** An entity which nests with the same precedence as embedded content */
+ const LEVEL_RELATED = 65536;
+
+ /**
+ * Get the level at which this entity shall be nested in final document.
+ *
+ * The lower the value, the more outermost the entity will be nested.
+ *
+ * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
+ *
+ * @return int
+ */
+ public function getNestingLevel();
+
+ /**
+ * Get the qualified content-type of this mime entity.
+ *
+ * @return string
+ */
+ public function getContentType();
+
+ /**
+ * Returns a unique ID for this entity.
+ *
+ * For most entities this will likely be the Content-ID, though it has
+ * no explicit semantic meaning and can be considered an identifier for
+ * programming logic purposes.
+ *
+ * If a Content-ID header is present, this value SHOULD match the value of
+ * the header.
+ *
+ * @return string
+ */
+ public function getId();
+
+ /**
+ * Get all children nested inside this entity.
+ *
+ * These are not just the immediate children, but all children.
+ *
+ * @return Swift_Mime_MimeEntity[]
+ */
+ public function getChildren();
+
+ /**
+ * Set all children nested inside this entity.
+ *
+ * This includes grandchildren.
+ *
+ * @param Swift_Mime_MimeEntity[] $children
+ */
+ public function setChildren(array $children);
+
+ /**
+ * Get the collection of Headers in this Mime entity.
+ *
+ * @return Swift_Mime_HeaderSet
+ */
+ public function getHeaders();
+
+ /**
+ * Get the body content of this entity as a string.
+ *
+ * Returns NULL if no body has been set.
+ *
+ * @return string|null
+ */
+ public function getBody();
+
+ /**
+ * Set the body content of this entity as a string.
+ *
+ * @param string $body
+ * @param string $contentType optional
+ */
+ public function setBody($body, $contentType = null);
+
+ /**
+ * Get this entire entity in its string form.
+ *
+ * @return string
+ */
+ public function toString();
+
+ /**
+ * Get this entire entity as a ByteStream.
+ *
+ * @param Swift_InputByteStream $is to write to
+ */
+ public function toByteStream(Swift_InputByteStream $is);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php
new file mode 100644
index 0000000..4564fef
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php
@@ -0,0 +1,212 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME part, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_MimePart extends Swift_Mime_SimpleMimeEntity
+{
+ /** The format parameter last specified by the user */
+ protected $_userFormat;
+
+ /** The charset last specified by the user */
+ protected $_userCharset;
+
+ /** The delsp parameter last specified by the user */
+ protected $_userDelSp;
+
+ /** The nesting level of this MimePart */
+ private $_nestingLevel = self::LEVEL_ALTERNATIVE;
+
+ /**
+ * Create a new MimePart with $headers, $encoder and $cache.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ * @param string $charset
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $charset = null)
+ {
+ parent::__construct($headers, $encoder, $cache, $grammar);
+ $this->setContentType('text/plain');
+ if (null !== $charset) {
+ $this->setCharset($charset);
+ }
+ }
+
+ /**
+ * Set the body of this entity, either as a string, or as an instance of
+ * {@link Swift_OutputByteStream}.
+ *
+ * @param mixed $body
+ * @param string $contentType optional
+ * @param string $charset optional
+ *
+ * @return $this
+ */
+ public function setBody($body, $contentType = null, $charset = null)
+ {
+ if (isset($charset)) {
+ $this->setCharset($charset);
+ }
+ $body = $this->_convertString($body);
+
+ parent::setBody($body, $contentType);
+
+ return $this;
+ }
+
+ /**
+ * Get the character set of this entity.
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->_getHeaderParameter('Content-Type', 'charset');
+ }
+
+ /**
+ * Set the character set of this entity.
+ *
+ * @param string $charset
+ *
+ * @return $this
+ */
+ public function setCharset($charset)
+ {
+ $this->_setHeaderParameter('Content-Type', 'charset', $charset);
+ if ($charset !== $this->_userCharset) {
+ $this->_clearCache();
+ }
+ $this->_userCharset = $charset;
+ parent::charsetChanged($charset);
+
+ return $this;
+ }
+
+ /**
+ * Get the format of this entity (i.e. flowed or fixed).
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->_getHeaderParameter('Content-Type', 'format');
+ }
+
+ /**
+ * Set the format of this entity (flowed or fixed).
+ *
+ * @param string $format
+ *
+ * @return $this
+ */
+ public function setFormat($format)
+ {
+ $this->_setHeaderParameter('Content-Type', 'format', $format);
+ $this->_userFormat = $format;
+
+ return $this;
+ }
+
+ /**
+ * Test if delsp is being used for this entity.
+ *
+ * @return bool
+ */
+ public function getDelSp()
+ {
+ return 'yes' == $this->_getHeaderParameter('Content-Type', 'delsp') ? true : false;
+ }
+
+ /**
+ * Turn delsp on or off for this entity.
+ *
+ * @param bool $delsp
+ *
+ * @return $this
+ */
+ public function setDelSp($delsp = true)
+ {
+ $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null);
+ $this->_userDelSp = $delsp;
+
+ return $this;
+ }
+
+ /**
+ * Get the nesting level of this entity.
+ *
+ * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return $this->_nestingLevel;
+ }
+
+ /**
+ * Receive notification that the charset has changed on this document, or a
+ * parent document.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->setCharset($charset);
+ }
+
+ /** Fix the content-type and encoding of this entity */
+ protected function _fixHeaders()
+ {
+ parent::_fixHeaders();
+ if (count($this->getChildren())) {
+ $this->_setHeaderParameter('Content-Type', 'charset', null);
+ $this->_setHeaderParameter('Content-Type', 'format', null);
+ $this->_setHeaderParameter('Content-Type', 'delsp', null);
+ } else {
+ $this->setCharset($this->_userCharset);
+ $this->setFormat($this->_userFormat);
+ $this->setDelSp($this->_userDelSp);
+ }
+ }
+
+ /** Set the nesting level of this entity */
+ protected function _setNestingLevel($level)
+ {
+ $this->_nestingLevel = $level;
+ }
+
+ /** Encode charset when charset is not utf-8 */
+ protected function _convertString($string)
+ {
+ $charset = strtolower($this->getCharset());
+ if (!in_array($charset, array('utf-8', 'iso-8859-1', 'iso-8859-15', ''))) {
+ // mb_convert_encoding must be the first one to check, since iconv cannot convert some words.
+ if (function_exists('mb_convert_encoding')) {
+ $string = mb_convert_encoding($string, $charset, 'utf-8');
+ } elseif (function_exists('iconv')) {
+ $string = iconv('utf-8//TRANSLIT//IGNORE', $charset, $string);
+ } else {
+ throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).');
+ }
+
+ return $string;
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php
new file mode 100644
index 0000000..e15c6ef
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME Header with parameters.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Mime_ParameterizedHeader extends Swift_Mime_Header
+{
+ /**
+ * Set the value of $parameter.
+ *
+ * @param string $parameter
+ * @param string $value
+ */
+ public function setParameter($parameter, $value);
+
+ /**
+ * Get the value of $parameter.
+ *
+ * @param string $parameter
+ *
+ * @return string
+ */
+ public function getParameter($parameter);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php
new file mode 100644
index 0000000..1ca504e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php
@@ -0,0 +1,193 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Creates MIME headers.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_SimpleHeaderFactory implements Swift_Mime_HeaderFactory
+{
+ /** The HeaderEncoder used by these headers */
+ private $_encoder;
+
+ /** The Encoder used by parameters */
+ private $_paramEncoder;
+
+ /** The Grammar */
+ private $_grammar;
+
+ /** The charset of created Headers */
+ private $_charset;
+
+ /**
+ * Creates a new SimpleHeaderFactory using $encoder and $paramEncoder.
+ *
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param Swift_Encoder $paramEncoder
+ * @param Swift_Mime_Grammar $grammar
+ * @param string|null $charset
+ */
+ public function __construct(Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder, Swift_Mime_Grammar $grammar, $charset = null)
+ {
+ $this->_encoder = $encoder;
+ $this->_paramEncoder = $paramEncoder;
+ $this->_grammar = $grammar;
+ $this->_charset = $charset;
+ }
+
+ /**
+ * Create a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string|null $addresses
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createMailboxHeader($name, $addresses = null)
+ {
+ $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder, $this->_grammar);
+ if (isset($addresses)) {
+ $header->setFieldBodyModel($addresses);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new Date header using $timestamp (UNIX time).
+ *
+ * @param string $name
+ * @param int|null $timestamp
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createDateHeader($name, $timestamp = null)
+ {
+ $header = new Swift_Mime_Headers_DateHeader($name, $this->_grammar);
+ if (isset($timestamp)) {
+ $header->setFieldBodyModel($timestamp);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createTextHeader($name, $value = null)
+ {
+ $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder, $this->_grammar);
+ if (isset($value)) {
+ $header->setFieldBodyModel($value);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ *
+ * @return Swift_Mime_ParameterizedHeader
+ */
+ public function createParameterizedHeader($name, $value = null,
+ $params = array())
+ {
+ $header = new Swift_Mime_Headers_ParameterizedHeader($name, $this->_encoder, strtolower($name) == 'content-disposition' ? $this->_paramEncoder : null, $this->_grammar);
+ if (isset($value)) {
+ $header->setFieldBodyModel($value);
+ }
+ foreach ($params as $k => $v) {
+ $header->setParameter($k, $v);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createIdHeader($name, $ids = null)
+ {
+ $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->_grammar);
+ if (isset($ids)) {
+ $header->setFieldBodyModel($ids);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createPathHeader($name, $path = null)
+ {
+ $header = new Swift_Mime_Headers_PathHeader($name, $this->_grammar);
+ if (isset($path)) {
+ $header->setFieldBodyModel($path);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_charset = $charset;
+ $this->_encoder->charsetChanged($charset);
+ $this->_paramEncoder->charsetChanged($charset);
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->_encoder = clone $this->_encoder;
+ $this->_paramEncoder = clone $this->_paramEncoder;
+ }
+
+ /** Apply the charset to the Header */
+ private function _setHeaderCharset(Swift_Mime_Header $header)
+ {
+ if (isset($this->_charset)) {
+ $header->setCharset($this->_charset);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php
new file mode 100644
index 0000000..a06ce72
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php
@@ -0,0 +1,414 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A collection of MIME headers.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_SimpleHeaderSet implements Swift_Mime_HeaderSet
+{
+ /** HeaderFactory */
+ private $_factory;
+
+ /** Collection of set Headers */
+ private $_headers = array();
+
+ /** Field ordering details */
+ private $_order = array();
+
+ /** List of fields which are required to be displayed */
+ private $_required = array();
+
+ /** The charset used by Headers */
+ private $_charset;
+
+ /**
+ * Create a new SimpleHeaderSet with the given $factory.
+ *
+ * @param Swift_Mime_HeaderFactory $factory
+ * @param string $charset
+ */
+ public function __construct(Swift_Mime_HeaderFactory $factory, $charset = null)
+ {
+ $this->_factory = $factory;
+ if (isset($charset)) {
+ $this->setCharset($charset);
+ }
+ }
+
+ /**
+ * Set the charset used by these headers.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset)
+ {
+ $this->_charset = $charset;
+ $this->_factory->charsetChanged($charset);
+ $this->_notifyHeadersOfCharset($charset);
+ }
+
+ /**
+ * Add a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string $addresses
+ */
+ public function addMailboxHeader($name, $addresses = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createMailboxHeader($name, $addresses));
+ }
+
+ /**
+ * Add a new Date header using $timestamp (UNIX time).
+ *
+ * @param string $name
+ * @param int $timestamp
+ */
+ public function addDateHeader($name, $timestamp = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createDateHeader($name, $timestamp));
+ }
+
+ /**
+ * Add a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function addTextHeader($name, $value = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createTextHeader($name, $value));
+ }
+
+ /**
+ * Add a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ */
+ public function addParameterizedHeader($name, $value = null, $params = array())
+ {
+ $this->_storeHeader($name, $this->_factory->createParameterizedHeader($name, $value, $params));
+ }
+
+ /**
+ * Add a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ */
+ public function addIdHeader($name, $ids = null)
+ {
+ $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids));
+ }
+
+ /**
+ * Add a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ */
+ public function addPathHeader($name, $path = null)
+ {
+ $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path));
+ }
+
+ /**
+ * Returns true if at least one header with the given $name exists.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return bool
+ */
+ public function has($name, $index = 0)
+ {
+ $lowerName = strtolower($name);
+
+ if (!array_key_exists($lowerName, $this->_headers)) {
+ return false;
+ }
+
+ if (func_num_args() < 2) {
+ // index was not specified, so we only need to check that there is at least one header value set
+ return (bool) count($this->_headers[$lowerName]);
+ }
+
+ return array_key_exists($index, $this->_headers[$lowerName]);
+ }
+
+ /**
+ * Set a header in the HeaderSet.
+ *
+ * The header may be a previously fetched header via {@link get()} or it may
+ * be one that has been created separately.
+ *
+ * If $index is specified, the header will be inserted into the set at this
+ * offset.
+ *
+ * @param Swift_Mime_Header $header
+ * @param int $index
+ */
+ public function set(Swift_Mime_Header $header, $index = 0)
+ {
+ $this->_storeHeader($header->getFieldName(), $header, $index);
+ }
+
+ /**
+ * Get the header with the given $name.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ * Returns NULL if none present.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return Swift_Mime_Header
+ */
+ public function get($name, $index = 0)
+ {
+ $name = strtolower($name);
+
+ if (func_num_args() < 2) {
+ if ($this->has($name)) {
+ $values = array_values($this->_headers[$name]);
+
+ return array_shift($values);
+ }
+ } else {
+ if ($this->has($name, $index)) {
+ return $this->_headers[$name][$index];
+ }
+ }
+ }
+
+ /**
+ * Get all headers with the given $name.
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getAll($name = null)
+ {
+ if (!isset($name)) {
+ $headers = array();
+ foreach ($this->_headers as $collection) {
+ $headers = array_merge($headers, $collection);
+ }
+
+ return $headers;
+ }
+
+ $lowerName = strtolower($name);
+ if (!array_key_exists($lowerName, $this->_headers)) {
+ return array();
+ }
+
+ return $this->_headers[$lowerName];
+ }
+
+ /**
+ * Return the name of all Headers.
+ *
+ * @return array
+ */
+ public function listAll()
+ {
+ $headers = $this->_headers;
+ if ($this->_canSort()) {
+ uksort($headers, array($this, '_sortHeaders'));
+ }
+
+ return array_keys($headers);
+ }
+
+ /**
+ * Remove the header with the given $name if it's set.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ */
+ public function remove($name, $index = 0)
+ {
+ $lowerName = strtolower($name);
+ unset($this->_headers[$lowerName][$index]);
+ }
+
+ /**
+ * Remove all headers with the given $name.
+ *
+ * @param string $name
+ */
+ public function removeAll($name)
+ {
+ $lowerName = strtolower($name);
+ unset($this->_headers[$lowerName]);
+ }
+
+ /**
+ * Create a new instance of this HeaderSet.
+ *
+ * @return self
+ */
+ public function newInstance()
+ {
+ return new self($this->_factory);
+ }
+
+ /**
+ * Define a list of Header names as an array in the correct order.
+ *
+ * These Headers will be output in the given order where present.
+ *
+ * @param array $sequence
+ */
+ public function defineOrdering(array $sequence)
+ {
+ $this->_order = array_flip(array_map('strtolower', $sequence));
+ }
+
+ /**
+ * Set a list of header names which must always be displayed when set.
+ *
+ * Usually headers without a field value won't be output unless set here.
+ *
+ * @param array $names
+ */
+ public function setAlwaysDisplayed(array $names)
+ {
+ $this->_required = array_flip(array_map('strtolower', $names));
+ }
+
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->setCharset($charset);
+ }
+
+ /**
+ * Returns a string with a representation of all headers.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $string = '';
+ $headers = $this->_headers;
+ if ($this->_canSort()) {
+ uksort($headers, array($this, '_sortHeaders'));
+ }
+ foreach ($headers as $collection) {
+ foreach ($collection as $header) {
+ if ($this->_isDisplayed($header) || $header->getFieldBody() != '') {
+ $string .= $header->toString();
+ }
+ }
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ *
+ * @see toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /** Save a Header to the internal collection */
+ private function _storeHeader($name, Swift_Mime_Header $header, $offset = null)
+ {
+ if (!isset($this->_headers[strtolower($name)])) {
+ $this->_headers[strtolower($name)] = array();
+ }
+ if (!isset($offset)) {
+ $this->_headers[strtolower($name)][] = $header;
+ } else {
+ $this->_headers[strtolower($name)][$offset] = $header;
+ }
+ }
+
+ /** Test if the headers can be sorted */
+ private function _canSort()
+ {
+ return count($this->_order) > 0;
+ }
+
+ /** uksort() algorithm for Header ordering */
+ private function _sortHeaders($a, $b)
+ {
+ $lowerA = strtolower($a);
+ $lowerB = strtolower($b);
+ $aPos = array_key_exists($lowerA, $this->_order) ? $this->_order[$lowerA] : -1;
+ $bPos = array_key_exists($lowerB, $this->_order) ? $this->_order[$lowerB] : -1;
+
+ if (-1 === $aPos && -1 === $bPos) {
+ // just be sure to be determinist here
+ return $a > $b ? -1 : 1;
+ }
+
+ if ($aPos == -1) {
+ return 1;
+ } elseif ($bPos == -1) {
+ return -1;
+ }
+
+ return $aPos < $bPos ? -1 : 1;
+ }
+
+ /** Test if the given Header is always displayed */
+ private function _isDisplayed(Swift_Mime_Header $header)
+ {
+ return array_key_exists(strtolower($header->getFieldName()), $this->_required);
+ }
+
+ /** Notify all Headers of the new charset */
+ private function _notifyHeadersOfCharset($charset)
+ {
+ foreach ($this->_headers as $headerGroup) {
+ foreach ($headerGroup as $header) {
+ $header->setCharset($charset);
+ }
+ }
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->_factory = clone $this->_factory;
+ foreach ($this->_headers as $groupKey => $headerGroup) {
+ foreach ($headerGroup as $key => $header) {
+ $this->_headers[$groupKey][$key] = clone $header;
+ }
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php
new file mode 100644
index 0000000..72d40ce
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php
@@ -0,0 +1,655 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * The default email message class.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_SimpleMessage extends Swift_Mime_MimePart implements Swift_Mime_Message
+{
+ const PRIORITY_HIGHEST = 1;
+ const PRIORITY_HIGH = 2;
+ const PRIORITY_NORMAL = 3;
+ const PRIORITY_LOW = 4;
+ const PRIORITY_LOWEST = 5;
+
+ /**
+ * Create a new SimpleMessage with $headers, $encoder and $cache.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ * @param string $charset
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $charset = null)
+ {
+ parent::__construct($headers, $encoder, $cache, $grammar, $charset);
+ $this->getHeaders()->defineOrdering(array(
+ 'Return-Path',
+ 'Received',
+ 'DKIM-Signature',
+ 'DomainKey-Signature',
+ 'Sender',
+ 'Message-ID',
+ 'Date',
+ 'Subject',
+ 'From',
+ 'Reply-To',
+ 'To',
+ 'Cc',
+ 'Bcc',
+ 'MIME-Version',
+ 'Content-Type',
+ 'Content-Transfer-Encoding',
+ ));
+ $this->getHeaders()->setAlwaysDisplayed(array('Date', 'Message-ID', 'From'));
+ $this->getHeaders()->addTextHeader('MIME-Version', '1.0');
+ $this->setDate(time());
+ $this->setId($this->getId());
+ $this->getHeaders()->addMailboxHeader('From');
+ }
+
+ /**
+ * Always returns {@link LEVEL_TOP} for a message instance.
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return self::LEVEL_TOP;
+ }
+
+ /**
+ * Set the subject of this message.
+ *
+ * @param string $subject
+ *
+ * @return $this
+ */
+ public function setSubject($subject)
+ {
+ if (!$this->_setHeaderFieldModel('Subject', $subject)) {
+ $this->getHeaders()->addTextHeader('Subject', $subject);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the subject of this message.
+ *
+ * @return string
+ */
+ public function getSubject()
+ {
+ return $this->_getHeaderFieldModel('Subject');
+ }
+
+ /**
+ * Set the date at which this message was created.
+ *
+ * @param int $date
+ *
+ * @return $this
+ */
+ public function setDate($date)
+ {
+ if (!$this->_setHeaderFieldModel('Date', $date)) {
+ $this->getHeaders()->addDateHeader('Date', $date);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the date at which this message was created.
+ *
+ * @return int
+ */
+ public function getDate()
+ {
+ return $this->_getHeaderFieldModel('Date');
+ }
+
+ /**
+ * Set the return-path (the bounce address) of this message.
+ *
+ * @param string $address
+ *
+ * @return $this
+ */
+ public function setReturnPath($address)
+ {
+ if (!$this->_setHeaderFieldModel('Return-Path', $address)) {
+ $this->getHeaders()->addPathHeader('Return-Path', $address);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the return-path (bounce address) of this message.
+ *
+ * @return string
+ */
+ public function getReturnPath()
+ {
+ return $this->_getHeaderFieldModel('Return-Path');
+ }
+
+ /**
+ * Set the sender of this message.
+ *
+ * This does not override the From field, but it has a higher significance.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setSender($address, $name = null)
+ {
+ if (!is_array($address) && isset($name)) {
+ $address = array($address => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Sender', (array) $address)) {
+ $this->getHeaders()->addMailboxHeader('Sender', (array) $address);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the sender of this message.
+ *
+ * @return string
+ */
+ public function getSender()
+ {
+ return $this->_getHeaderFieldModel('Sender');
+ }
+
+ /**
+ * Add a From: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function addFrom($address, $name = null)
+ {
+ $current = $this->getFrom();
+ $current[$address] = $name;
+
+ return $this->setFrom($current);
+ }
+
+ /**
+ * Set the from address of this message.
+ *
+ * You may pass an array of addresses if this message is from multiple people.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param string|array $addresses
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setFrom($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('From', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('From', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the from address of this message.
+ *
+ * @return mixed
+ */
+ public function getFrom()
+ {
+ return $this->_getHeaderFieldModel('From');
+ }
+
+ /**
+ * Add a Reply-To: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function addReplyTo($address, $name = null)
+ {
+ $current = $this->getReplyTo();
+ $current[$address] = $name;
+
+ return $this->setReplyTo($current);
+ }
+
+ /**
+ * Set the reply-to address of this message.
+ *
+ * You may pass an array of addresses if replies will go to multiple people.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setReplyTo($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the reply-to address of this message.
+ *
+ * @return string
+ */
+ public function getReplyTo()
+ {
+ return $this->_getHeaderFieldModel('Reply-To');
+ }
+
+ /**
+ * Add a To: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function addTo($address, $name = null)
+ {
+ $current = $this->getTo();
+ $current[$address] = $name;
+
+ return $this->setTo($current);
+ }
+
+ /**
+ * Set the to addresses of this message.
+ *
+ * If multiple recipients will receive the message an array should be used.
+ * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setTo($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('To', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('To', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the To addresses of this message.
+ *
+ * @return array
+ */
+ public function getTo()
+ {
+ return $this->_getHeaderFieldModel('To');
+ }
+
+ /**
+ * Add a Cc: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function addCc($address, $name = null)
+ {
+ $current = $this->getCc();
+ $current[$address] = $name;
+
+ return $this->setCc($current);
+ }
+
+ /**
+ * Set the Cc addresses of this message.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setCc($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the Cc address of this message.
+ *
+ * @return array
+ */
+ public function getCc()
+ {
+ return $this->_getHeaderFieldModel('Cc');
+ }
+
+ /**
+ * Add a Bcc: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function addBcc($address, $name = null)
+ {
+ $current = $this->getBcc();
+ $current[$address] = $name;
+
+ return $this->setBcc($current);
+ }
+
+ /**
+ * Set the Bcc addresses of this message.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return $this
+ */
+ public function setBcc($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the Bcc addresses of this message.
+ *
+ * @return array
+ */
+ public function getBcc()
+ {
+ return $this->_getHeaderFieldModel('Bcc');
+ }
+
+ /**
+ * Set the priority of this message.
+ *
+ * The value is an integer where 1 is the highest priority and 5 is the lowest.
+ *
+ * @param int $priority
+ *
+ * @return $this
+ */
+ public function setPriority($priority)
+ {
+ $priorityMap = array(
+ self::PRIORITY_HIGHEST => 'Highest',
+ self::PRIORITY_HIGH => 'High',
+ self::PRIORITY_NORMAL => 'Normal',
+ self::PRIORITY_LOW => 'Low',
+ self::PRIORITY_LOWEST => 'Lowest',
+ );
+ $pMapKeys = array_keys($priorityMap);
+ if ($priority > max($pMapKeys)) {
+ $priority = max($pMapKeys);
+ } elseif ($priority < min($pMapKeys)) {
+ $priority = min($pMapKeys);
+ }
+ if (!$this->_setHeaderFieldModel('X-Priority',
+ sprintf('%d (%s)', $priority, $priorityMap[$priority]))) {
+ $this->getHeaders()->addTextHeader('X-Priority',
+ sprintf('%d (%s)', $priority, $priorityMap[$priority]));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the priority of this message.
+ *
+ * The returned value is an integer where 1 is the highest priority and 5
+ * is the lowest.
+ *
+ * @return int
+ */
+ public function getPriority()
+ {
+ list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'),
+ '%[1-5]'
+ );
+
+ return isset($priority) ? $priority : 3;
+ }
+
+ /**
+ * Ask for a delivery receipt from the recipient to be sent to $addresses.
+ *
+ * @param array $addresses
+ *
+ * @return $this
+ */
+ public function setReadReceiptTo($addresses)
+ {
+ if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) {
+ $this->getHeaders()
+ ->addMailboxHeader('Disposition-Notification-To', $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the addresses to which a read-receipt will be sent.
+ *
+ * @return string
+ */
+ public function getReadReceiptTo()
+ {
+ return $this->_getHeaderFieldModel('Disposition-Notification-To');
+ }
+
+ /**
+ * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return $this
+ */
+ public function attach(Swift_Mime_MimeEntity $entity)
+ {
+ $this->setChildren(array_merge($this->getChildren(), array($entity)));
+
+ return $this;
+ }
+
+ /**
+ * Remove an already attached entity.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return $this
+ */
+ public function detach(Swift_Mime_MimeEntity $entity)
+ {
+ $newChildren = array();
+ foreach ($this->getChildren() as $child) {
+ if ($entity !== $child) {
+ $newChildren[] = $child;
+ }
+ }
+ $this->setChildren($newChildren);
+
+ return $this;
+ }
+
+ /**
+ * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source.
+ * This method should be used when embedding images or other data in a message.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return string
+ */
+ public function embed(Swift_Mime_MimeEntity $entity)
+ {
+ $this->attach($entity);
+
+ return 'cid:'.$entity->getId();
+ }
+
+ /**
+ * Get this message as a complete string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
+ $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
+ $string = parent::toString();
+ $this->setChildren($children);
+ } else {
+ $string = parent::toString();
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Write this message to a {@link Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
+ $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
+ parent::toByteStream($is);
+ $this->setChildren($children);
+ } else {
+ parent::toByteStream($is);
+ }
+ }
+
+ /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */
+ protected function _getIdField()
+ {
+ return 'Message-ID';
+ }
+
+ /** Turn the body of this message into a child of itself if needed */
+ protected function _becomeMimePart()
+ {
+ $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(),
+ $this->_getCache(), $this->_getGrammar(), $this->_userCharset
+ );
+ $part->setContentType($this->_userContentType);
+ $part->setBody($this->getBody());
+ $part->setFormat($this->_userFormat);
+ $part->setDelSp($this->_userDelSp);
+ $part->_setNestingLevel($this->_getTopNestingLevel());
+
+ return $part;
+ }
+
+ /** Get the highest nesting level nested inside this message */
+ private function _getTopNestingLevel()
+ {
+ $highestLevel = $this->getNestingLevel();
+ foreach ($this->getChildren() as $child) {
+ $childLevel = $child->getNestingLevel();
+ if ($highestLevel < $childLevel) {
+ $highestLevel = $childLevel;
+ }
+ }
+
+ return $highestLevel;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php
new file mode 100644
index 0000000..a13f1b2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php
@@ -0,0 +1,846 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME entity, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity
+{
+ /** A collection of Headers for this mime entity */
+ private $_headers;
+
+ /** The body as a string, or a stream */
+ private $_body;
+
+ /** The encoder that encodes the body into a streamable format */
+ private $_encoder;
+
+ /** The grammar to use for id validation */
+ private $_grammar;
+
+ /** A mime boundary, if any is used */
+ private $_boundary;
+
+ /** Mime types to be used based on the nesting level */
+ private $_compositeRanges = array(
+ 'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED),
+ 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
+ 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED),
+ );
+
+ /** A set of filter rules to define what level an entity should be nested at */
+ private $_compoundLevelFilters = array();
+
+ /** The nesting level of this entity */
+ private $_nestingLevel = self::LEVEL_ALTERNATIVE;
+
+ /** A KeyCache instance used during encoding and streaming */
+ private $_cache;
+
+ /** Direct descendants of this entity */
+ private $_immediateChildren = array();
+
+ /** All descendants of this entity */
+ private $_children = array();
+
+ /** The maximum line length of the body of this entity */
+ private $_maxLineLength = 78;
+
+ /** The order in which alternative mime types should appear */
+ private $_alternativePartOrder = array(
+ 'text/plain' => 1,
+ 'text/html' => 2,
+ 'multipart/related' => 3,
+ );
+
+ /** The CID of this entity */
+ private $_id;
+
+ /** The key used for accessing the cache */
+ private $_cacheKey;
+
+ protected $_userContentType;
+
+ /**
+ * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar)
+ {
+ $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true));
+ $this->_cache = $cache;
+ $this->_headers = $headers;
+ $this->_grammar = $grammar;
+ $this->setEncoder($encoder);
+ $this->_headers->defineOrdering(array('Content-Type', 'Content-Transfer-Encoding'));
+
+ // This array specifies that, when the entire MIME document contains
+ // $compoundLevel, then for each child within $level, if its Content-Type
+ // is $contentType then it should be treated as if it's level is
+ // $neededLevel instead. I tried to write that unambiguously! :-\
+ // Data Structure:
+ // array (
+ // $compoundLevel => array(
+ // $level => array(
+ // $contentType => $neededLevel
+ // )
+ // )
+ // )
+
+ $this->_compoundLevelFilters = array(
+ (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
+ self::LEVEL_ALTERNATIVE => array(
+ 'text/plain' => self::LEVEL_ALTERNATIVE,
+ 'text/html' => self::LEVEL_RELATED,
+ ),
+ ),
+ );
+
+ $this->_id = $this->getRandomId();
+ }
+
+ /**
+ * Generate a new Content-ID or Message-ID for this MIME entity.
+ *
+ * @return string
+ */
+ public function generateId()
+ {
+ $this->setId($this->getRandomId());
+
+ return $this->_id;
+ }
+
+ /**
+ * Get the {@link Swift_Mime_HeaderSet} for this entity.
+ *
+ * @return Swift_Mime_HeaderSet
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Get the nesting level of this entity.
+ *
+ * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return $this->_nestingLevel;
+ }
+
+ /**
+ * Get the Content-type of this entity.
+ *
+ * @return string
+ */
+ public function getContentType()
+ {
+ return $this->_getHeaderFieldModel('Content-Type');
+ }
+
+ /**
+ * Set the Content-type of this entity.
+ *
+ * @param string $type
+ *
+ * @return $this
+ */
+ public function setContentType($type)
+ {
+ $this->_setContentTypeInHeaders($type);
+ // Keep track of the value so that if the content-type changes automatically
+ // due to added child entities, it can be restored if they are later removed
+ $this->_userContentType = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get the CID of this entity.
+ *
+ * The CID will only be present in headers if a Content-ID header is present.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ $tmp = (array) $this->_getHeaderFieldModel($this->_getIdField());
+
+ return $this->_headers->has($this->_getIdField()) ? current($tmp) : $this->_id;
+ }
+
+ /**
+ * Set the CID of this entity.
+ *
+ * @param string $id
+ *
+ * @return $this
+ */
+ public function setId($id)
+ {
+ if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) {
+ $this->_headers->addIdHeader($this->_getIdField(), $id);
+ }
+ $this->_id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Get the description of this entity.
+ *
+ * This value comes from the Content-Description header if set.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_getHeaderFieldModel('Content-Description');
+ }
+
+ /**
+ * Set the description of this entity.
+ *
+ * This method sets a value in the Content-ID header.
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Description', $description)) {
+ $this->_headers->addTextHeader('Content-Description', $description);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum line length of the body of this entity.
+ *
+ * @return int
+ */
+ public function getMaxLineLength()
+ {
+ return $this->_maxLineLength;
+ }
+
+ /**
+ * Set the maximum line length of lines in this body.
+ *
+ * Though not enforced by the library, lines should not exceed 1000 chars.
+ *
+ * @param int $length
+ *
+ * @return $this
+ */
+ public function setMaxLineLength($length)
+ {
+ $this->_maxLineLength = $length;
+
+ return $this;
+ }
+
+ /**
+ * Get all children added to this entity.
+ *
+ * @return Swift_Mime_MimeEntity[]
+ */
+ public function getChildren()
+ {
+ return $this->_children;
+ }
+
+ /**
+ * Set all children of this entity.
+ *
+ * @param Swift_Mime_MimeEntity[] $children
+ * @param int $compoundLevel For internal use only
+ *
+ * @return $this
+ */
+ public function setChildren(array $children, $compoundLevel = null)
+ {
+ // TODO: Try to refactor this logic
+
+ $compoundLevel = isset($compoundLevel) ? $compoundLevel : $this->_getCompoundLevel($children);
+ $immediateChildren = array();
+ $grandchildren = array();
+ $newContentType = $this->_userContentType;
+
+ foreach ($children as $child) {
+ $level = $this->_getNeededChildLevel($child, $compoundLevel);
+ if (empty($immediateChildren)) {
+ //first iteration
+ $immediateChildren = array($child);
+ } else {
+ $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+ if ($nextLevel == $level) {
+ $immediateChildren[] = $child;
+ } elseif ($level < $nextLevel) {
+ // Re-assign immediateChildren to grandchildren
+ $grandchildren = array_merge($grandchildren, $immediateChildren);
+ // Set new children
+ $immediateChildren = array($child);
+ } else {
+ $grandchildren[] = $child;
+ }
+ }
+ }
+
+ if ($immediateChildren) {
+ $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+
+ // Determine which composite media type is needed to accommodate the
+ // immediate children
+ foreach ($this->_compositeRanges as $mediaType => $range) {
+ if ($lowestLevel > $range[0] && $lowestLevel <= $range[1]) {
+ $newContentType = $mediaType;
+
+ break;
+ }
+ }
+
+ // Put any grandchildren in a subpart
+ if (!empty($grandchildren)) {
+ $subentity = $this->_createChild();
+ $subentity->_setNestingLevel($lowestLevel);
+ $subentity->setChildren($grandchildren, $compoundLevel);
+ array_unshift($immediateChildren, $subentity);
+ }
+ }
+
+ $this->_immediateChildren = $immediateChildren;
+ $this->_children = $children;
+ $this->_setContentTypeInHeaders($newContentType);
+ $this->_fixHeaders();
+ $this->_sortChildren();
+
+ return $this;
+ }
+
+ /**
+ * Get the body of this entity as a string.
+ *
+ * @return string
+ */
+ public function getBody()
+ {
+ return $this->_body instanceof Swift_OutputByteStream ? $this->_readStream($this->_body) : $this->_body;
+ }
+
+ /**
+ * Set the body of this entity, either as a string, or as an instance of
+ * {@link Swift_OutputByteStream}.
+ *
+ * @param mixed $body
+ * @param string $contentType optional
+ *
+ * @return $this
+ */
+ public function setBody($body, $contentType = null)
+ {
+ if ($body !== $this->_body) {
+ $this->_clearCache();
+ }
+
+ $this->_body = $body;
+ if (isset($contentType)) {
+ $this->setContentType($contentType);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the encoder used for the body of this entity.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public function getEncoder()
+ {
+ return $this->_encoder;
+ }
+
+ /**
+ * Set the encoder used for the body of this entity.
+ *
+ * @param Swift_Mime_ContentEncoder $encoder
+ *
+ * @return $this
+ */
+ public function setEncoder(Swift_Mime_ContentEncoder $encoder)
+ {
+ if ($encoder !== $this->_encoder) {
+ $this->_clearCache();
+ }
+
+ $this->_encoder = $encoder;
+ $this->_setEncoding($encoder->getName());
+ $this->_notifyEncoderChanged($encoder);
+
+ return $this;
+ }
+
+ /**
+ * Get the boundary used to separate children in this entity.
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ if (!isset($this->_boundary)) {
+ $this->_boundary = '_=_swift_v4_'.time().'_'.md5(getmypid().mt_rand().uniqid('', true)).'_=_';
+ }
+
+ return $this->_boundary;
+ }
+
+ /**
+ * Set the boundary used to separate children in this entity.
+ *
+ * @param string $boundary
+ *
+ * @throws Swift_RfcComplianceException
+ *
+ * @return $this
+ */
+ public function setBoundary($boundary)
+ {
+ $this->_assertValidBoundary($boundary);
+ $this->_boundary = $boundary;
+
+ return $this;
+ }
+
+ /**
+ * Receive notification that the charset of this entity, or a parent entity
+ * has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_notifyCharsetChanged($charset);
+ }
+
+ /**
+ * Receive notification that the encoder of this entity or a parent entity
+ * has changed.
+ *
+ * @param Swift_Mime_ContentEncoder $encoder
+ */
+ public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
+ {
+ $this->_notifyEncoderChanged($encoder);
+ }
+
+ /**
+ * Get this entire entity as a string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $string = $this->_headers->toString();
+ $string .= $this->_bodyToString();
+
+ return $string;
+ }
+
+ /**
+ * Get this entire entity as a string.
+ *
+ * @return string
+ */
+ protected function _bodyToString()
+ {
+ $string = '';
+
+ if (isset($this->_body) && empty($this->_immediateChildren)) {
+ if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
+ $body = $this->_cache->getString($this->_cacheKey, 'body');
+ } else {
+ $body = "\r\n".$this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength());
+ $this->_cache->setString($this->_cacheKey, 'body', $body, Swift_KeyCache::MODE_WRITE);
+ }
+ $string .= $body;
+ }
+
+ if (!empty($this->_immediateChildren)) {
+ foreach ($this->_immediateChildren as $child) {
+ $string .= "\r\n\r\n--".$this->getBoundary()."\r\n";
+ $string .= $child->toString();
+ }
+ $string .= "\r\n\r\n--".$this->getBoundary()."--\r\n";
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Write this entire entity to a {@see Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream
+ */
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ $is->write($this->_headers->toString());
+ $is->commit();
+
+ $this->_bodyToByteStream($is);
+ }
+
+ /**
+ * Write this entire entity to a {@link Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream
+ */
+ protected function _bodyToByteStream(Swift_InputByteStream $is)
+ {
+ if (empty($this->_immediateChildren)) {
+ if (isset($this->_body)) {
+ if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
+ $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
+ } else {
+ $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
+ if ($cacheIs) {
+ $is->bind($cacheIs);
+ }
+
+ $is->write("\r\n");
+
+ if ($this->_body instanceof Swift_OutputByteStream) {
+ $this->_body->setReadPointer(0);
+
+ $this->_encoder->encodeByteStream($this->_body, $is, 0, $this->getMaxLineLength());
+ } else {
+ $is->write($this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength()));
+ }
+
+ if ($cacheIs) {
+ $is->unbind($cacheIs);
+ }
+ }
+ }
+ }
+
+ if (!empty($this->_immediateChildren)) {
+ foreach ($this->_immediateChildren as $child) {
+ $is->write("\r\n\r\n--".$this->getBoundary()."\r\n");
+ $child->toByteStream($is);
+ }
+ $is->write("\r\n\r\n--".$this->getBoundary()."--\r\n");
+ }
+ }
+
+ /**
+ * Get the name of the header that provides the ID of this entity.
+ */
+ protected function _getIdField()
+ {
+ return 'Content-ID';
+ }
+
+ /**
+ * Get the model data (usually an array or a string) for $field.
+ */
+ protected function _getHeaderFieldModel($field)
+ {
+ if ($this->_headers->has($field)) {
+ return $this->_headers->get($field)->getFieldBodyModel();
+ }
+ }
+
+ /**
+ * Set the model data for $field.
+ */
+ protected function _setHeaderFieldModel($field, $model)
+ {
+ if ($this->_headers->has($field)) {
+ $this->_headers->get($field)->setFieldBodyModel($model);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the parameter value of $parameter on $field header.
+ */
+ protected function _getHeaderParameter($field, $parameter)
+ {
+ if ($this->_headers->has($field)) {
+ return $this->_headers->get($field)->getParameter($parameter);
+ }
+ }
+
+ /**
+ * Set the parameter value of $parameter on $field header.
+ */
+ protected function _setHeaderParameter($field, $parameter, $value)
+ {
+ if ($this->_headers->has($field)) {
+ $this->_headers->get($field)->setParameter($parameter, $value);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Re-evaluate what content type and encoding should be used on this entity.
+ */
+ protected function _fixHeaders()
+ {
+ if (count($this->_immediateChildren)) {
+ $this->_setHeaderParameter('Content-Type', 'boundary',
+ $this->getBoundary()
+ );
+ $this->_headers->remove('Content-Transfer-Encoding');
+ } else {
+ $this->_setHeaderParameter('Content-Type', 'boundary', null);
+ $this->_setEncoding($this->_encoder->getName());
+ }
+ }
+
+ /**
+ * Get the KeyCache used in this entity.
+ *
+ * @return Swift_KeyCache
+ */
+ protected function _getCache()
+ {
+ return $this->_cache;
+ }
+
+ /**
+ * Get the grammar used for validation.
+ *
+ * @return Swift_Mime_Grammar
+ */
+ protected function _getGrammar()
+ {
+ return $this->_grammar;
+ }
+
+ /**
+ * Empty the KeyCache for this entity.
+ */
+ protected function _clearCache()
+ {
+ $this->_cache->clearKey($this->_cacheKey, 'body');
+ }
+
+ /**
+ * Returns a random Content-ID or Message-ID.
+ *
+ * @return string
+ */
+ protected function getRandomId()
+ {
+ $idLeft = md5(getmypid().'.'.time().'.'.uniqid(mt_rand(), true));
+ $idRight = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated';
+ $id = $idLeft.'@'.$idRight;
+
+ try {
+ $this->_assertValidId($id);
+ } catch (Swift_RfcComplianceException $e) {
+ $id = $idLeft.'@swift.generated';
+ }
+
+ return $id;
+ }
+
+ private function _readStream(Swift_OutputByteStream $os)
+ {
+ $string = '';
+ while (false !== $bytes = $os->read(8192)) {
+ $string .= $bytes;
+ }
+
+ $os->setReadPointer(0);
+
+ return $string;
+ }
+
+ private function _setEncoding($encoding)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) {
+ $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
+ }
+ }
+
+ private function _assertValidBoundary($boundary)
+ {
+ if (!preg_match('/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', $boundary)) {
+ throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
+ }
+ }
+
+ private function _setContentTypeInHeaders($type)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Type', $type)) {
+ $this->_headers->addParameterizedHeader('Content-Type', $type);
+ }
+ }
+
+ private function _setNestingLevel($level)
+ {
+ $this->_nestingLevel = $level;
+ }
+
+ private function _getCompoundLevel($children)
+ {
+ $level = 0;
+ foreach ($children as $child) {
+ $level |= $child->getNestingLevel();
+ }
+
+ return $level;
+ }
+
+ private function _getNeededChildLevel($child, $compoundLevel)
+ {
+ $filter = array();
+ foreach ($this->_compoundLevelFilters as $bitmask => $rules) {
+ if (($compoundLevel & $bitmask) === $bitmask) {
+ $filter = $rules + $filter;
+ }
+ }
+
+ $realLevel = $child->getNestingLevel();
+ $lowercaseType = strtolower($child->getContentType());
+
+ if (isset($filter[$realLevel]) && isset($filter[$realLevel][$lowercaseType])) {
+ return $filter[$realLevel][$lowercaseType];
+ }
+
+ return $realLevel;
+ }
+
+ private function _createChild()
+ {
+ return new self($this->_headers->newInstance(), $this->_encoder, $this->_cache, $this->_grammar);
+ }
+
+ private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
+ {
+ foreach ($this->_immediateChildren as $child) {
+ $child->encoderChanged($encoder);
+ }
+ }
+
+ private function _notifyCharsetChanged($charset)
+ {
+ $this->_encoder->charsetChanged($charset);
+ $this->_headers->charsetChanged($charset);
+ foreach ($this->_immediateChildren as $child) {
+ $child->charsetChanged($charset);
+ }
+ }
+
+ private function _sortChildren()
+ {
+ $shouldSort = false;
+ foreach ($this->_immediateChildren as $child) {
+ // NOTE: This include alternative parts moved into a related part
+ if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) {
+ $shouldSort = true;
+ break;
+ }
+ }
+
+ // Sort in order of preference, if there is one
+ if ($shouldSort) {
+ // Group the messages by order of preference
+ $sorted = array();
+ foreach ($this->_immediateChildren as $child) {
+ $type = $child->getContentType();
+ $level = array_key_exists($type, $this->_alternativePartOrder) ? $this->_alternativePartOrder[$type] : max($this->_alternativePartOrder) + 1;
+
+ if (empty($sorted[$level])) {
+ $sorted[$level] = array();
+ }
+
+ $sorted[$level][] = $child;
+ }
+
+ ksort($sorted);
+
+ $this->_immediateChildren = array_reduce($sorted, 'array_merge', array());
+ }
+ }
+
+ /**
+ * Empties it's own contents from the cache.
+ */
+ public function __destruct()
+ {
+ if ($this->_cache instanceof Swift_KeyCache) {
+ $this->_cache->clearAll($this->_cacheKey);
+ }
+ }
+
+ /**
+ * Throws an Exception if the id passed does not comply with RFC 2822.
+ *
+ * @param string $id
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ private function _assertValidId($id)
+ {
+ if (!preg_match('/^'.$this->_grammar->getDefinition('id-left').'@'.$this->_grammar->getDefinition('id-right').'$/D', $id)) {
+ throw new Swift_RfcComplianceException('Invalid ID given <'.$id.'>');
+ }
+ }
+
+ /**
+ * Make a deep copy of object.
+ */
+ public function __clone()
+ {
+ $this->_headers = clone $this->_headers;
+ $this->_encoder = clone $this->_encoder;
+ $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true));
+ $children = array();
+ foreach ($this->_children as $pos => $child) {
+ $children[$pos] = clone $child;
+ }
+ $this->setChildren($children);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php
new file mode 100644
index 0000000..525b7ec
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A MIME part, in a multipart message.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_MimePart extends Swift_Mime_MimePart
+{
+ /**
+ * Create a new MimePart.
+ *
+ * Details may be optionally passed into the constructor.
+ *
+ * @param string $body
+ * @param string $contentType
+ * @param string $charset
+ */
+ public function __construct($body = null, $contentType = null, $charset = null)
+ {
+ call_user_func_array(
+ array($this, 'Swift_Mime_MimePart::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('mime.part')
+ );
+
+ if (!isset($charset)) {
+ $charset = Swift_DependencyContainer::getInstance()
+ ->lookup('properties.charset');
+ }
+ $this->setBody($body);
+ $this->setCharset($charset);
+ if ($contentType) {
+ $this->setContentType($contentType);
+ }
+ }
+
+ /**
+ * Create a new MimePart.
+ *
+ * @param string $body
+ * @param string $contentType
+ * @param string $charset
+ *
+ * @return self
+ */
+ public static function newInstance($body = null, $contentType = null, $charset = null)
+ {
+ return new self($body, $contentType, $charset);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php
new file mode 100644
index 0000000..ddde335
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Pretends messages have been sent, but just ignores them.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_NullTransport extends Swift_Transport_NullTransport
+{
+ public function __construct()
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_NullTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.null')
+ );
+ }
+
+ /**
+ * Create a new NullTransport instance.
+ *
+ * @return self
+ */
+ public static function newInstance()
+ {
+ return new self();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php
new file mode 100644
index 0000000..1f26f9b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An abstract means of reading data.
+ *
+ * Classes implementing this interface may use a subsystem which requires less
+ * memory than working with large strings of data.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_OutputByteStream
+{
+ /**
+ * Reads $length bytes from the stream into a string and moves the pointer
+ * through the stream by $length.
+ *
+ * If less bytes exist than are requested the remaining bytes are given instead.
+ * If no bytes are remaining at all, boolean false is returned.
+ *
+ * @param int $length
+ *
+ * @throws Swift_IoException
+ *
+ * @return string|bool
+ */
+ public function read($length);
+
+ /**
+ * Move the internal read pointer to $byteOffset in the stream.
+ *
+ * @param int $byteOffset
+ *
+ * @throws Swift_IoException
+ *
+ * @return bool
+ */
+ public function setReadPointer($byteOffset);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php
new file mode 100644
index 0000000..a2ec2ab
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php
@@ -0,0 +1,141 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Reduces network flooding when sending large amounts of mail.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_AntiFloodPlugin implements Swift_Events_SendListener, Swift_Plugins_Sleeper
+{
+ /**
+ * The number of emails to send before restarting Transport.
+ *
+ * @var int
+ */
+ private $_threshold;
+
+ /**
+ * The number of seconds to sleep for during a restart.
+ *
+ * @var int
+ */
+ private $_sleep;
+
+ /**
+ * The internal counter.
+ *
+ * @var int
+ */
+ private $_counter = 0;
+
+ /**
+ * The Sleeper instance for sleeping.
+ *
+ * @var Swift_Plugins_Sleeper
+ */
+ private $_sleeper;
+
+ /**
+ * Create a new AntiFloodPlugin with $threshold and $sleep time.
+ *
+ * @param int $threshold
+ * @param int $sleep time
+ * @param Swift_Plugins_Sleeper $sleeper (not needed really)
+ */
+ public function __construct($threshold = 99, $sleep = 0, Swift_Plugins_Sleeper $sleeper = null)
+ {
+ $this->setThreshold($threshold);
+ $this->setSleepTime($sleep);
+ $this->_sleeper = $sleeper;
+ }
+
+ /**
+ * Set the number of emails to send before restarting.
+ *
+ * @param int $threshold
+ */
+ public function setThreshold($threshold)
+ {
+ $this->_threshold = $threshold;
+ }
+
+ /**
+ * Get the number of emails to send before restarting.
+ *
+ * @return int
+ */
+ public function getThreshold()
+ {
+ return $this->_threshold;
+ }
+
+ /**
+ * Set the number of seconds to sleep for during a restart.
+ *
+ * @param int $sleep time
+ */
+ public function setSleepTime($sleep)
+ {
+ $this->_sleep = $sleep;
+ }
+
+ /**
+ * Get the number of seconds to sleep for during a restart.
+ *
+ * @return int
+ */
+ public function getSleepTime()
+ {
+ return $this->_sleep;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ ++$this->_counter;
+ if ($this->_counter >= $this->_threshold) {
+ $transport = $evt->getTransport();
+ $transport->stop();
+ if ($this->_sleep) {
+ $this->sleep($this->_sleep);
+ }
+ $transport->start();
+ $this->_counter = 0;
+ }
+ }
+
+ /**
+ * Sleep for $seconds.
+ *
+ * @param int $seconds
+ */
+ public function sleep($seconds)
+ {
+ if (isset($this->_sleeper)) {
+ $this->_sleeper->sleep($seconds);
+ } else {
+ sleep($seconds);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php
new file mode 100644
index 0000000..f7e18d0
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php
@@ -0,0 +1,164 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Reduces network flooding when sending large amounts of mail.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_BandwidthMonitorPlugin implements Swift_Events_SendListener, Swift_Events_CommandListener, Swift_Events_ResponseListener, Swift_InputByteStream
+{
+ /**
+ * The outgoing traffic counter.
+ *
+ * @var int
+ */
+ private $_out = 0;
+
+ /**
+ * The incoming traffic counter.
+ *
+ * @var int
+ */
+ private $_in = 0;
+
+ /** Bound byte streams */
+ private $_mirrors = array();
+
+ /**
+ * Not used.
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $message->toByteStream($this);
+ }
+
+ /**
+ * Invoked immediately following a command being sent.
+ *
+ * @param Swift_Events_CommandEvent $evt
+ */
+ public function commandSent(Swift_Events_CommandEvent $evt)
+ {
+ $command = $evt->getCommand();
+ $this->_out += strlen($command);
+ }
+
+ /**
+ * Invoked immediately following a response coming back.
+ *
+ * @param Swift_Events_ResponseEvent $evt
+ */
+ public function responseReceived(Swift_Events_ResponseEvent $evt)
+ {
+ $response = $evt->getResponse();
+ $this->_in += strlen($response);
+ }
+
+ /**
+ * Called when a message is sent so that the outgoing counter can be increased.
+ *
+ * @param string $bytes
+ */
+ public function write($bytes)
+ {
+ $this->_out += strlen($bytes);
+ foreach ($this->_mirrors as $stream) {
+ $stream->write($bytes);
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function commit()
+ {
+ }
+
+ /**
+ * Attach $is to this stream.
+ *
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ $this->_mirrors[] = $is;
+ }
+
+ /**
+ * Remove an already bound stream.
+ *
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ foreach ($this->_mirrors as $k => $stream) {
+ if ($is === $stream) {
+ unset($this->_mirrors[$k]);
+ }
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function flushBuffers()
+ {
+ foreach ($this->_mirrors as $stream) {
+ $stream->flushBuffers();
+ }
+ }
+
+ /**
+ * Get the total number of bytes sent to the server.
+ *
+ * @return int
+ */
+ public function getBytesOut()
+ {
+ return $this->_out;
+ }
+
+ /**
+ * Get the total number of bytes received from the server.
+ *
+ * @return int
+ */
+ public function getBytesIn()
+ {
+ return $this->_in;
+ }
+
+ /**
+ * Reset the internal counters to zero.
+ */
+ public function reset()
+ {
+ $this->_out = 0;
+ $this->_in = 0;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php
new file mode 100644
index 0000000..9f9f08b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Allows customization of Messages on-the-fly.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Decorator_Replacements
+{
+ /**
+ * Return the array of replacements for $address.
+ *
+ * This method is invoked once for every single recipient of a message.
+ *
+ * If no replacements can be found, an empty value (NULL) should be returned
+ * and no replacements will then be made on the message.
+ *
+ * @param string $address
+ *
+ * @return array
+ */
+ public function getReplacementsFor($address);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php
new file mode 100644
index 0000000..0762b36
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php
@@ -0,0 +1,204 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Allows customization of Messages on-the-fly.
+ *
+ * @author Chris Corbyn
+ * @author Fabien Potencier
+ */
+class Swift_Plugins_DecoratorPlugin implements Swift_Events_SendListener, Swift_Plugins_Decorator_Replacements
+{
+ /** The replacement map */
+ private $_replacements;
+
+ /** The body as it was before replacements */
+ private $_originalBody;
+
+ /** The original headers of the message, before replacements */
+ private $_originalHeaders = array();
+
+ /** Bodies of children before they are replaced */
+ private $_originalChildBodies = array();
+
+ /** The Message that was last replaced */
+ private $_lastMessage;
+
+ /**
+ * Create a new DecoratorPlugin with $replacements.
+ *
+ * The $replacements can either be an associative array, or an implementation
+ * of {@link Swift_Plugins_Decorator_Replacements}.
+ *
+ * When using an array, it should be of the form:
+ * <code>
+ * $replacements = array(
+ * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"),
+ * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y")
+ * )
+ * </code>
+ *
+ * When using an instance of {@link Swift_Plugins_Decorator_Replacements},
+ * the object should return just the array of replacements for the address
+ * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}.
+ *
+ * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
+ */
+ public function __construct($replacements)
+ {
+ $this->setReplacements($replacements);
+ }
+
+ /**
+ * Sets replacements.
+ *
+ * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
+ *
+ * @see __construct()
+ */
+ public function setReplacements($replacements)
+ {
+ if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) {
+ $this->_replacements = (array) $replacements;
+ } else {
+ $this->_replacements = $replacements;
+ }
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $this->_restoreMessage($message);
+ $to = array_keys($message->getTo());
+ $address = array_shift($to);
+ if ($replacements = $this->getReplacementsFor($address)) {
+ $body = $message->getBody();
+ $search = array_keys($replacements);
+ $replace = array_values($replacements);
+ $bodyReplaced = str_replace(
+ $search, $replace, $body
+ );
+ if ($body != $bodyReplaced) {
+ $this->_originalBody = $body;
+ $message->setBody($bodyReplaced);
+ }
+
+ foreach ($message->getHeaders()->getAll() as $header) {
+ $body = $header->getFieldBodyModel();
+ $count = 0;
+ if (is_array($body)) {
+ $bodyReplaced = array();
+ foreach ($body as $key => $value) {
+ $count1 = 0;
+ $count2 = 0;
+ $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key;
+ $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value;
+ $bodyReplaced[$key] = $value;
+
+ if (!$count && ($count1 || $count2)) {
+ $count = 1;
+ }
+ }
+ } else {
+ $bodyReplaced = str_replace($search, $replace, $body, $count);
+ }
+
+ if ($count) {
+ $this->_originalHeaders[$header->getFieldName()] = $body;
+ $header->setFieldBodyModel($bodyReplaced);
+ }
+ }
+
+ $children = (array) $message->getChildren();
+ foreach ($children as $child) {
+ list($type) = sscanf($child->getContentType(), '%[^/]/%s');
+ if ('text' == $type) {
+ $body = $child->getBody();
+ $bodyReplaced = str_replace(
+ $search, $replace, $body
+ );
+ if ($body != $bodyReplaced) {
+ $child->setBody($bodyReplaced);
+ $this->_originalChildBodies[$child->getId()] = $body;
+ }
+ }
+ }
+ $this->_lastMessage = $message;
+ }
+ }
+
+ /**
+ * Find a map of replacements for the address.
+ *
+ * If this plugin was provided with a delegate instance of
+ * {@link Swift_Plugins_Decorator_Replacements} then the call will be
+ * delegated to it. Otherwise, it will attempt to find the replacements
+ * from the array provided in the constructor.
+ *
+ * If no replacements can be found, an empty value (NULL) is returned.
+ *
+ * @param string $address
+ *
+ * @return array
+ */
+ public function getReplacementsFor($address)
+ {
+ if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) {
+ return $this->_replacements->getReplacementsFor($address);
+ }
+
+ return isset($this->_replacements[$address]) ? $this->_replacements[$address] : null;
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $this->_restoreMessage($evt->getMessage());
+ }
+
+ /** Restore a changed message back to its original state */
+ private function _restoreMessage(Swift_Mime_Message $message)
+ {
+ if ($this->_lastMessage === $message) {
+ if (isset($this->_originalBody)) {
+ $message->setBody($this->_originalBody);
+ $this->_originalBody = null;
+ }
+ if (!empty($this->_originalHeaders)) {
+ foreach ($message->getHeaders()->getAll() as $header) {
+ if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) {
+ $header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]);
+ }
+ }
+ $this->_originalHeaders = array();
+ }
+ if (!empty($this->_originalChildBodies)) {
+ $children = (array) $message->getChildren();
+ foreach ($children as $child) {
+ $id = $child->getId();
+ if (array_key_exists($id, $this->_originalChildBodies)) {
+ $child->setBody($this->_originalChildBodies[$id]);
+ }
+ }
+ $this->_originalChildBodies = array();
+ }
+ $this->_lastMessage = null;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php
new file mode 100644
index 0000000..5834440
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Replaces the sender of a message.
+ *
+ * @author Arjen Brouwer
+ */
+class Swift_Plugins_ImpersonatePlugin implements Swift_Events_SendListener
+{
+ /**
+ * The sender to impersonate.
+ *
+ * @var string
+ */
+ private $_sender;
+
+ /**
+ * Create a new ImpersonatePlugin to impersonate $sender.
+ *
+ * @param string $sender address
+ */
+ public function __construct($sender)
+ {
+ $this->_sender = $sender;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $headers = $message->getHeaders();
+
+ // save current recipients
+ $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath());
+
+ // replace them with the one to send to
+ $message->setReturnPath($this->_sender);
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+
+ // restore original headers
+ $headers = $message->getHeaders();
+
+ if ($headers->has('X-Swift-Return-Path')) {
+ $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress());
+ $headers->removeAll('X-Swift-Return-Path');
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php
new file mode 100644
index 0000000..d9bce89
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Logs events in the Transport system.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Logger
+{
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry);
+
+ /**
+ * Clear the log contents.
+ */
+ public function clear();
+
+ /**
+ * Get this log as a string.
+ *
+ * @return string
+ */
+ public function dump();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php
new file mode 100644
index 0000000..64db438
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php
@@ -0,0 +1,142 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Does real time logging of Transport level information.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_LoggerPlugin implements Swift_Events_CommandListener, Swift_Events_ResponseListener, Swift_Events_TransportChangeListener, Swift_Events_TransportExceptionListener, Swift_Plugins_Logger
+{
+ /** The logger which is delegated to */
+ private $_logger;
+
+ /**
+ * Create a new LoggerPlugin using $logger.
+ *
+ * @param Swift_Plugins_Logger $logger
+ */
+ public function __construct(Swift_Plugins_Logger $logger)
+ {
+ $this->_logger = $logger;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ $this->_logger->add($entry);
+ }
+
+ /**
+ * Clear the log contents.
+ */
+ public function clear()
+ {
+ $this->_logger->clear();
+ }
+
+ /**
+ * Get this log as a string.
+ *
+ * @return string
+ */
+ public function dump()
+ {
+ return $this->_logger->dump();
+ }
+
+ /**
+ * Invoked immediately following a command being sent.
+ *
+ * @param Swift_Events_CommandEvent $evt
+ */
+ public function commandSent(Swift_Events_CommandEvent $evt)
+ {
+ $command = $evt->getCommand();
+ $this->_logger->add(sprintf('>> %s', $command));
+ }
+
+ /**
+ * Invoked immediately following a response coming back.
+ *
+ * @param Swift_Events_ResponseEvent $evt
+ */
+ public function responseReceived(Swift_Events_ResponseEvent $evt)
+ {
+ $response = $evt->getResponse();
+ $this->_logger->add(sprintf('<< %s', $response));
+ }
+
+ /**
+ * Invoked just before a Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf('++ Starting %s', $transportName));
+ }
+
+ /**
+ * Invoked immediately after the Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf('++ %s started', $transportName));
+ }
+
+ /**
+ * Invoked just before a Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf('++ Stopping %s', $transportName));
+ }
+
+ /**
+ * Invoked immediately after the Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf('++ %s stopped', $transportName));
+ }
+
+ /**
+ * Invoked as a TransportException is thrown in the Transport system.
+ *
+ * @param Swift_Events_TransportExceptionEvent $evt
+ */
+ public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt)
+ {
+ $e = $evt->getException();
+ $message = $e->getMessage();
+ $code = $e->getCode();
+ $this->_logger->add(sprintf('!! %s (code: %s)', $message, $code));
+ $message .= PHP_EOL;
+ $message .= 'Log data:'.PHP_EOL;
+ $message .= $this->_logger->dump();
+ $evt->cancelBubble();
+ throw new Swift_TransportException($message, $code, $e->getPrevious());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php
new file mode 100644
index 0000000..865bb0a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Logs to an Array backend.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_Loggers_ArrayLogger implements Swift_Plugins_Logger
+{
+ /**
+ * The log contents.
+ *
+ * @var array
+ */
+ private $_log = array();
+
+ /**
+ * Max size of the log.
+ *
+ * @var int
+ */
+ private $_size = 0;
+
+ /**
+ * Create a new ArrayLogger with a maximum of $size entries.
+ *
+ * @var int
+ */
+ public function __construct($size = 50)
+ {
+ $this->_size = $size;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ $this->_log[] = $entry;
+ while (count($this->_log) > $this->_size) {
+ array_shift($this->_log);
+ }
+ }
+
+ /**
+ * Clear the log contents.
+ */
+ public function clear()
+ {
+ $this->_log = array();
+ }
+
+ /**
+ * Get this log as a string.
+ *
+ * @return string
+ */
+ public function dump()
+ {
+ return implode(PHP_EOL, $this->_log);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php
new file mode 100644
index 0000000..3583297
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Prints all log messages in real time.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_Loggers_EchoLogger implements Swift_Plugins_Logger
+{
+ /** Whether or not HTML should be output */
+ private $_isHtml;
+
+ /**
+ * Create a new EchoLogger.
+ *
+ * @param bool $isHtml
+ */
+ public function __construct($isHtml = true)
+ {
+ $this->_isHtml = $isHtml;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ if ($this->_isHtml) {
+ printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '<br />', PHP_EOL);
+ } else {
+ printf('%s%s', $entry, PHP_EOL);
+ }
+ }
+
+ /**
+ * Not implemented.
+ */
+ public function clear()
+ {
+ }
+
+ /**
+ * Not implemented.
+ */
+ public function dump()
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php
new file mode 100644
index 0000000..5ff1d93
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2011 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Stores all sent emails for further usage.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_Plugins_MessageLogger implements Swift_Events_SendListener
+{
+ /**
+ * @var Swift_Mime_Message[]
+ */
+ private $messages;
+
+ public function __construct()
+ {
+ $this->messages = array();
+ }
+
+ /**
+ * Get the message list.
+ *
+ * @return Swift_Mime_Message[]
+ */
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ /**
+ * Get the message count.
+ *
+ * @return int count
+ */
+ public function countMessages()
+ {
+ return count($this->messages);
+ }
+
+ /**
+ * Empty the message list.
+ */
+ public function clear()
+ {
+ $this->messages = array();
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $this->messages[] = clone $evt->getMessage();
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php
new file mode 100644
index 0000000..fb99e4c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Pop3Connection interface for connecting and disconnecting to a POP3 host.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Pop_Pop3Connection
+{
+ /**
+ * Connect to the POP3 host and throw an Exception if it fails.
+ *
+ * @throws Swift_Plugins_Pop_Pop3Exception
+ */
+ public function connect();
+
+ /**
+ * Disconnect from the POP3 host and throw an Exception if it fails.
+ *
+ * @throws Swift_Plugins_Pop_Pop3Exception
+ */
+ public function disconnect();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.php
new file mode 100644
index 0000000..dc7be0c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Pop3Exception thrown when an error occurs connecting to a POP3 host.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_Pop_Pop3Exception extends Swift_IoException
+{
+ /**
+ * Create a new Pop3Exception with $message.
+ *
+ * @param string $message
+ */
+ public function __construct($message)
+ {
+ parent::__construct($message);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php
new file mode 100644
index 0000000..3146152
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php
@@ -0,0 +1,273 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Makes sure a connection to a POP3 host has been established prior to connecting to SMTP.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_PopBeforeSmtpPlugin implements Swift_Events_TransportChangeListener, Swift_Plugins_Pop_Pop3Connection
+{
+ /** A delegate connection to use (mostly a test hook) */
+ private $_connection;
+
+ /** Hostname of the POP3 server */
+ private $_host;
+
+ /** Port number to connect on */
+ private $_port;
+
+ /** Encryption type to use (if any) */
+ private $_crypto;
+
+ /** Username to use (if any) */
+ private $_username;
+
+ /** Password to use (if any) */
+ private $_password;
+
+ /** Established connection via TCP socket */
+ private $_socket;
+
+ /** Connect timeout in seconds */
+ private $_timeout = 10;
+
+ /** SMTP Transport to bind to */
+ private $_transport;
+
+ /**
+ * Create a new PopBeforeSmtpPlugin for $host and $port.
+ *
+ * @param string $host
+ * @param int $port
+ * @param string $crypto as "tls" or "ssl"
+ */
+ public function __construct($host, $port = 110, $crypto = null)
+ {
+ $this->_host = $host;
+ $this->_port = $port;
+ $this->_crypto = $crypto;
+ }
+
+ /**
+ * Create a new PopBeforeSmtpPlugin for $host and $port.
+ *
+ * @param string $host
+ * @param int $port
+ * @param string $crypto as "tls" or "ssl"
+ *
+ * @return self
+ */
+ public static function newInstance($host, $port = 110, $crypto = null)
+ {
+ return new self($host, $port, $crypto);
+ }
+
+ /**
+ * Set a Pop3Connection to delegate to instead of connecting directly.
+ *
+ * @param Swift_Plugins_Pop_Pop3Connection $connection
+ *
+ * @return $this
+ */
+ public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection)
+ {
+ $this->_connection = $connection;
+
+ return $this;
+ }
+
+ /**
+ * Bind this plugin to a specific SMTP transport instance.
+ *
+ * @param Swift_Transport
+ */
+ public function bindSmtp(Swift_Transport $smtp)
+ {
+ $this->_transport = $smtp;
+ }
+
+ /**
+ * Set the connection timeout in seconds (default 10).
+ *
+ * @param int $timeout
+ *
+ * @return $this
+ */
+ public function setTimeout($timeout)
+ {
+ $this->_timeout = (int) $timeout;
+
+ return $this;
+ }
+
+ /**
+ * Set the username to use when connecting (if needed).
+ *
+ * @param string $username
+ *
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->_username = $username;
+
+ return $this;
+ }
+
+ /**
+ * Set the password to use when connecting (if needed).
+ *
+ * @param string $password
+ *
+ * @return $this
+ */
+ public function setPassword($password)
+ {
+ $this->_password = $password;
+
+ return $this;
+ }
+
+ /**
+ * Connect to the POP3 host and authenticate.
+ *
+ * @throws Swift_Plugins_Pop_Pop3Exception if connection fails
+ */
+ public function connect()
+ {
+ if (isset($this->_connection)) {
+ $this->_connection->connect();
+ } else {
+ if (!isset($this->_socket)) {
+ if (!$socket = fsockopen(
+ $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout)) {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr)
+ );
+ }
+ $this->_socket = $socket;
+
+ if (false === $greeting = fgets($this->_socket)) {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('Failed to connect to POP3 host [%s]', trim($greeting))
+ );
+ }
+
+ $this->_assertOk($greeting);
+
+ if ($this->_username) {
+ $this->_command(sprintf("USER %s\r\n", $this->_username));
+ $this->_command(sprintf("PASS %s\r\n", $this->_password));
+ }
+ }
+ }
+ }
+
+ /**
+ * Disconnect from the POP3 host.
+ */
+ public function disconnect()
+ {
+ if (isset($this->_connection)) {
+ $this->_connection->disconnect();
+ } else {
+ $this->_command("QUIT\r\n");
+ if (!fclose($this->_socket)) {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('POP3 host [%s] connection could not be stopped', $this->_host)
+ );
+ }
+ $this->_socket = null;
+ }
+ }
+
+ /**
+ * Invoked just before a Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ if (isset($this->_transport)) {
+ if ($this->_transport !== $evt->getTransport()) {
+ return;
+ }
+ }
+
+ $this->connect();
+ $this->disconnect();
+ }
+
+ /**
+ * Not used.
+ */
+ public function transportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ }
+
+ /**
+ * Not used.
+ */
+ public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ }
+
+ /**
+ * Not used.
+ */
+ public function transportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ }
+
+ private function _command($command)
+ {
+ if (!fwrite($this->_socket, $command)) {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('Failed to write command [%s] to POP3 host', trim($command))
+ );
+ }
+
+ if (false === $response = fgets($this->_socket)) {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('Failed to read from POP3 host after command [%s]', trim($command))
+ );
+ }
+
+ $this->_assertOk($response);
+
+ return $response;
+ }
+
+ private function _assertOk($response)
+ {
+ if (substr($response, 0, 3) != '+OK') {
+ throw new Swift_Plugins_Pop_Pop3Exception(
+ sprintf('POP3 command failed [%s]', trim($response))
+ );
+ }
+ }
+
+ private function _getHostString()
+ {
+ $host = $this->_host;
+ switch (strtolower($this->_crypto)) {
+ case 'ssl':
+ $host = 'ssl://'.$host;
+ break;
+
+ case 'tls':
+ $host = 'tls://'.$host;
+ break;
+ }
+
+ return $host;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php
new file mode 100644
index 0000000..c3a1f86
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php
@@ -0,0 +1,213 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Redirects all email to a single recipient.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_Plugins_RedirectingPlugin implements Swift_Events_SendListener
+{
+ /**
+ * The recipient who will receive all messages.
+ *
+ * @var mixed
+ */
+ private $_recipient;
+
+ /**
+ * List of regular expression for recipient whitelisting.
+ *
+ * @var array
+ */
+ private $_whitelist = array();
+
+ /**
+ * Create a new RedirectingPlugin.
+ *
+ * @param mixed $recipient
+ * @param array $whitelist
+ */
+ public function __construct($recipient, array $whitelist = array())
+ {
+ $this->_recipient = $recipient;
+ $this->_whitelist = $whitelist;
+ }
+
+ /**
+ * Set the recipient of all messages.
+ *
+ * @param mixed $recipient
+ */
+ public function setRecipient($recipient)
+ {
+ $this->_recipient = $recipient;
+ }
+
+ /**
+ * Get the recipient of all messages.
+ *
+ * @return mixed
+ */
+ public function getRecipient()
+ {
+ return $this->_recipient;
+ }
+
+ /**
+ * Set a list of regular expressions to whitelist certain recipients.
+ *
+ * @param array $whitelist
+ */
+ public function setWhitelist(array $whitelist)
+ {
+ $this->_whitelist = $whitelist;
+ }
+
+ /**
+ * Get the whitelist.
+ *
+ * @return array
+ */
+ public function getWhitelist()
+ {
+ return $this->_whitelist;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $headers = $message->getHeaders();
+
+ // conditionally save current recipients
+
+ if ($headers->has('to')) {
+ $headers->addMailboxHeader('X-Swift-To', $message->getTo());
+ }
+
+ if ($headers->has('cc')) {
+ $headers->addMailboxHeader('X-Swift-Cc', $message->getCc());
+ }
+
+ if ($headers->has('bcc')) {
+ $headers->addMailboxHeader('X-Swift-Bcc', $message->getBcc());
+ }
+
+ // Filter remaining headers against whitelist
+ $this->_filterHeaderSet($headers, 'To');
+ $this->_filterHeaderSet($headers, 'Cc');
+ $this->_filterHeaderSet($headers, 'Bcc');
+
+ // Add each hard coded recipient
+ $to = $message->getTo();
+ if (null === $to) {
+ $to = array();
+ }
+
+ foreach ((array) $this->_recipient as $recipient) {
+ if (!array_key_exists($recipient, $to)) {
+ $message->addTo($recipient);
+ }
+ }
+ }
+
+ /**
+ * Filter header set against a whitelist of regular expressions.
+ *
+ * @param Swift_Mime_HeaderSet $headerSet
+ * @param string $type
+ */
+ private function _filterHeaderSet(Swift_Mime_HeaderSet $headerSet, $type)
+ {
+ foreach ($headerSet->getAll($type) as $headers) {
+ $headers->setNameAddresses($this->_filterNameAddresses($headers->getNameAddresses()));
+ }
+ }
+
+ /**
+ * Filtered list of addresses => name pairs.
+ *
+ * @param array $recipients
+ *
+ * @return array
+ */
+ private function _filterNameAddresses(array $recipients)
+ {
+ $filtered = array();
+
+ foreach ($recipients as $address => $name) {
+ if ($this->_isWhitelisted($address)) {
+ $filtered[$address] = $name;
+ }
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * Matches address against whitelist of regular expressions.
+ *
+ * @param $recipient
+ *
+ * @return bool
+ */
+ protected function _isWhitelisted($recipient)
+ {
+ if (in_array($recipient, (array) $this->_recipient)) {
+ return true;
+ }
+
+ foreach ($this->_whitelist as $pattern) {
+ if (preg_match($pattern, $recipient)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $this->_restoreMessage($evt->getMessage());
+ }
+
+ private function _restoreMessage(Swift_Mime_Message $message)
+ {
+ // restore original headers
+ $headers = $message->getHeaders();
+
+ if ($headers->has('X-Swift-To')) {
+ $message->setTo($headers->get('X-Swift-To')->getNameAddresses());
+ $headers->removeAll('X-Swift-To');
+ } else {
+ $message->setTo(null);
+ }
+
+ if ($headers->has('X-Swift-Cc')) {
+ $message->setCc($headers->get('X-Swift-Cc')->getNameAddresses());
+ $headers->removeAll('X-Swift-Cc');
+ }
+
+ if ($headers->has('X-Swift-Bcc')) {
+ $message->setBcc($headers->get('X-Swift-Bcc')->getNameAddresses());
+ $headers->removeAll('X-Swift-Bcc');
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php
new file mode 100644
index 0000000..0f21b7d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * The Reporter plugin sends pass/fail notification to a Reporter.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Reporter
+{
+ /** The recipient was accepted for delivery */
+ const RESULT_PASS = 0x01;
+
+ /** The recipient could not be accepted */
+ const RESULT_FAIL = 0x10;
+
+ /**
+ * Notifies this ReportNotifier that $address failed or succeeded.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string $address
+ * @param int $result from {@link RESULT_PASS, RESULT_FAIL}
+ */
+ public function notify(Swift_Mime_Message $message, $address, $result);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php
new file mode 100644
index 0000000..a37901f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Does real time reporting of pass/fail for each recipient.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_ReporterPlugin implements Swift_Events_SendListener
+{
+ /**
+ * The reporter backend which takes notifications.
+ *
+ * @var Swift_Plugins_Reporter
+ */
+ private $_reporter;
+
+ /**
+ * Create a new ReporterPlugin using $reporter.
+ *
+ * @param Swift_Plugins_Reporter $reporter
+ */
+ public function __construct(Swift_Plugins_Reporter $reporter)
+ {
+ $this->_reporter = $reporter;
+ }
+
+ /**
+ * Not used.
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $failures = array_flip($evt->getFailedRecipients());
+ foreach ((array) $message->getTo() as $address => $null) {
+ $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
+ }
+ foreach ((array) $message->getCc() as $address => $null) {
+ $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
+ }
+ foreach ((array) $message->getBcc() as $address => $null) {
+ $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php
new file mode 100644
index 0000000..cad9d16
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A reporter which "collects" failures for the Reporter plugin.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_Reporters_HitReporter implements Swift_Plugins_Reporter
+{
+ /**
+ * The list of failures.
+ *
+ * @var array
+ */
+ private $_failures = array();
+
+ private $_failures_cache = array();
+
+ /**
+ * Notifies this ReportNotifier that $address failed or succeeded.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string $address
+ * @param int $result from {@link RESULT_PASS, RESULT_FAIL}
+ */
+ public function notify(Swift_Mime_Message $message, $address, $result)
+ {
+ if (self::RESULT_FAIL == $result && !isset($this->_failures_cache[$address])) {
+ $this->_failures[] = $address;
+ $this->_failures_cache[$address] = true;
+ }
+ }
+
+ /**
+ * Get an array of addresses for which delivery failed.
+ *
+ * @return array
+ */
+ public function getFailedRecipients()
+ {
+ return $this->_failures;
+ }
+
+ /**
+ * Clear the buffer (empty the list).
+ */
+ public function clear()
+ {
+ $this->_failures = $this->_failures_cache = array();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php
new file mode 100644
index 0000000..c625935
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A HTML output reporter for the Reporter plugin.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_Reporters_HtmlReporter implements Swift_Plugins_Reporter
+{
+ /**
+ * Notifies this ReportNotifier that $address failed or succeeded.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string $address
+ * @param int $result from {@see RESULT_PASS, RESULT_FAIL}
+ */
+ public function notify(Swift_Mime_Message $message, $address, $result)
+ {
+ if (self::RESULT_PASS == $result) {
+ echo '<div style="color: #fff; background: #006600; padding: 2px; margin: 2px;">'.PHP_EOL;
+ echo 'PASS '.$address.PHP_EOL;
+ echo '</div>'.PHP_EOL;
+ flush();
+ } else {
+ echo '<div style="color: #fff; background: #880000; padding: 2px; margin: 2px;">'.PHP_EOL;
+ echo 'FAIL '.$address.PHP_EOL;
+ echo '</div>'.PHP_EOL;
+ flush();
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php
new file mode 100644
index 0000000..595c0f6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sleeps for a duration of time.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Sleeper
+{
+ /**
+ * Sleep for $seconds.
+ *
+ * @param int $seconds
+ */
+ public function sleep($seconds);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php
new file mode 100644
index 0000000..2f4b9a7
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php
@@ -0,0 +1,200 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Throttles the rate at which emails are sent.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Plugins_ThrottlerPlugin extends Swift_Plugins_BandwidthMonitorPlugin implements Swift_Plugins_Sleeper, Swift_Plugins_Timer
+{
+ /** Flag for throttling in bytes per minute */
+ const BYTES_PER_MINUTE = 0x01;
+
+ /** Flag for throttling in emails per second (Amazon SES) */
+ const MESSAGES_PER_SECOND = 0x11;
+
+ /** Flag for throttling in emails per minute */
+ const MESSAGES_PER_MINUTE = 0x10;
+
+ /**
+ * The Sleeper instance for sleeping.
+ *
+ * @var Swift_Plugins_Sleeper
+ */
+ private $_sleeper;
+
+ /**
+ * The Timer instance which provides the timestamp.
+ *
+ * @var Swift_Plugins_Timer
+ */
+ private $_timer;
+
+ /**
+ * The time at which the first email was sent.
+ *
+ * @var int
+ */
+ private $_start;
+
+ /**
+ * The rate at which messages should be sent.
+ *
+ * @var int
+ */
+ private $_rate;
+
+ /**
+ * The mode for throttling.
+ *
+ * This is {@link BYTES_PER_MINUTE} or {@link MESSAGES_PER_MINUTE}
+ *
+ * @var int
+ */
+ private $_mode;
+
+ /**
+ * An internal counter of the number of messages sent.
+ *
+ * @var int
+ */
+ private $_messages = 0;
+
+ /**
+ * Create a new ThrottlerPlugin.
+ *
+ * @param int $rate
+ * @param int $mode, defaults to {@link BYTES_PER_MINUTE}
+ * @param Swift_Plugins_Sleeper $sleeper (only needed in testing)
+ * @param Swift_Plugins_Timer $timer (only needed in testing)
+ */
+ public function __construct($rate, $mode = self::BYTES_PER_MINUTE, Swift_Plugins_Sleeper $sleeper = null, Swift_Plugins_Timer $timer = null)
+ {
+ $this->_rate = $rate;
+ $this->_mode = $mode;
+ $this->_sleeper = $sleeper;
+ $this->_timer = $timer;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $time = $this->getTimestamp();
+ if (!isset($this->_start)) {
+ $this->_start = $time;
+ }
+ $duration = $time - $this->_start;
+
+ switch ($this->_mode) {
+ case self::BYTES_PER_MINUTE:
+ $sleep = $this->_throttleBytesPerMinute($duration);
+ break;
+ case self::MESSAGES_PER_SECOND:
+ $sleep = $this->_throttleMessagesPerSecond($duration);
+ break;
+ case self::MESSAGES_PER_MINUTE:
+ $sleep = $this->_throttleMessagesPerMinute($duration);
+ break;
+ default:
+ $sleep = 0;
+ break;
+ }
+
+ if ($sleep > 0) {
+ $this->sleep($sleep);
+ }
+ }
+
+ /**
+ * Invoked when a Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ parent::sendPerformed($evt);
+ ++$this->_messages;
+ }
+
+ /**
+ * Sleep for $seconds.
+ *
+ * @param int $seconds
+ */
+ public function sleep($seconds)
+ {
+ if (isset($this->_sleeper)) {
+ $this->_sleeper->sleep($seconds);
+ } else {
+ sleep($seconds);
+ }
+ }
+
+ /**
+ * Get the current UNIX timestamp.
+ *
+ * @return int
+ */
+ public function getTimestamp()
+ {
+ if (isset($this->_timer)) {
+ return $this->_timer->getTimestamp();
+ }
+
+ return time();
+ }
+
+ /**
+ * Get a number of seconds to sleep for.
+ *
+ * @param int $timePassed
+ *
+ * @return int
+ */
+ private function _throttleBytesPerMinute($timePassed)
+ {
+ $expectedDuration = $this->getBytesOut() / ($this->_rate / 60);
+
+ return (int) ceil($expectedDuration - $timePassed);
+ }
+
+ /**
+ * Get a number of seconds to sleep for.
+ *
+ * @param int $timePassed
+ *
+ * @return int
+ */
+ private function _throttleMessagesPerSecond($timePassed)
+ {
+ $expectedDuration = $this->_messages / ($this->_rate);
+
+ return (int) ceil($expectedDuration - $timePassed);
+ }
+
+ /**
+ * Get a number of seconds to sleep for.
+ *
+ * @param int $timePassed
+ *
+ * @return int
+ */
+ private function _throttleMessagesPerMinute($timePassed)
+ {
+ $expectedDuration = $this->_messages / ($this->_rate / 60);
+
+ return (int) ceil($expectedDuration - $timePassed);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php
new file mode 100644
index 0000000..9c8deb3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Provides timestamp data.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Plugins_Timer
+{
+ /**
+ * Get the current UNIX timestamp.
+ *
+ * @return int
+ */
+ public function getTimestamp();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php
new file mode 100644
index 0000000..83cbddc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Changes some global preference settings in Swift Mailer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Preferences
+{
+ /** Singleton instance */
+ private static $_instance = null;
+
+ /** Constructor not to be used */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Gets the instance of Preferences.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (!isset(self::$_instance)) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Set the default charset used.
+ *
+ * @param string $charset
+ *
+ * @return $this
+ */
+ public function setCharset($charset)
+ {
+ Swift_DependencyContainer::getInstance()->register('properties.charset')->asValue($charset);
+
+ return $this;
+ }
+
+ /**
+ * Set the directory where temporary files can be saved.
+ *
+ * @param string $dir
+ *
+ * @return $this
+ */
+ public function setTempDir($dir)
+ {
+ Swift_DependencyContainer::getInstance()->register('tempdir')->asValue($dir);
+
+ return $this;
+ }
+
+ /**
+ * Set the type of cache to use (i.e. "disk" or "array").
+ *
+ * @param string $type
+ *
+ * @return $this
+ */
+ public function setCacheType($type)
+ {
+ Swift_DependencyContainer::getInstance()->register('cache')->asAliasOf(sprintf('cache.%s', $type));
+
+ return $this;
+ }
+
+ /**
+ * Set the QuotedPrintable dot escaper preference.
+ *
+ * @param bool $dotEscape
+ *
+ * @return $this
+ */
+ public function setQPDotEscape($dotEscape)
+ {
+ $dotEscape = !empty($dotEscape);
+ Swift_DependencyContainer::getInstance()
+ ->register('mime.qpcontentencoder')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder')
+ ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer'))
+ ->addConstructorValue($dotEscape);
+
+ return $this;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php
new file mode 100644
index 0000000..2897474
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Creates StreamFilters.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_ReplacementFilterFactory
+{
+ /**
+ * Create a filter to replace $search with $replace.
+ *
+ * @param mixed $search
+ * @param mixed $replace
+ *
+ * @return Swift_StreamFilter
+ */
+ public function createFilter($search, $replace);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php
new file mode 100644
index 0000000..81bc403
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * RFC Compliance Exception class.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_RfcComplianceException extends Swift_SwiftException
+{
+ /**
+ * Create a new RfcComplianceException with $message.
+ *
+ * @param string $message
+ */
+ public function __construct($message)
+ {
+ parent::__construct($message);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php
new file mode 100644
index 0000000..47ae7a5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_SendmailTransport extends Swift_Transport_SendmailTransport
+{
+ /**
+ * Create a new SendmailTransport, optionally using $command for sending.
+ *
+ * @param string $command
+ */
+ public function __construct($command = '/usr/sbin/sendmail -bs')
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_SendmailTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.sendmail')
+ );
+
+ $this->setCommand($command);
+ }
+
+ /**
+ * Create a new SendmailTransport instance.
+ *
+ * @param string $command
+ *
+ * @return self
+ */
+ public static function newInstance($command = '/usr/sbin/sendmail -bs')
+ {
+ return new self($command);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php
new file mode 100644
index 0000000..2e7a872
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Signed Message, message that can be signed using a signer.
+ *
+ * This class is only kept for compatibility
+ *
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ *
+ * @deprecated
+ */
+class Swift_SignedMessage extends Swift_Message
+{
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php
new file mode 100644
index 0000000..2d8176d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Base Class of Signer Infrastructure.
+ *
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+interface Swift_Signer
+{
+ public function reset();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php
new file mode 100644
index 0000000..8e66e18
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Body Signer Interface used to apply Body-Based Signature to a message.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+interface Swift_Signers_BodySigner extends Swift_Signer
+{
+ /**
+ * Change the Swift_Signed_Message to apply the singing.
+ *
+ * @param Swift_Message $message
+ *
+ * @return self
+ */
+ public function signMessage(Swift_Message $message);
+
+ /**
+ * Return the list of header a signer might tamper.
+ *
+ * @return array
+ */
+ public function getAlteredHeaders();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php
new file mode 100644
index 0000000..454e84b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php
@@ -0,0 +1,712 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * DKIM Signer used to apply DKIM Signature to a message.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner
+{
+ /**
+ * PrivateKey.
+ *
+ * @var string
+ */
+ protected $_privateKey;
+
+ /**
+ * DomainName.
+ *
+ * @var string
+ */
+ protected $_domainName;
+
+ /**
+ * Selector.
+ *
+ * @var string
+ */
+ protected $_selector;
+
+ /**
+ * Hash algorithm used.
+ *
+ * @see RFC6376 3.3: Signers MUST implement and SHOULD sign using rsa-sha256.
+ *
+ * @var string
+ */
+ protected $_hashAlgorithm = 'rsa-sha256';
+
+ /**
+ * Body canon method.
+ *
+ * @var string
+ */
+ protected $_bodyCanon = 'simple';
+
+ /**
+ * Header canon method.
+ *
+ * @var string
+ */
+ protected $_headerCanon = 'simple';
+
+ /**
+ * Headers not being signed.
+ *
+ * @var array
+ */
+ protected $_ignoredHeaders = array('return-path' => true);
+
+ /**
+ * Signer identity.
+ *
+ * @var string
+ */
+ protected $_signerIdentity;
+
+ /**
+ * BodyLength.
+ *
+ * @var int
+ */
+ protected $_bodyLen = 0;
+
+ /**
+ * Maximum signedLen.
+ *
+ * @var int
+ */
+ protected $_maxLen = PHP_INT_MAX;
+
+ /**
+ * Embbed bodyLen in signature.
+ *
+ * @var bool
+ */
+ protected $_showLen = false;
+
+ /**
+ * When the signature has been applied (true means time()), false means not embedded.
+ *
+ * @var mixed
+ */
+ protected $_signatureTimestamp = true;
+
+ /**
+ * When will the signature expires false means not embedded, if sigTimestamp is auto
+ * Expiration is relative, otherwise it's absolute.
+ *
+ * @var int
+ */
+ protected $_signatureExpiration = false;
+
+ /**
+ * Must we embed signed headers?
+ *
+ * @var bool
+ */
+ protected $_debugHeaders = false;
+
+ // work variables
+ /**
+ * Headers used to generate hash.
+ *
+ * @var array
+ */
+ protected $_signedHeaders = array();
+
+ /**
+ * If debugHeaders is set store debugData here.
+ *
+ * @var string
+ */
+ private $_debugHeadersData = '';
+
+ /**
+ * Stores the bodyHash.
+ *
+ * @var string
+ */
+ private $_bodyHash = '';
+
+ /**
+ * Stores the signature header.
+ *
+ * @var Swift_Mime_Headers_ParameterizedHeader
+ */
+ protected $_dkimHeader;
+
+ private $_bodyHashHandler;
+
+ private $_headerHash;
+
+ private $_headerCanonData = '';
+
+ private $_bodyCanonEmptyCounter = 0;
+
+ private $_bodyCanonIgnoreStart = 2;
+
+ private $_bodyCanonSpace = false;
+
+ private $_bodyCanonLastChar = null;
+
+ private $_bodyCanonLine = '';
+
+ private $_bound = array();
+
+ /**
+ * Constructor.
+ *
+ * @param string $privateKey
+ * @param string $domainName
+ * @param string $selector
+ */
+ public function __construct($privateKey, $domainName, $selector)
+ {
+ $this->_privateKey = $privateKey;
+ $this->_domainName = $domainName;
+ $this->_signerIdentity = '@'.$domainName;
+ $this->_selector = $selector;
+
+ // keep fallback hash algorithm sha1 if php version is lower than 5.4.8
+ if (PHP_VERSION_ID < 50408) {
+ $this->_hashAlgorithm = 'rsa-sha1';
+ }
+ }
+
+ /**
+ * Instanciate DKIMSigner.
+ *
+ * @param string $privateKey
+ * @param string $domainName
+ * @param string $selector
+ *
+ * @return self
+ */
+ public static function newInstance($privateKey, $domainName, $selector)
+ {
+ return new static($privateKey, $domainName, $selector);
+ }
+
+ /**
+ * Reset the Signer.
+ *
+ * @see Swift_Signer::reset()
+ */
+ public function reset()
+ {
+ $this->_headerHash = null;
+ $this->_signedHeaders = array();
+ $this->_bodyHash = null;
+ $this->_bodyHashHandler = null;
+ $this->_bodyCanonIgnoreStart = 2;
+ $this->_bodyCanonEmptyCounter = 0;
+ $this->_bodyCanonLastChar = null;
+ $this->_bodyCanonSpace = false;
+ }
+
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * Writing may not happen immediately if the stream chooses to buffer. If
+ * you want to write these bytes with immediate effect, call {@link commit()}
+ * after calling write().
+ *
+ * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
+ * second, etc etc).
+ *
+ * @param string $bytes
+ *
+ * @throws Swift_IoException
+ *
+ * @return int
+ */
+ // TODO fix return
+ public function write($bytes)
+ {
+ $this->_canonicalizeBody($bytes);
+ foreach ($this->_bound as $is) {
+ $is->write($bytes);
+ }
+ }
+
+ /**
+ * For any bytes that are currently buffered inside the stream, force them
+ * off the buffer.
+ */
+ public function commit()
+ {
+ // Nothing to do
+ return;
+ }
+
+ /**
+ * Attach $is to this stream.
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ // Don't have to mirror anything
+ $this->_bound[] = $is;
+
+ return;
+ }
+
+ /**
+ * Remove an already bound stream.
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ // Don't have to mirror anything
+ foreach ($this->_bound as $k => $stream) {
+ if ($stream === $is) {
+ unset($this->_bound[$k]);
+
+ return;
+ }
+ }
+ }
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ *
+ * @throws Swift_IoException
+ */
+ public function flushBuffers()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1.
+ *
+ * @param string $hash 'rsa-sha1' or 'rsa-sha256'
+ *
+ * @throws Swift_SwiftException
+ *
+ * @return $this
+ */
+ public function setHashAlgorithm($hash)
+ {
+ switch ($hash) {
+ case 'rsa-sha1':
+ $this->_hashAlgorithm = 'rsa-sha1';
+ break;
+ case 'rsa-sha256':
+ $this->_hashAlgorithm = 'rsa-sha256';
+ if (!defined('OPENSSL_ALGO_SHA256')) {
+ throw new Swift_SwiftException('Unable to set sha256 as it is not supported by OpenSSL.');
+ }
+ break;
+ default:
+ throw new Swift_SwiftException('Unable to set the hash algorithm, must be one of rsa-sha1 or rsa-sha256 (%s given).', $hash);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the body canonicalization algorithm.
+ *
+ * @param string $canon
+ *
+ * @return $this
+ */
+ public function setBodyCanon($canon)
+ {
+ if ($canon == 'relaxed') {
+ $this->_bodyCanon = 'relaxed';
+ } else {
+ $this->_bodyCanon = 'simple';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the header canonicalization algorithm.
+ *
+ * @param string $canon
+ *
+ * @return $this
+ */
+ public function setHeaderCanon($canon)
+ {
+ if ($canon == 'relaxed') {
+ $this->_headerCanon = 'relaxed';
+ } else {
+ $this->_headerCanon = 'simple';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the signer identity.
+ *
+ * @param string $identity
+ *
+ * @return $this
+ */
+ public function setSignerIdentity($identity)
+ {
+ $this->_signerIdentity = $identity;
+
+ return $this;
+ }
+
+ /**
+ * Set the length of the body to sign.
+ *
+ * @param mixed $len (bool or int)
+ *
+ * @return $this
+ */
+ public function setBodySignedLen($len)
+ {
+ if ($len === true) {
+ $this->_showLen = true;
+ $this->_maxLen = PHP_INT_MAX;
+ } elseif ($len === false) {
+ $this->_showLen = false;
+ $this->_maxLen = PHP_INT_MAX;
+ } else {
+ $this->_showLen = true;
+ $this->_maxLen = (int) $len;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the signature timestamp.
+ *
+ * @param int $time A timestamp
+ *
+ * @return $this
+ */
+ public function setSignatureTimestamp($time)
+ {
+ $this->_signatureTimestamp = $time;
+
+ return $this;
+ }
+
+ /**
+ * Set the signature expiration timestamp.
+ *
+ * @param int $time A timestamp
+ *
+ * @return $this
+ */
+ public function setSignatureExpiration($time)
+ {
+ $this->_signatureExpiration = $time;
+
+ return $this;
+ }
+
+ /**
+ * Enable / disable the DebugHeaders.
+ *
+ * @param bool $debug
+ *
+ * @return Swift_Signers_DKIMSigner
+ */
+ public function setDebugHeaders($debug)
+ {
+ $this->_debugHeaders = (bool) $debug;
+
+ return $this;
+ }
+
+ /**
+ * Start Body.
+ */
+ public function startBody()
+ {
+ // Init
+ switch ($this->_hashAlgorithm) {
+ case 'rsa-sha256':
+ $this->_bodyHashHandler = hash_init('sha256');
+ break;
+ case 'rsa-sha1':
+ $this->_bodyHashHandler = hash_init('sha1');
+ break;
+ }
+ $this->_bodyCanonLine = '';
+ }
+
+ /**
+ * End Body.
+ */
+ public function endBody()
+ {
+ $this->_endOfBody();
+ }
+
+ /**
+ * Returns the list of Headers Tampered by this plugin.
+ *
+ * @return array
+ */
+ public function getAlteredHeaders()
+ {
+ if ($this->_debugHeaders) {
+ return array('DKIM-Signature', 'X-DebugHash');
+ } else {
+ return array('DKIM-Signature');
+ }
+ }
+
+ /**
+ * Adds an ignored Header.
+ *
+ * @param string $header_name
+ *
+ * @return Swift_Signers_DKIMSigner
+ */
+ public function ignoreHeader($header_name)
+ {
+ $this->_ignoredHeaders[strtolower($header_name)] = true;
+
+ return $this;
+ }
+
+ /**
+ * Set the headers to sign.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ *
+ * @return Swift_Signers_DKIMSigner
+ */
+ public function setHeaders(Swift_Mime_HeaderSet $headers)
+ {
+ $this->_headerCanonData = '';
+ // Loop through Headers
+ $listHeaders = $headers->listAll();
+ foreach ($listHeaders as $hName) {
+ // Check if we need to ignore Header
+ if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
+ if ($headers->has($hName)) {
+ $tmp = $headers->getAll($hName);
+ foreach ($tmp as $header) {
+ if ($header->getFieldBody() != '') {
+ $this->_addHeader($header->toString());
+ $this->_signedHeaders[] = $header->getFieldName();
+ }
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add the signature to the given Headers.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ *
+ * @return Swift_Signers_DKIMSigner
+ */
+ public function addSignature(Swift_Mime_HeaderSet $headers)
+ {
+ // Prepare the DKIM-Signature
+ $params = array('v' => '1', 'a' => $this->_hashAlgorithm, 'bh' => base64_encode($this->_bodyHash), 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'i' => $this->_signerIdentity, 's' => $this->_selector);
+ if ($this->_bodyCanon != 'simple') {
+ $params['c'] = $this->_headerCanon.'/'.$this->_bodyCanon;
+ } elseif ($this->_headerCanon != 'simple') {
+ $params['c'] = $this->_headerCanon;
+ }
+ if ($this->_showLen) {
+ $params['l'] = $this->_bodyLen;
+ }
+ if ($this->_signatureTimestamp === true) {
+ $params['t'] = time();
+ if ($this->_signatureExpiration !== false) {
+ $params['x'] = $params['t'] + $this->_signatureExpiration;
+ }
+ } else {
+ if ($this->_signatureTimestamp !== false) {
+ $params['t'] = $this->_signatureTimestamp;
+ }
+ if ($this->_signatureExpiration !== false) {
+ $params['x'] = $this->_signatureExpiration;
+ }
+ }
+ if ($this->_debugHeaders) {
+ $params['z'] = implode('|', $this->_debugHeadersData);
+ }
+ $string = '';
+ foreach ($params as $k => $v) {
+ $string .= $k.'='.$v.'; ';
+ }
+ $string = trim($string);
+ $headers->addTextHeader('DKIM-Signature', $string);
+ // Add the last DKIM-Signature
+ $tmp = $headers->getAll('DKIM-Signature');
+ $this->_dkimHeader = end($tmp);
+ $this->_addHeader(trim($this->_dkimHeader->toString())."\r\n b=", true);
+ $this->_endOfHeaders();
+ if ($this->_debugHeaders) {
+ $headers->addTextHeader('X-DebugHash', base64_encode($this->_headerHash));
+ }
+ $this->_dkimHeader->setValue($string.' b='.trim(chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' ')));
+
+ return $this;
+ }
+
+ /* Private helpers */
+
+ protected function _addHeader($header, $is_sig = false)
+ {
+ switch ($this->_headerCanon) {
+ case 'relaxed':
+ // Prepare Header and cascade
+ $exploded = explode(':', $header, 2);
+ $name = strtolower(trim($exploded[0]));
+ $value = str_replace("\r\n", '', $exploded[1]);
+ $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
+ $header = $name.':'.trim($value).($is_sig ? '' : "\r\n");
+ case 'simple':
+ // Nothing to do
+ }
+ $this->_addToHeaderHash($header);
+ }
+
+ /**
+ * @deprecated This method is currently useless in this class but it must be
+ * kept for BC reasons due to its "protected" scope. This method
+ * might be overridden by custom client code.
+ */
+ protected function _endOfHeaders()
+ {
+ }
+
+ protected function _canonicalizeBody($string)
+ {
+ $len = strlen($string);
+ $canon = '';
+ $method = ($this->_bodyCanon == 'relaxed');
+ for ($i = 0; $i < $len; ++$i) {
+ if ($this->_bodyCanonIgnoreStart > 0) {
+ --$this->_bodyCanonIgnoreStart;
+ continue;
+ }
+ switch ($string[$i]) {
+ case "\r":
+ $this->_bodyCanonLastChar = "\r";
+ break;
+ case "\n":
+ if ($this->_bodyCanonLastChar == "\r") {
+ if ($method) {
+ $this->_bodyCanonSpace = false;
+ }
+ if ($this->_bodyCanonLine == '') {
+ ++$this->_bodyCanonEmptyCounter;
+ } else {
+ $this->_bodyCanonLine = '';
+ $canon .= "\r\n";
+ }
+ } else {
+ // Wooops Error
+ // todo handle it but should never happen
+ }
+ break;
+ case ' ':
+ case "\t":
+ if ($method) {
+ $this->_bodyCanonSpace = true;
+ break;
+ }
+ default:
+ if ($this->_bodyCanonEmptyCounter > 0) {
+ $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
+ $this->_bodyCanonEmptyCounter = 0;
+ }
+ if ($this->_bodyCanonSpace) {
+ $this->_bodyCanonLine .= ' ';
+ $canon .= ' ';
+ $this->_bodyCanonSpace = false;
+ }
+ $this->_bodyCanonLine .= $string[$i];
+ $canon .= $string[$i];
+ }
+ }
+ $this->_addToBodyHash($canon);
+ }
+
+ protected function _endOfBody()
+ {
+ // Add trailing Line return if last line is non empty
+ if (strlen($this->_bodyCanonLine) > 0) {
+ $this->_addToBodyHash("\r\n");
+ }
+ $this->_bodyHash = hash_final($this->_bodyHashHandler, true);
+ }
+
+ private function _addToBodyHash($string)
+ {
+ $len = strlen($string);
+ if ($len > ($new_len = ($this->_maxLen - $this->_bodyLen))) {
+ $string = substr($string, 0, $new_len);
+ $len = $new_len;
+ }
+ hash_update($this->_bodyHashHandler, $string);
+ $this->_bodyLen += $len;
+ }
+
+ private function _addToHeaderHash($header)
+ {
+ if ($this->_debugHeaders) {
+ $this->_debugHeadersData[] = trim($header);
+ }
+ $this->_headerCanonData .= $header;
+ }
+
+ /**
+ * @throws Swift_SwiftException
+ *
+ * @return string
+ */
+ private function _getEncryptedHash()
+ {
+ $signature = '';
+
+ switch ($this->_hashAlgorithm) {
+ case 'rsa-sha1':
+ $algorithm = OPENSSL_ALGO_SHA1;
+ break;
+ case 'rsa-sha256':
+ $algorithm = OPENSSL_ALGO_SHA256;
+ break;
+ }
+ $pkeyId = openssl_get_privatekey($this->_privateKey);
+ if (!$pkeyId) {
+ throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']');
+ }
+ if (openssl_sign($this->_headerCanonData, $signature, $pkeyId, $algorithm)) {
+ return $signature;
+ }
+ throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php
new file mode 100644
index 0000000..0365363
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php
@@ -0,0 +1,524 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * DomainKey Signer used to apply DomainKeys Signature to a message.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner
+{
+ /**
+ * PrivateKey.
+ *
+ * @var string
+ */
+ protected $_privateKey;
+
+ /**
+ * DomainName.
+ *
+ * @var string
+ */
+ protected $_domainName;
+
+ /**
+ * Selector.
+ *
+ * @var string
+ */
+ protected $_selector;
+
+ /**
+ * Hash algorithm used.
+ *
+ * @var string
+ */
+ protected $_hashAlgorithm = 'rsa-sha1';
+
+ /**
+ * Canonisation method.
+ *
+ * @var string
+ */
+ protected $_canon = 'simple';
+
+ /**
+ * Headers not being signed.
+ *
+ * @var array
+ */
+ protected $_ignoredHeaders = array();
+
+ /**
+ * Signer identity.
+ *
+ * @var string
+ */
+ protected $_signerIdentity;
+
+ /**
+ * Must we embed signed headers?
+ *
+ * @var bool
+ */
+ protected $_debugHeaders = false;
+
+ // work variables
+ /**
+ * Headers used to generate hash.
+ *
+ * @var array
+ */
+ private $_signedHeaders = array();
+
+ /**
+ * Stores the signature header.
+ *
+ * @var Swift_Mime_Headers_ParameterizedHeader
+ */
+ protected $_domainKeyHeader;
+
+ /**
+ * Hash Handler.
+ *
+ * @var resource|null
+ */
+ private $_hashHandler;
+
+ private $_hash;
+
+ private $_canonData = '';
+
+ private $_bodyCanonEmptyCounter = 0;
+
+ private $_bodyCanonIgnoreStart = 2;
+
+ private $_bodyCanonSpace = false;
+
+ private $_bodyCanonLastChar = null;
+
+ private $_bodyCanonLine = '';
+
+ private $_bound = array();
+
+ /**
+ * Constructor.
+ *
+ * @param string $privateKey
+ * @param string $domainName
+ * @param string $selector
+ */
+ public function __construct($privateKey, $domainName, $selector)
+ {
+ $this->_privateKey = $privateKey;
+ $this->_domainName = $domainName;
+ $this->_signerIdentity = '@'.$domainName;
+ $this->_selector = $selector;
+ }
+
+ /**
+ * Instanciate DomainKeySigner.
+ *
+ * @param string $privateKey
+ * @param string $domainName
+ * @param string $selector
+ *
+ * @return self
+ */
+ public static function newInstance($privateKey, $domainName, $selector)
+ {
+ return new static($privateKey, $domainName, $selector);
+ }
+
+ /**
+ * Resets internal states.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ $this->_hash = null;
+ $this->_hashHandler = null;
+ $this->_bodyCanonIgnoreStart = 2;
+ $this->_bodyCanonEmptyCounter = 0;
+ $this->_bodyCanonLastChar = null;
+ $this->_bodyCanonSpace = false;
+
+ return $this;
+ }
+
+ /**
+ * Writes $bytes to the end of the stream.
+ *
+ * Writing may not happen immediately if the stream chooses to buffer. If
+ * you want to write these bytes with immediate effect, call {@link commit()}
+ * after calling write().
+ *
+ * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
+ * second, etc etc).
+ *
+ * @param string $bytes
+ *
+ * @throws Swift_IoException
+ *
+ * @return $this
+ */
+ public function write($bytes)
+ {
+ $this->_canonicalizeBody($bytes);
+ foreach ($this->_bound as $is) {
+ $is->write($bytes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * For any bytes that are currently buffered inside the stream, force them
+ * off the buffer.
+ *
+ * @throws Swift_IoException
+ *
+ * @return $this
+ */
+ public function commit()
+ {
+ // Nothing to do
+ return $this;
+ }
+
+ /**
+ * Attach $is to this stream.
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ *
+ * @return $this
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ // Don't have to mirror anything
+ $this->_bound[] = $is;
+
+ return $this;
+ }
+
+ /**
+ * Remove an already bound stream.
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ *
+ * @return $this
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ // Don't have to mirror anything
+ foreach ($this->_bound as $k => $stream) {
+ if ($stream === $is) {
+ unset($this->_bound[$k]);
+
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Flush the contents of the stream (empty it) and set the internal pointer
+ * to the beginning.
+ *
+ * @throws Swift_IoException
+ *
+ * @return $this
+ */
+ public function flushBuffers()
+ {
+ $this->reset();
+
+ return $this;
+ }
+
+ /**
+ * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256.
+ *
+ * @param string $hash
+ *
+ * @return $this
+ */
+ public function setHashAlgorithm($hash)
+ {
+ $this->_hashAlgorithm = 'rsa-sha1';
+
+ return $this;
+ }
+
+ /**
+ * Set the canonicalization algorithm.
+ *
+ * @param string $canon simple | nofws defaults to simple
+ *
+ * @return $this
+ */
+ public function setCanon($canon)
+ {
+ if ($canon == 'nofws') {
+ $this->_canon = 'nofws';
+ } else {
+ $this->_canon = 'simple';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the signer identity.
+ *
+ * @param string $identity
+ *
+ * @return $this
+ */
+ public function setSignerIdentity($identity)
+ {
+ $this->_signerIdentity = $identity;
+
+ return $this;
+ }
+
+ /**
+ * Enable / disable the DebugHeaders.
+ *
+ * @param bool $debug
+ *
+ * @return $this
+ */
+ public function setDebugHeaders($debug)
+ {
+ $this->_debugHeaders = (bool) $debug;
+
+ return $this;
+ }
+
+ /**
+ * Start Body.
+ */
+ public function startBody()
+ {
+ }
+
+ /**
+ * End Body.
+ */
+ public function endBody()
+ {
+ $this->_endOfBody();
+ }
+
+ /**
+ * Returns the list of Headers Tampered by this plugin.
+ *
+ * @return array
+ */
+ public function getAlteredHeaders()
+ {
+ if ($this->_debugHeaders) {
+ return array('DomainKey-Signature', 'X-DebugHash');
+ }
+
+ return array('DomainKey-Signature');
+ }
+
+ /**
+ * Adds an ignored Header.
+ *
+ * @param string $header_name
+ *
+ * @return $this
+ */
+ public function ignoreHeader($header_name)
+ {
+ $this->_ignoredHeaders[strtolower($header_name)] = true;
+
+ return $this;
+ }
+
+ /**
+ * Set the headers to sign.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ *
+ * @return $this
+ */
+ public function setHeaders(Swift_Mime_HeaderSet $headers)
+ {
+ $this->_startHash();
+ $this->_canonData = '';
+ // Loop through Headers
+ $listHeaders = $headers->listAll();
+ foreach ($listHeaders as $hName) {
+ // Check if we need to ignore Header
+ if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
+ if ($headers->has($hName)) {
+ $tmp = $headers->getAll($hName);
+ foreach ($tmp as $header) {
+ if ($header->getFieldBody() != '') {
+ $this->_addHeader($header->toString());
+ $this->_signedHeaders[] = $header->getFieldName();
+ }
+ }
+ }
+ }
+ }
+ $this->_endOfHeaders();
+
+ return $this;
+ }
+
+ /**
+ * Add the signature to the given Headers.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ *
+ * @return $this
+ */
+ public function addSignature(Swift_Mime_HeaderSet $headers)
+ {
+ // Prepare the DomainKey-Signature Header
+ $params = array('a' => $this->_hashAlgorithm, 'b' => chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' '), 'c' => $this->_canon, 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'q' => 'dns', 's' => $this->_selector);
+ $string = '';
+ foreach ($params as $k => $v) {
+ $string .= $k.'='.$v.'; ';
+ }
+ $string = trim($string);
+ $headers->addTextHeader('DomainKey-Signature', $string);
+
+ return $this;
+ }
+
+ /* Private helpers */
+
+ protected function _addHeader($header)
+ {
+ switch ($this->_canon) {
+ case 'nofws':
+ // Prepare Header and cascade
+ $exploded = explode(':', $header, 2);
+ $name = strtolower(trim($exploded[0]));
+ $value = str_replace("\r\n", '', $exploded[1]);
+ $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
+ $header = $name.':'.trim($value)."\r\n";
+ case 'simple':
+ // Nothing to do
+ }
+ $this->_addToHash($header);
+ }
+
+ protected function _endOfHeaders()
+ {
+ $this->_bodyCanonEmptyCounter = 1;
+ }
+
+ protected function _canonicalizeBody($string)
+ {
+ $len = strlen($string);
+ $canon = '';
+ $nofws = ($this->_canon == 'nofws');
+ for ($i = 0; $i < $len; ++$i) {
+ if ($this->_bodyCanonIgnoreStart > 0) {
+ --$this->_bodyCanonIgnoreStart;
+ continue;
+ }
+ switch ($string[$i]) {
+ case "\r":
+ $this->_bodyCanonLastChar = "\r";
+ break;
+ case "\n":
+ if ($this->_bodyCanonLastChar == "\r") {
+ if ($nofws) {
+ $this->_bodyCanonSpace = false;
+ }
+ if ($this->_bodyCanonLine == '') {
+ ++$this->_bodyCanonEmptyCounter;
+ } else {
+ $this->_bodyCanonLine = '';
+ $canon .= "\r\n";
+ }
+ } else {
+ // Wooops Error
+ throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r');
+ }
+ break;
+ case ' ':
+ case "\t":
+ case "\x09": //HTAB
+ if ($nofws) {
+ $this->_bodyCanonSpace = true;
+ break;
+ }
+ default:
+ if ($this->_bodyCanonEmptyCounter > 0) {
+ $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
+ $this->_bodyCanonEmptyCounter = 0;
+ }
+ $this->_bodyCanonLine .= $string[$i];
+ $canon .= $string[$i];
+ }
+ }
+ $this->_addToHash($canon);
+ }
+
+ protected function _endOfBody()
+ {
+ if (strlen($this->_bodyCanonLine) > 0) {
+ $this->_addToHash("\r\n");
+ }
+ $this->_hash = hash_final($this->_hashHandler, true);
+ }
+
+ private function _addToHash($string)
+ {
+ $this->_canonData .= $string;
+ hash_update($this->_hashHandler, $string);
+ }
+
+ private function _startHash()
+ {
+ // Init
+ switch ($this->_hashAlgorithm) {
+ case 'rsa-sha1':
+ $this->_hashHandler = hash_init('sha1');
+ break;
+ }
+ $this->_bodyCanonLine = '';
+ }
+
+ /**
+ * @throws Swift_SwiftException
+ *
+ * @return string
+ */
+ private function _getEncryptedHash()
+ {
+ $signature = '';
+ $pkeyId = openssl_get_privatekey($this->_privateKey);
+ if (!$pkeyId) {
+ throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']');
+ }
+ if (openssl_sign($this->_canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) {
+ return $signature;
+ }
+ throw new Swift_SwiftException('Unable to sign DomainKey Hash ['.openssl_error_string().']');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php
new file mode 100644
index 0000000..ef8832f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Header Signer Interface used to apply Header-Based Signature to a message.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+interface Swift_Signers_HeaderSigner extends Swift_Signer, Swift_InputByteStream
+{
+ /**
+ * Exclude an header from the signed headers.
+ *
+ * @param string $header_name
+ *
+ * @return self
+ */
+ public function ignoreHeader($header_name);
+
+ /**
+ * Prepare the Signer to get a new Body.
+ *
+ * @return self
+ */
+ public function startBody();
+
+ /**
+ * Give the signal that the body has finished streaming.
+ *
+ * @return self
+ */
+ public function endBody();
+
+ /**
+ * Give the headers already given.
+ *
+ * @param Swift_Mime_SimpleHeaderSet $headers
+ *
+ * @return self
+ */
+ public function setHeaders(Swift_Mime_HeaderSet $headers);
+
+ /**
+ * Add the header(s) to the headerSet.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ *
+ * @return self
+ */
+ public function addSignature(Swift_Mime_HeaderSet $headers);
+
+ /**
+ * Return the list of header a signer might tamper.
+ *
+ * @return array
+ */
+ public function getAlteredHeaders();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php
new file mode 100644
index 0000000..8fdbaa4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php
@@ -0,0 +1,190 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * DKIM Signer used to apply DKIM Signature to a message
+ * Takes advantage of pecl extension.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_Signers_OpenDKIMSigner extends Swift_Signers_DKIMSigner
+{
+ private $_peclLoaded = false;
+
+ private $_dkimHandler = null;
+
+ private $dropFirstLF = true;
+
+ const CANON_RELAXED = 1;
+ const CANON_SIMPLE = 2;
+ const SIG_RSA_SHA1 = 3;
+ const SIG_RSA_SHA256 = 4;
+
+ public function __construct($privateKey, $domainName, $selector)
+ {
+ if (!extension_loaded('opendkim')) {
+ throw new Swift_SwiftException('php-opendkim extension not found');
+ }
+
+ $this->_peclLoaded = true;
+
+ parent::__construct($privateKey, $domainName, $selector);
+ }
+
+ public static function newInstance($privateKey, $domainName, $selector)
+ {
+ return new static($privateKey, $domainName, $selector);
+ }
+
+ public function addSignature(Swift_Mime_HeaderSet $headers)
+ {
+ $header = new Swift_Mime_Headers_OpenDKIMHeader('DKIM-Signature');
+ $headerVal = $this->_dkimHandler->getSignatureHeader();
+ if (!$headerVal) {
+ throw new Swift_SwiftException('OpenDKIM Error: '.$this->_dkimHandler->getError());
+ }
+ $header->setValue($headerVal);
+ $headers->set($header);
+
+ return $this;
+ }
+
+ public function setHeaders(Swift_Mime_HeaderSet $headers)
+ {
+ $bodyLen = $this->_bodyLen;
+ if (is_bool($bodyLen)) {
+ $bodyLen = -1;
+ }
+ $hash = $this->_hashAlgorithm == 'rsa-sha1' ? OpenDKIMSign::ALG_RSASHA1 : OpenDKIMSign::ALG_RSASHA256;
+ $bodyCanon = $this->_bodyCanon == 'simple' ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED;
+ $headerCanon = $this->_headerCanon == 'simple' ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED;
+ $this->_dkimHandler = new OpenDKIMSign($this->_privateKey, $this->_selector, $this->_domainName, $headerCanon, $bodyCanon, $hash, $bodyLen);
+ // Hardcode signature Margin for now
+ $this->_dkimHandler->setMargin(78);
+
+ if (!is_numeric($this->_signatureTimestamp)) {
+ OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, time());
+ } else {
+ if (!OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, $this->_signatureTimestamp)) {
+ throw new Swift_SwiftException('Unable to force signature timestamp ['.openssl_error_string().']');
+ }
+ }
+ if (isset($this->_signerIdentity)) {
+ $this->_dkimHandler->setSigner($this->_signerIdentity);
+ }
+ $listHeaders = $headers->listAll();
+ foreach ($listHeaders as $hName) {
+ // Check if we need to ignore Header
+ if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
+ $tmp = $headers->getAll($hName);
+ if ($headers->has($hName)) {
+ foreach ($tmp as $header) {
+ if ($header->getFieldBody() != '') {
+ $htosign = $header->toString();
+ $this->_dkimHandler->header($htosign);
+ $this->_signedHeaders[] = $header->getFieldName();
+ }
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ public function startBody()
+ {
+ if (!$this->_peclLoaded) {
+ return parent::startBody();
+ }
+ $this->dropFirstLF = true;
+ $this->_dkimHandler->eoh();
+
+ return $this;
+ }
+
+ public function endBody()
+ {
+ if (!$this->_peclLoaded) {
+ return parent::endBody();
+ }
+ $this->_dkimHandler->eom();
+
+ return $this;
+ }
+
+ public function reset()
+ {
+ $this->_dkimHandler = null;
+ parent::reset();
+
+ return $this;
+ }
+
+ /**
+ * Set the signature timestamp.
+ *
+ * @param int $time
+ *
+ * @return $this
+ */
+ public function setSignatureTimestamp($time)
+ {
+ $this->_signatureTimestamp = $time;
+
+ return $this;
+ }
+
+ /**
+ * Set the signature expiration timestamp.
+ *
+ * @param int $time
+ *
+ * @return $this
+ */
+ public function setSignatureExpiration($time)
+ {
+ $this->_signatureExpiration = $time;
+
+ return $this;
+ }
+
+ /**
+ * Enable / disable the DebugHeaders.
+ *
+ * @param bool $debug
+ *
+ * @return $this
+ */
+ public function setDebugHeaders($debug)
+ {
+ $this->_debugHeaders = (bool) $debug;
+
+ return $this;
+ }
+
+ // Protected
+
+ protected function _canonicalizeBody($string)
+ {
+ if (!$this->_peclLoaded) {
+ return parent::_canonicalizeBody($string);
+ }
+ if (false && $this->dropFirstLF === true) {
+ if ($string[0] == "\r" && $string[1] == "\n") {
+ $string = substr($string, 2);
+ }
+ }
+ $this->dropFirstLF = false;
+ if (strlen($string)) {
+ $this->_dkimHandler->body($string);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php
new file mode 100644
index 0000000..d13c02e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php
@@ -0,0 +1,436 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * MIME Message Signer used to apply S/MIME Signature/Encryption to a message.
+ *
+ *
+ * @author Romain-Geissler
+ * @author Sebastiaan Stok <s.stok@rollerscapes.net>
+ */
+class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner
+{
+ protected $signCertificate;
+ protected $signPrivateKey;
+ protected $encryptCert;
+ protected $signThenEncrypt = true;
+ protected $signLevel;
+ protected $encryptLevel;
+ protected $signOptions;
+ protected $encryptOptions;
+ protected $encryptCipher;
+ protected $extraCerts = null;
+
+ /**
+ * @var Swift_StreamFilters_StringReplacementFilterFactory
+ */
+ protected $replacementFactory;
+
+ /**
+ * @var Swift_Mime_HeaderFactory
+ */
+ protected $headerFactory;
+
+ /**
+ * Constructor.
+ *
+ * @param string|null $signCertificate
+ * @param string|null $signPrivateKey
+ * @param string|null $encryptCertificate
+ */
+ public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null)
+ {
+ if (null !== $signPrivateKey) {
+ $this->setSignCertificate($signCertificate, $signPrivateKey);
+ }
+
+ if (null !== $encryptCertificate) {
+ $this->setEncryptCertificate($encryptCertificate);
+ }
+
+ $this->replacementFactory = Swift_DependencyContainer::getInstance()
+ ->lookup('transport.replacementfactory');
+
+ $this->signOptions = PKCS7_DETACHED;
+
+ // Supported since php5.4
+ if (defined('OPENSSL_CIPHER_AES_128_CBC')) {
+ $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC;
+ } else {
+ $this->encryptCipher = OPENSSL_CIPHER_RC2_128;
+ }
+ }
+
+ /**
+ * Returns an new Swift_Signers_SMimeSigner instance.
+ *
+ * @param string $certificate
+ * @param string $privateKey
+ *
+ * @return self
+ */
+ public static function newInstance($certificate = null, $privateKey = null)
+ {
+ return new self($certificate, $privateKey);
+ }
+
+ /**
+ * Set the certificate location to use for signing.
+ *
+ * @see http://www.php.net/manual/en/openssl.pkcs7.flags.php
+ *
+ * @param string $certificate
+ * @param string|array $privateKey If the key needs an passphrase use array('file-location', 'passphrase') instead
+ * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign()
+ * @param string $extraCerts A file containing intermediate certificates needed by the signing certificate
+ *
+ * @return $this
+ */
+ public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null)
+ {
+ $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate));
+
+ if (null !== $privateKey) {
+ if (is_array($privateKey)) {
+ $this->signPrivateKey = $privateKey;
+ $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0]));
+ } else {
+ $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey));
+ }
+ }
+
+ $this->signOptions = $signOptions;
+ if (null !== $extraCerts) {
+ $this->extraCerts = str_replace('\\', '/', realpath($extraCerts));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the certificate location to use for encryption.
+ *
+ * @see http://www.php.net/manual/en/openssl.pkcs7.flags.php
+ * @see http://nl3.php.net/manual/en/openssl.ciphers.php
+ *
+ * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates.
+ * @param int $cipher
+ *
+ * @return $this
+ */
+ public function setEncryptCertificate($recipientCerts, $cipher = null)
+ {
+ if (is_array($recipientCerts)) {
+ $this->encryptCert = array();
+
+ foreach ($recipientCerts as $cert) {
+ $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert));
+ }
+ } else {
+ $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts));
+ }
+
+ if (null !== $cipher) {
+ $this->encryptCipher = $cipher;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSignCertificate()
+ {
+ return $this->signCertificate;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSignPrivateKey()
+ {
+ return $this->signPrivateKey;
+ }
+
+ /**
+ * Set perform signing before encryption.
+ *
+ * The default is to first sign the message and then encrypt.
+ * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted.
+ * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients.
+ *
+ * @param bool $signThenEncrypt
+ *
+ * @return $this
+ */
+ public function setSignThenEncrypt($signThenEncrypt = true)
+ {
+ $this->signThenEncrypt = $signThenEncrypt;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSignThenEncrypt()
+ {
+ return $this->signThenEncrypt;
+ }
+
+ /**
+ * Resets internal states.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ return $this;
+ }
+
+ /**
+ * Change the Swift_Message to apply the signing.
+ *
+ * @param Swift_Message $message
+ *
+ * @return $this
+ */
+ public function signMessage(Swift_Message $message)
+ {
+ if (null === $this->signCertificate && null === $this->encryptCert) {
+ return $this;
+ }
+
+ // Store the message using ByteStream to a file{1}
+ // Remove all Children
+ // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity
+ // Set the singed-body as the new body (without boundary)
+
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $this->toSMimeByteStream($messageStream, $message);
+ $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));
+
+ $message->setChildren(array());
+ $this->streamToMime($messageStream, $message);
+ }
+
+ /**
+ * Return the list of header a signer might tamper.
+ *
+ * @return array
+ */
+ public function getAlteredHeaders()
+ {
+ return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition');
+ }
+
+ /**
+ * @param Swift_InputByteStream $inputStream
+ * @param Swift_Message $mimeEntity
+ */
+ protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_Message $message)
+ {
+ $mimeEntity = $this->createMessage($message);
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ $mimeEntity->toByteStream($messageStream);
+ $messageStream->commit();
+
+ if (null !== $this->signCertificate && null !== $this->encryptCert) {
+ $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if ($this->signThenEncrypt) {
+ $this->messageStreamToSignedByteStream($messageStream, $temporaryStream);
+ $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream);
+ } else {
+ $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream);
+ $this->messageStreamToSignedByteStream($temporaryStream, $inputStream);
+ }
+ } elseif ($this->signCertificate !== null) {
+ $this->messageStreamToSignedByteStream($messageStream, $inputStream);
+ } else {
+ $this->messageStreamToEncryptedByteStream($messageStream, $inputStream);
+ }
+ }
+
+ /**
+ * @param Swift_Message $message
+ *
+ * @return Swift_Message
+ */
+ protected function createMessage(Swift_Message $message)
+ {
+ $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset());
+ $mimeEntity->setChildren($message->getChildren());
+
+ $messageHeaders = $mimeEntity->getHeaders();
+ $messageHeaders->remove('Message-ID');
+ $messageHeaders->remove('Date');
+ $messageHeaders->remove('Subject');
+ $messageHeaders->remove('MIME-Version');
+ $messageHeaders->remove('To');
+ $messageHeaders->remove('From');
+
+ return $mimeEntity;
+ }
+
+ /**
+ * @param Swift_FileStream $outputStream
+ * @param Swift_InputByteStream $inputStream
+ *
+ * @throws Swift_IoException
+ */
+ protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream)
+ {
+ $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ $args = array($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions);
+ if (null !== $this->extraCerts) {
+ $args[] = $this->extraCerts;
+ }
+
+ if (!call_user_func_array('openssl_pkcs7_sign', $args)) {
+ throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
+ }
+
+ $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream);
+ }
+
+ /**
+ * @param Swift_FileStream $outputStream
+ * @param Swift_InputByteStream $is
+ *
+ * @throws Swift_IoException
+ */
+ protected function messageStreamToEncryptedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $is)
+ {
+ $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) {
+ throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
+ }
+
+ $this->copyFromOpenSSLOutput($encryptedMessageStream, $is);
+ }
+
+ /**
+ * @param Swift_OutputByteStream $fromStream
+ * @param Swift_InputByteStream $toStream
+ */
+ protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream)
+ {
+ $bufferLength = 4096;
+ $filteredStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
+ $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
+
+ while (false !== ($buffer = $fromStream->read($bufferLength))) {
+ $filteredStream->write($buffer);
+ }
+
+ $filteredStream->flushBuffers();
+
+ while (false !== ($buffer = $filteredStream->read($bufferLength))) {
+ $toStream->write($buffer);
+ }
+
+ $toStream->commit();
+ }
+
+ /**
+ * Merges an OutputByteStream to Swift_Message.
+ *
+ * @param Swift_OutputByteStream $fromStream
+ * @param Swift_Message $message
+ */
+ protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message)
+ {
+ $bufferLength = 78;
+ $headerData = '';
+
+ $fromStream->setReadPointer(0);
+
+ while (($buffer = $fromStream->read($bufferLength)) !== false) {
+ $headerData .= $buffer;
+
+ if (false !== strpos($buffer, "\r\n\r\n")) {
+ break;
+ }
+ }
+
+ $headersPosEnd = strpos($headerData, "\r\n\r\n");
+ $headerData = trim($headerData);
+ $headerData = substr($headerData, 0, $headersPosEnd);
+ $headerLines = explode("\r\n", $headerData);
+ unset($headerData);
+
+ $headers = array();
+ $currentHeaderName = '';
+
+ foreach ($headerLines as $headerLine) {
+ // Line separated
+ if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
+ $headers[$currentHeaderName] .= ' '.trim($headerLine);
+ continue;
+ }
+
+ $header = explode(':', $headerLine, 2);
+ $currentHeaderName = strtolower($header[0]);
+ $headers[$currentHeaderName] = trim($header[1]);
+ }
+
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
+ $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
+
+ $messageHeaders = $message->getHeaders();
+
+ // No need to check for 'application/pkcs7-mime', as this is always base64
+ if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) {
+ if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) {
+ throw new Swift_SwiftException('Failed to find Boundary parameter');
+ }
+
+ $boundary = trim($contentTypeData['1'], '"');
+
+ // Skip the header and CRLF CRLF
+ $fromStream->setReadPointer($headersPosEnd + 4);
+
+ while (false !== ($buffer = $fromStream->read($bufferLength))) {
+ $messageStream->write($buffer);
+ }
+
+ $messageStream->commit();
+
+ $messageHeaders->remove('Content-Transfer-Encoding');
+ $message->setContentType($headers['content-type']);
+ $message->setBoundary($boundary);
+ $message->setBody($messageStream);
+ } else {
+ $fromStream->setReadPointer($headersPosEnd + 4);
+
+ if (null === $this->headerFactory) {
+ $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory');
+ }
+
+ $message->setContentType($headers['content-type']);
+ $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding']));
+ $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition']));
+
+ while (false !== ($buffer = $fromStream->read($bufferLength))) {
+ $messageStream->write($buffer);
+ }
+
+ $messageStream->commit();
+ $message->setBody($messageStream);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php
new file mode 100644
index 0000000..b97f01e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages over SMTP with ESMTP support.
+ *
+ * @author Chris Corbyn
+ *
+ * @method Swift_SmtpTransport setUsername(string $username) Set the username to authenticate with.
+ * @method string getUsername() Get the username to authenticate with.
+ * @method Swift_SmtpTransport setPassword(string $password) Set the password to authenticate with.
+ * @method string getPassword() Get the password to authenticate with.
+ * @method Swift_SmtpTransport setAuthMode(string $mode) Set the auth mode to use to authenticate.
+ * @method string getAuthMode() Get the auth mode to use to authenticate.
+ */
+class Swift_SmtpTransport extends Swift_Transport_EsmtpTransport
+{
+ /**
+ * Create a new SmtpTransport, optionally with $host, $port and $security.
+ *
+ * @param string $host
+ * @param int $port
+ * @param string $security
+ */
+ public function __construct($host = 'localhost', $port = 25, $security = null)
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_EsmtpTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.smtp')
+ );
+
+ $this->setHost($host);
+ $this->setPort($port);
+ $this->setEncryption($security);
+ }
+
+ /**
+ * Create a new SmtpTransport instance.
+ *
+ * @param string $host
+ * @param int $port
+ * @param string $security
+ *
+ * @return self
+ */
+ public static function newInstance($host = 'localhost', $port = 25, $security = null)
+ {
+ return new self($host, $port, $security);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php
new file mode 100644
index 0000000..c16ab4b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for spools.
+ *
+ * @author Fabien Potencier
+ */
+interface Swift_Spool
+{
+ /**
+ * Starts this Spool mechanism.
+ */
+ public function start();
+
+ /**
+ * Stops this Spool mechanism.
+ */
+ public function stop();
+
+ /**
+ * Tests if this Spool mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted();
+
+ /**
+ * Queues a message.
+ *
+ * @param Swift_Mime_Message $message The message to store
+ *
+ * @return bool Whether the operation has succeeded
+ */
+ public function queueMessage(Swift_Mime_Message $message);
+
+ /**
+ * Sends messages using the given transport instance.
+ *
+ * @param Swift_Transport $transport A transport instance
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of sent emails
+ */
+ public function flushQueue(Swift_Transport $transport, &$failedRecipients = null);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php
new file mode 100644
index 0000000..79c9b1f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Stores Messages in a queue.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_SpoolTransport extends Swift_Transport_SpoolTransport
+{
+ /**
+ * Create a new SpoolTransport.
+ *
+ * @param Swift_Spool $spool
+ */
+ public function __construct(Swift_Spool $spool)
+ {
+ $arguments = Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.spool');
+
+ $arguments[] = $spool;
+
+ call_user_func_array(
+ array($this, 'Swift_Transport_SpoolTransport::__construct'),
+ $arguments
+ );
+ }
+
+ /**
+ * Create a new SpoolTransport instance.
+ *
+ * @param Swift_Spool $spool
+ *
+ * @return self
+ */
+ public static function newInstance(Swift_Spool $spool)
+ {
+ return new self($spool);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php
new file mode 100644
index 0000000..362be2e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Processes bytes as they pass through a stream and performs filtering.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_StreamFilter
+{
+ /**
+ * Based on the buffer given, this returns true if more buffering is needed.
+ *
+ * @param mixed $buffer
+ *
+ * @return bool
+ */
+ public function shouldBuffer($buffer);
+
+ /**
+ * Filters $buffer and returns the changes.
+ *
+ * @param mixed $buffer
+ *
+ * @return mixed
+ */
+ public function filter($buffer);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php
new file mode 100644
index 0000000..9412b1d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php
@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Processes bytes as they pass through a buffer and replaces sequences in it.
+ *
+ * This stream filter deals with Byte arrays rather than simple strings.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_StreamFilters_ByteArrayReplacementFilter implements Swift_StreamFilter
+{
+ /** The needle(s) to search for */
+ private $_search;
+
+ /** The replacement(s) to make */
+ private $_replace;
+
+ /** The Index for searching */
+ private $_index;
+
+ /** The Search Tree */
+ private $_tree = array();
+
+ /** Gives the size of the largest search */
+ private $_treeMaxLen = 0;
+
+ private $_repSize;
+
+ /**
+ * Create a new ByteArrayReplacementFilter with $search and $replace.
+ *
+ * @param array $search
+ * @param array $replace
+ */
+ public function __construct($search, $replace)
+ {
+ $this->_search = $search;
+ $this->_index = array();
+ $this->_tree = array();
+ $this->_replace = array();
+ $this->_repSize = array();
+
+ $tree = null;
+ $i = null;
+ $last_size = $size = 0;
+ foreach ($search as $i => $search_element) {
+ if ($tree !== null) {
+ $tree[-1] = min(count($replace) - 1, $i - 1);
+ $tree[-2] = $last_size;
+ }
+ $tree = &$this->_tree;
+ if (is_array($search_element)) {
+ foreach ($search_element as $k => $char) {
+ $this->_index[$char] = true;
+ if (!isset($tree[$char])) {
+ $tree[$char] = array();
+ }
+ $tree = &$tree[$char];
+ }
+ $last_size = $k + 1;
+ $size = max($size, $last_size);
+ } else {
+ $last_size = 1;
+ if (!isset($tree[$search_element])) {
+ $tree[$search_element] = array();
+ }
+ $tree = &$tree[$search_element];
+ $size = max($last_size, $size);
+ $this->_index[$search_element] = true;
+ }
+ }
+ if ($i !== null) {
+ $tree[-1] = min(count($replace) - 1, $i);
+ $tree[-2] = $last_size;
+ $this->_treeMaxLen = $size;
+ }
+ foreach ($replace as $rep) {
+ if (!is_array($rep)) {
+ $rep = array($rep);
+ }
+ $this->_replace[] = $rep;
+ }
+ for ($i = count($this->_replace) - 1; $i >= 0; --$i) {
+ $this->_replace[$i] = $rep = $this->filter($this->_replace[$i], $i);
+ $this->_repSize[$i] = count($rep);
+ }
+ }
+
+ /**
+ * Returns true if based on the buffer passed more bytes should be buffered.
+ *
+ * @param array $buffer
+ *
+ * @return bool
+ */
+ public function shouldBuffer($buffer)
+ {
+ $endOfBuffer = end($buffer);
+
+ return isset($this->_index[$endOfBuffer]);
+ }
+
+ /**
+ * Perform the actual replacements on $buffer and return the result.
+ *
+ * @param array $buffer
+ * @param int $_minReplaces
+ *
+ * @return array
+ */
+ public function filter($buffer, $_minReplaces = -1)
+ {
+ if ($this->_treeMaxLen == 0) {
+ return $buffer;
+ }
+
+ $newBuffer = array();
+ $buf_size = count($buffer);
+ $last_size = 0;
+ for ($i = 0; $i < $buf_size; ++$i) {
+ $search_pos = $this->_tree;
+ $last_found = PHP_INT_MAX;
+ // We try to find if the next byte is part of a search pattern
+ for ($j = 0; $j <= $this->_treeMaxLen; ++$j) {
+ // We have a new byte for a search pattern
+ if (isset($buffer[$p = $i + $j]) && isset($search_pos[$buffer[$p]])) {
+ $search_pos = $search_pos[$buffer[$p]];
+ // We have a complete pattern, save, in case we don't find a better match later
+ if (isset($search_pos[-1]) && $search_pos[-1] < $last_found
+ && $search_pos[-1] > $_minReplaces) {
+ $last_found = $search_pos[-1];
+ $last_size = $search_pos[-2];
+ }
+ }
+ // We got a complete pattern
+ elseif ($last_found !== PHP_INT_MAX) {
+ // Adding replacement datas to output buffer
+ $rep_size = $this->_repSize[$last_found];
+ for ($j = 0; $j < $rep_size; ++$j) {
+ $newBuffer[] = $this->_replace[$last_found][$j];
+ }
+ // We Move cursor forward
+ $i += $last_size - 1;
+ // Edge Case, last position in buffer
+ if ($i >= $buf_size) {
+ $newBuffer[] = $buffer[$i];
+ }
+
+ // We start the next loop
+ continue 2;
+ } else {
+ // this byte is not in a pattern and we haven't found another pattern
+ break;
+ }
+ }
+ // Normal byte, move it to output buffer
+ $newBuffer[] = $buffer[$i];
+ }
+
+ return $newBuffer;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php
new file mode 100644
index 0000000..f64144a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Processes bytes as they pass through a buffer and replaces sequences in it.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_StreamFilters_StringReplacementFilter implements Swift_StreamFilter
+{
+ /** The needle(s) to search for */
+ private $_search;
+
+ /** The replacement(s) to make */
+ private $_replace;
+
+ /**
+ * Create a new StringReplacementFilter with $search and $replace.
+ *
+ * @param string|array $search
+ * @param string|array $replace
+ */
+ public function __construct($search, $replace)
+ {
+ $this->_search = $search;
+ $this->_replace = $replace;
+ }
+
+ /**
+ * Returns true if based on the buffer passed more bytes should be buffered.
+ *
+ * @param string $buffer
+ *
+ * @return bool
+ */
+ public function shouldBuffer($buffer)
+ {
+ if ('' === $buffer) {
+ return false;
+ }
+
+ $endOfBuffer = substr($buffer, -1);
+ foreach ((array) $this->_search as $needle) {
+ if (false !== strpos($needle, $endOfBuffer)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Perform the actual replacements on $buffer and return the result.
+ *
+ * @param string $buffer
+ *
+ * @return string
+ */
+ public function filter($buffer)
+ {
+ return str_replace($this->_search, $this->_replace, $buffer);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php
new file mode 100644
index 0000000..e98240b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Creates filters for replacing needles in a string buffer.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_StreamFilters_StringReplacementFilterFactory implements Swift_ReplacementFilterFactory
+{
+ /** Lazy-loaded filters */
+ private $_filters = array();
+
+ /**
+ * Create a new StreamFilter to replace $search with $replace in a string.
+ *
+ * @param string $search
+ * @param string $replace
+ *
+ * @return Swift_StreamFilter
+ */
+ public function createFilter($search, $replace)
+ {
+ if (!isset($this->_filters[$search][$replace])) {
+ if (!isset($this->_filters[$search])) {
+ $this->_filters[$search] = array();
+ }
+
+ if (!isset($this->_filters[$search][$replace])) {
+ $this->_filters[$search][$replace] = array();
+ }
+
+ $this->_filters[$search][$replace] = new Swift_StreamFilters_StringReplacementFilter($search, $replace);
+ }
+
+ return $this->_filters[$search][$replace];
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php
new file mode 100644
index 0000000..db3d310
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Base Exception class.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_SwiftException extends Exception
+{
+ /**
+ * Create a new SwiftException with $message.
+ *
+ * @param string $message
+ * @param int $code
+ * @param Exception $previous
+ */
+ public function __construct($message, $code = 0, Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php
new file mode 100644
index 0000000..6535ead
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages via an abstract Transport subsystem.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport
+{
+ /**
+ * Test if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted();
+
+ /**
+ * Start this Transport mechanism.
+ */
+ public function start();
+
+ /**
+ * Stop this Transport mechanism.
+ */
+ public function stop();
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients who were accepted for delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null);
+
+ /**
+ * Register a plugin in the Transport.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php
new file mode 100644
index 0000000..60233f9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php
@@ -0,0 +1,499 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages over SMTP.
+ *
+ * @author Chris Corbyn
+ */
+abstract class Swift_Transport_AbstractSmtpTransport implements Swift_Transport
+{
+ /** Input-Output buffer for sending/receiving SMTP commands and responses */
+ protected $_buffer;
+
+ /** Connection status */
+ protected $_started = false;
+
+ /** The domain name to use in HELO command */
+ protected $_domain = '[127.0.0.1]';
+
+ /** The event dispatching layer */
+ protected $_eventDispatcher;
+
+ /** Source Ip */
+ protected $_sourceIp;
+
+ /** Return an array of params for the Buffer */
+ abstract protected function _getBufferParams();
+
+ /**
+ * Creates a new EsmtpTransport using the given I/O buffer.
+ *
+ * @param Swift_Transport_IoBuffer $buf
+ * @param Swift_Events_EventDispatcher $dispatcher
+ */
+ public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher)
+ {
+ $this->_eventDispatcher = $dispatcher;
+ $this->_buffer = $buf;
+ $this->_lookupHostname();
+ }
+
+ /**
+ * Set the name of the local domain which Swift will identify itself as.
+ *
+ * This should be a fully-qualified domain name and should be truly the domain
+ * you're using.
+ *
+ * If your server doesn't have a domain name, use the IP in square
+ * brackets (i.e. [127.0.0.1]).
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setLocalDomain($domain)
+ {
+ $this->_domain = $domain;
+
+ return $this;
+ }
+
+ /**
+ * Get the name of the domain Swift will identify as.
+ *
+ * @return string
+ */
+ public function getLocalDomain()
+ {
+ return $this->_domain;
+ }
+
+ /**
+ * Sets the source IP.
+ *
+ * @param string $source
+ */
+ public function setSourceIp($source)
+ {
+ $this->_sourceIp = $source;
+ }
+
+ /**
+ * Returns the IP used to connect to the destination.
+ *
+ * @return string
+ */
+ public function getSourceIp()
+ {
+ return $this->_sourceIp;
+ }
+
+ /**
+ * Start the SMTP connection.
+ */
+ public function start()
+ {
+ if (!$this->_started) {
+ if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
+ if ($evt->bubbleCancelled()) {
+ return;
+ }
+ }
+
+ try {
+ $this->_buffer->initialize($this->_getBufferParams());
+ } catch (Swift_TransportException $e) {
+ $this->_throwException($e);
+ }
+ $this->_readGreeting();
+ $this->_doHeloCommand();
+
+ if ($evt) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
+ }
+
+ $this->_started = true;
+ }
+ }
+
+ /**
+ * Test if an SMTP connection has been established.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return $this->_started;
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients who were accepted for delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $sent = 0;
+ $failedRecipients = (array) $failedRecipients;
+
+ if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+ if ($evt->bubbleCancelled()) {
+ return 0;
+ }
+ }
+
+ if (!$reversePath = $this->_getReversePath($message)) {
+ $this->_throwException(new Swift_TransportException(
+ 'Cannot send message without a sender address'
+ )
+ );
+ }
+
+ $to = (array) $message->getTo();
+ $cc = (array) $message->getCc();
+ $tos = array_merge($to, $cc);
+ $bcc = (array) $message->getBcc();
+
+ $message->setBcc(array());
+
+ try {
+ $sent += $this->_sendTo($message, $reversePath, $tos, $failedRecipients);
+ $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
+ } catch (Exception $e) {
+ $message->setBcc($bcc);
+ throw $e;
+ }
+
+ $message->setBcc($bcc);
+
+ if ($evt) {
+ if ($sent == count($to) + count($cc) + count($bcc)) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
+ } elseif ($sent > 0) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
+ } else {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
+ }
+ $evt->setFailedRecipients($failedRecipients);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ $message->generateId(); //Make sure a new Message ID is used
+
+ return $sent;
+ }
+
+ /**
+ * Stop the SMTP connection.
+ */
+ public function stop()
+ {
+ if ($this->_started) {
+ if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
+ if ($evt->bubbleCancelled()) {
+ return;
+ }
+ }
+
+ try {
+ $this->executeCommand("QUIT\r\n", array(221));
+ } catch (Swift_TransportException $e) {
+ }
+
+ try {
+ $this->_buffer->terminate();
+
+ if ($evt) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
+ }
+ } catch (Swift_TransportException $e) {
+ $this->_throwException($e);
+ }
+ }
+ $this->_started = false;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ $this->_eventDispatcher->bindEventListener($plugin);
+ }
+
+ /**
+ * Reset the current mail transaction.
+ */
+ public function reset()
+ {
+ $this->executeCommand("RSET\r\n", array(250));
+ }
+
+ /**
+ * Get the IoBuffer where read/writes are occurring.
+ *
+ * @return Swift_Transport_IoBuffer
+ */
+ public function getBuffer()
+ {
+ return $this->_buffer;
+ }
+
+ /**
+ * Run a command against the buffer, expecting the given response codes.
+ *
+ * If no response codes are given, the response will not be validated.
+ * If codes are given, an exception will be thrown on an invalid response.
+ *
+ * @param string $command
+ * @param int[] $codes
+ * @param string[] $failures An array of failures by-reference
+ *
+ * @return string
+ */
+ public function executeCommand($command, $codes = array(), &$failures = null)
+ {
+ $failures = (array) $failures;
+ $seq = $this->_buffer->write($command);
+ $response = $this->_getFullResponse($seq);
+ if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
+ }
+ $this->_assertResponseCode($response, $codes);
+
+ return $response;
+ }
+
+ /** Read the opening SMTP greeting */
+ protected function _readGreeting()
+ {
+ $this->_assertResponseCode($this->_getFullResponse(0), array(220));
+ }
+
+ /** Send the HELO welcome */
+ protected function _doHeloCommand()
+ {
+ $this->executeCommand(
+ sprintf("HELO %s\r\n", $this->_domain), array(250)
+ );
+ }
+
+ /** Send the MAIL FROM command */
+ protected function _doMailFromCommand($address)
+ {
+ $this->executeCommand(
+ sprintf("MAIL FROM:<%s>\r\n", $address), array(250)
+ );
+ }
+
+ /** Send the RCPT TO command */
+ protected function _doRcptToCommand($address)
+ {
+ $this->executeCommand(
+ sprintf("RCPT TO:<%s>\r\n", $address), array(250, 251, 252)
+ );
+ }
+
+ /** Send the DATA command */
+ protected function _doDataCommand()
+ {
+ $this->executeCommand("DATA\r\n", array(354));
+ }
+
+ /** Stream the contents of the message over the buffer */
+ protected function _streamMessage(Swift_Mime_Message $message)
+ {
+ $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
+ try {
+ $message->toByteStream($this->_buffer);
+ $this->_buffer->flushBuffers();
+ } catch (Swift_TransportException $e) {
+ $this->_throwException($e);
+ }
+ $this->_buffer->setWriteTranslations(array());
+ $this->executeCommand("\r\n.\r\n", array(250));
+ }
+
+ /** Determine the best-use reverse path for this message */
+ protected function _getReversePath(Swift_Mime_Message $message)
+ {
+ $return = $message->getReturnPath();
+ $sender = $message->getSender();
+ $from = $message->getFrom();
+ $path = null;
+ if (!empty($return)) {
+ $path = $return;
+ } elseif (!empty($sender)) {
+ // Don't use array_keys
+ reset($sender); // Reset Pointer to first pos
+ $path = key($sender); // Get key
+ } elseif (!empty($from)) {
+ reset($from); // Reset Pointer to first pos
+ $path = key($from); // Get key
+ }
+
+ return $path;
+ }
+
+ /** Throw a TransportException, first sending it to any listeners */
+ protected function _throwException(Swift_TransportException $e)
+ {
+ if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
+ if (!$evt->bubbleCancelled()) {
+ throw $e;
+ }
+ } else {
+ throw $e;
+ }
+ }
+
+ /** Throws an Exception if a response code is incorrect */
+ protected function _assertResponseCode($response, $wanted)
+ {
+ list($code) = sscanf($response, '%3d');
+ $valid = (empty($wanted) || in_array($code, $wanted));
+
+ if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
+ $valid)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
+ }
+
+ if (!$valid) {
+ $this->_throwException(
+ new Swift_TransportException(
+ 'Expected response code '.implode('/', $wanted).' but got code '.
+ '"'.$code.'", with message "'.$response.'"',
+ $code)
+ );
+ }
+ }
+
+ /** Get an entire multi-line response using its sequence number */
+ protected function _getFullResponse($seq)
+ {
+ $response = '';
+ try {
+ do {
+ $line = $this->_buffer->readLine($seq);
+ $response .= $line;
+ } while (null !== $line && false !== $line && ' ' != $line[3]);
+ } catch (Swift_TransportException $e) {
+ $this->_throwException($e);
+ } catch (Swift_IoException $e) {
+ $this->_throwException(
+ new Swift_TransportException(
+ $e->getMessage())
+ );
+ }
+
+ return $response;
+ }
+
+ /** Send an email to the given recipients from the given reverse path */
+ private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
+ {
+ $sent = 0;
+ $this->_doMailFromCommand($reversePath);
+ foreach ($recipients as $forwardPath) {
+ try {
+ $this->_doRcptToCommand($forwardPath);
+ ++$sent;
+ } catch (Swift_TransportException $e) {
+ $failedRecipients[] = $forwardPath;
+ }
+ }
+
+ if ($sent != 0) {
+ $this->_doDataCommand();
+ $this->_streamMessage($message);
+ } else {
+ $this->reset();
+ }
+
+ return $sent;
+ }
+
+ /** Send a message to the given To: recipients */
+ private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients)
+ {
+ if (empty($to)) {
+ return 0;
+ }
+
+ return $this->_doMailTransaction($message, $reversePath, array_keys($to),
+ $failedRecipients);
+ }
+
+ /** Send a message to all Bcc: recipients */
+ private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients)
+ {
+ $sent = 0;
+ foreach ($bcc as $forwardPath => $name) {
+ $message->setBcc(array($forwardPath => $name));
+ $sent += $this->_doMailTransaction(
+ $message, $reversePath, array($forwardPath), $failedRecipients
+ );
+ }
+
+ return $sent;
+ }
+
+ /** Try to determine the hostname of the server this is run on */
+ private function _lookupHostname()
+ {
+ if (!empty($_SERVER['SERVER_NAME']) && $this->_isFqdn($_SERVER['SERVER_NAME'])) {
+ $this->_domain = $_SERVER['SERVER_NAME'];
+ } elseif (!empty($_SERVER['SERVER_ADDR'])) {
+ // Set the address literal tag (See RFC 5321, section: 4.1.3)
+ if (false === strpos($_SERVER['SERVER_ADDR'], ':')) {
+ $prefix = ''; // IPv4 addresses are not tagged.
+ } else {
+ $prefix = 'IPv6:'; // Adding prefix in case of IPv6.
+ }
+
+ $this->_domain = sprintf('[%s%s]', $prefix, $_SERVER['SERVER_ADDR']);
+ }
+ }
+
+ /** Determine is the $hostname is a fully-qualified name */
+ private function _isFqdn($hostname)
+ {
+ // We could do a really thorough check, but there's really no point
+ if (false !== $dotPos = strpos($hostname, '.')) {
+ return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
+ }
+
+ return false;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ try {
+ $this->stop();
+ } catch (Exception $e) {
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php
new file mode 100644
index 0000000..53f721d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles CRAM-MD5 authentication.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_Esmtp_Auth_CramMd5Authenticator implements Swift_Transport_Esmtp_Authenticator
+{
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword()
+ {
+ return 'CRAM-MD5';
+ }
+
+ /**
+ * Try to authenticate the user with $username and $password.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
+ {
+ try {
+ $challenge = $agent->executeCommand("AUTH CRAM-MD5\r\n", array(334));
+ $challenge = base64_decode(substr($challenge, 4));
+ $message = base64_encode(
+ $username.' '.$this->_getResponse($password, $challenge)
+ );
+ $agent->executeCommand(sprintf("%s\r\n", $message), array(235));
+
+ return true;
+ } catch (Swift_TransportException $e) {
+ $agent->executeCommand("RSET\r\n", array(250));
+
+ return false;
+ }
+ }
+
+ /**
+ * Generate a CRAM-MD5 response from a server challenge.
+ *
+ * @param string $secret
+ * @param string $challenge
+ *
+ * @return string
+ */
+ private function _getResponse($secret, $challenge)
+ {
+ if (strlen($secret) > 64) {
+ $secret = pack('H32', md5($secret));
+ }
+
+ if (strlen($secret) < 64) {
+ $secret = str_pad($secret, 64, chr(0));
+ }
+
+ $k_ipad = substr($secret, 0, 64) ^ str_repeat(chr(0x36), 64);
+ $k_opad = substr($secret, 0, 64) ^ str_repeat(chr(0x5C), 64);
+
+ $inner = pack('H32', md5($k_ipad.$challenge));
+ $digest = md5($k_opad.$inner);
+
+ return $digest;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php
new file mode 100644
index 0000000..6ab6e33
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles LOGIN authentication.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_Esmtp_Auth_LoginAuthenticator implements Swift_Transport_Esmtp_Authenticator
+{
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword()
+ {
+ return 'LOGIN';
+ }
+
+ /**
+ * Try to authenticate the user with $username and $password.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
+ {
+ try {
+ $agent->executeCommand("AUTH LOGIN\r\n", array(334));
+ $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), array(334));
+ $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), array(235));
+
+ return true;
+ } catch (Swift_TransportException $e) {
+ $agent->executeCommand("RSET\r\n", array(250));
+
+ return false;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php
new file mode 100644
index 0000000..8392658
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php
@@ -0,0 +1,725 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * This authentication is for Exchange servers. We support version 1 & 2.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles NTLM authentication.
+ *
+ * @author Ward Peeters <ward@coding-tech.com>
+ */
+class Swift_Transport_Esmtp_Auth_NTLMAuthenticator implements Swift_Transport_Esmtp_Authenticator
+{
+ const NTLMSIG = "NTLMSSP\x00";
+ const DESCONST = 'KGS!@#$%';
+
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword()
+ {
+ return 'NTLM';
+ }
+
+ /**
+ * Try to authenticate the user with $username and $password.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
+ {
+ if (!function_exists('openssl_random_pseudo_bytes') || !function_exists('openssl_encrypt')) {
+ throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.');
+ }
+
+ if (!function_exists('bcmul')) {
+ throw new LogicException('The BCMath functions must be enabled to use the NTLM authenticator.');
+ }
+
+ try {
+ // execute AUTH command and filter out the code at the beginning
+ // AUTH NTLM xxxx
+ $response = base64_decode(substr(trim($this->sendMessage1($agent)), 4));
+
+ // extra parameters for our unit cases
+ $timestamp = func_num_args() > 3 ? func_get_arg(3) : $this->getCorrectTimestamp(bcmul(microtime(true), '1000'));
+ $client = func_num_args() > 4 ? func_get_arg(4) : $this->getRandomBytes(8);
+
+ // Message 3 response
+ $this->sendMessage3($response, $username, $password, $timestamp, $client, $agent);
+
+ return true;
+ } catch (Swift_TransportException $e) {
+ $agent->executeCommand("RSET\r\n", array(250));
+
+ return false;
+ }
+ }
+
+ protected function si2bin($si, $bits = 32)
+ {
+ $bin = null;
+ if ($si >= -pow(2, $bits - 1) && ($si <= pow(2, $bits - 1))) {
+ // positive or zero
+ if ($si >= 0) {
+ $bin = base_convert($si, 10, 2);
+ // pad to $bits bit
+ $bin_length = strlen($bin);
+ if ($bin_length < $bits) {
+ $bin = str_repeat('0', $bits - $bin_length).$bin;
+ }
+ } else {
+ // negative
+ $si = -$si - pow(2, $bits);
+ $bin = base_convert($si, 10, 2);
+ $bin_length = strlen($bin);
+ if ($bin_length > $bits) {
+ $bin = str_repeat('1', $bits - $bin_length).$bin;
+ }
+ }
+ }
+
+ return $bin;
+ }
+
+ /**
+ * Send our auth message and returns the response.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ *
+ * @return string SMTP Response
+ */
+ protected function sendMessage1(Swift_Transport_SmtpAgent $agent)
+ {
+ $message = $this->createMessage1();
+
+ return $agent->executeCommand(sprintf("AUTH %s %s\r\n", $this->getAuthKeyword(), base64_encode($message)), array(334));
+ }
+
+ /**
+ * Fetch all details of our response (message 2).
+ *
+ * @param string $response
+ *
+ * @return array our response parsed
+ */
+ protected function parseMessage2($response)
+ {
+ $responseHex = bin2hex($response);
+ $length = floor(hexdec(substr($responseHex, 28, 4)) / 256) * 2;
+ $offset = floor(hexdec(substr($responseHex, 32, 4)) / 256) * 2;
+ $challenge = $this->hex2bin(substr($responseHex, 48, 16));
+ $context = $this->hex2bin(substr($responseHex, 64, 16));
+ $targetInfoH = $this->hex2bin(substr($responseHex, 80, 16));
+ $targetName = $this->hex2bin(substr($responseHex, $offset, $length));
+ $offset = floor(hexdec(substr($responseHex, 88, 4)) / 256) * 2;
+ $targetInfoBlock = substr($responseHex, $offset);
+ list($domainName, $serverName, $DNSDomainName, $DNSServerName, $terminatorByte) = $this->readSubBlock($targetInfoBlock);
+
+ return array(
+ $challenge,
+ $context,
+ $targetInfoH,
+ $targetName,
+ $domainName,
+ $serverName,
+ $DNSDomainName,
+ $DNSServerName,
+ $this->hex2bin($targetInfoBlock),
+ $terminatorByte,
+ );
+ }
+
+ /**
+ * Read the blob information in from message2.
+ *
+ * @param $block
+ *
+ * @return array
+ */
+ protected function readSubBlock($block)
+ {
+ // remove terminatorByte cause it's always the same
+ $block = substr($block, 0, -8);
+
+ $length = strlen($block);
+ $offset = 0;
+ $data = array();
+ while ($offset < $length) {
+ $blockLength = hexdec(substr(substr($block, $offset, 8), -4)) / 256;
+ $offset += 8;
+ $data[] = $this->hex2bin(substr($block, $offset, $blockLength * 2));
+ $offset += $blockLength * 2;
+ }
+
+ if (count($data) == 3) {
+ $data[] = $data[2];
+ $data[2] = '';
+ }
+
+ $data[] = $this->createByte('00');
+
+ return $data;
+ }
+
+ /**
+ * Send our final message with all our data.
+ *
+ * @param string $response Message 1 response (message 2)
+ * @param string $username
+ * @param string $password
+ * @param string $timestamp
+ * @param string $client
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param bool $v2 Use version2 of the protocol
+ *
+ * @return string
+ */
+ protected function sendMessage3($response, $username, $password, $timestamp, $client, Swift_Transport_SmtpAgent $agent, $v2 = true)
+ {
+ list($domain, $username) = $this->getDomainAndUsername($username);
+ //$challenge, $context, $targetInfoH, $targetName, $domainName, $workstation, $DNSDomainName, $DNSServerName, $blob, $ter
+ list($challenge, , , , , $workstation, , , $blob) = $this->parseMessage2($response);
+
+ if (!$v2) {
+ // LMv1
+ $lmResponse = $this->createLMPassword($password, $challenge);
+ // NTLMv1
+ $ntlmResponse = $this->createNTLMPassword($password, $challenge);
+ } else {
+ // LMv2
+ $lmResponse = $this->createLMv2Password($password, $username, $domain, $challenge, $client);
+ // NTLMv2
+ $ntlmResponse = $this->createNTLMv2Hash($password, $username, $domain, $challenge, $blob, $timestamp, $client);
+ }
+
+ $message = $this->createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse);
+
+ return $agent->executeCommand(sprintf("%s\r\n", base64_encode($message)), array(235));
+ }
+
+ /**
+ * Create our message 1.
+ *
+ * @return string
+ */
+ protected function createMessage1()
+ {
+ return self::NTLMSIG
+ .$this->createByte('01') // Message 1
+.$this->createByte('0702'); // Flags
+ }
+
+ /**
+ * Create our message 3.
+ *
+ * @param string $domain
+ * @param string $username
+ * @param string $workstation
+ * @param string $lmResponse
+ * @param string $ntlmResponse
+ *
+ * @return string
+ */
+ protected function createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse)
+ {
+ // Create security buffers
+ $domainSec = $this->createSecurityBuffer($domain, 64);
+ $domainInfo = $this->readSecurityBuffer(bin2hex($domainSec));
+ $userSec = $this->createSecurityBuffer($username, ($domainInfo[0] + $domainInfo[1]) / 2);
+ $userInfo = $this->readSecurityBuffer(bin2hex($userSec));
+ $workSec = $this->createSecurityBuffer($workstation, ($userInfo[0] + $userInfo[1]) / 2);
+ $workInfo = $this->readSecurityBuffer(bin2hex($workSec));
+ $lmSec = $this->createSecurityBuffer($lmResponse, ($workInfo[0] + $workInfo[1]) / 2, true);
+ $lmInfo = $this->readSecurityBuffer(bin2hex($lmSec));
+ $ntlmSec = $this->createSecurityBuffer($ntlmResponse, ($lmInfo[0] + $lmInfo[1]) / 2, true);
+
+ return self::NTLMSIG
+ .$this->createByte('03') // TYPE 3 message
+.$lmSec // LM response header
+.$ntlmSec // NTLM response header
+.$domainSec // Domain header
+.$userSec // User header
+.$workSec // Workstation header
+.$this->createByte('000000009a', 8) // session key header (empty)
+.$this->createByte('01020000') // FLAGS
+.$this->convertTo16bit($domain) // domain name
+.$this->convertTo16bit($username) // username
+.$this->convertTo16bit($workstation) // workstation
+.$lmResponse
+ .$ntlmResponse;
+ }
+
+ /**
+ * @param string $timestamp Epoch timestamp in microseconds
+ * @param string $client Random bytes
+ * @param string $targetInfo
+ *
+ * @return string
+ */
+ protected function createBlob($timestamp, $client, $targetInfo)
+ {
+ return $this->createByte('0101')
+ .$this->createByte('00')
+ .$timestamp
+ .$client
+ .$this->createByte('00')
+ .$targetInfo
+ .$this->createByte('00');
+ }
+
+ /**
+ * Get domain and username from our username.
+ *
+ * @example DOMAIN\username
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ protected function getDomainAndUsername($name)
+ {
+ if (strpos($name, '\\') !== false) {
+ return explode('\\', $name);
+ }
+
+ if (false !== strpos($name, '@')) {
+ list($user, $domain) = explode('@', $name);
+
+ return array($domain, $user);
+ }
+
+ // no domain passed
+ return array('', $name);
+ }
+
+ /**
+ * Create LMv1 response.
+ *
+ * @param string $password
+ * @param string $challenge
+ *
+ * @return string
+ */
+ protected function createLMPassword($password, $challenge)
+ {
+ // FIRST PART
+ $password = $this->createByte(strtoupper($password), 14, false);
+ list($key1, $key2) = str_split($password, 7);
+
+ $desKey1 = $this->createDesKey($key1);
+ $desKey2 = $this->createDesKey($key2);
+
+ $constantDecrypt = $this->createByte($this->desEncrypt(self::DESCONST, $desKey1).$this->desEncrypt(self::DESCONST, $desKey2), 21, false);
+
+ // SECOND PART
+ list($key1, $key2, $key3) = str_split($constantDecrypt, 7);
+
+ $desKey1 = $this->createDesKey($key1);
+ $desKey2 = $this->createDesKey($key2);
+ $desKey3 = $this->createDesKey($key3);
+
+ return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
+ }
+
+ /**
+ * Create NTLMv1 response.
+ *
+ * @param string $password
+ * @param string $challenge
+ *
+ * @return string
+ */
+ protected function createNTLMPassword($password, $challenge)
+ {
+ // FIRST PART
+ $ntlmHash = $this->createByte($this->md4Encrypt($password), 21, false);
+ list($key1, $key2, $key3) = str_split($ntlmHash, 7);
+
+ $desKey1 = $this->createDesKey($key1);
+ $desKey2 = $this->createDesKey($key2);
+ $desKey3 = $this->createDesKey($key3);
+
+ return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
+ }
+
+ /**
+ * Convert a normal timestamp to a tenth of a microtime epoch time.
+ *
+ * @param string $time
+ *
+ * @return string
+ */
+ protected function getCorrectTimestamp($time)
+ {
+ // Get our timestamp (tricky!)
+ $time = number_format($time, 0, '.', ''); // save microtime to string
+ $time = bcadd($time, '11644473600000', 0); // add epoch time
+ $time = bcmul($time, 10000, 0); // tenths of a microsecond.
+
+ $binary = $this->si2bin($time, 64); // create 64 bit binary string
+ $timestamp = '';
+ for ($i = 0; $i < 8; ++$i) {
+ $timestamp .= chr(bindec(substr($binary, -(($i + 1) * 8), 8)));
+ }
+
+ return $timestamp;
+ }
+
+ /**
+ * Create LMv2 response.
+ *
+ * @param string $password
+ * @param string $username
+ * @param string $domain
+ * @param string $challenge NTLM Challenge
+ * @param string $client Random string
+ *
+ * @return string
+ */
+ protected function createLMv2Password($password, $username, $domain, $challenge, $client)
+ {
+ $lmPass = '00'; // by default 00
+ // if $password > 15 than we can't use this method
+ if (strlen($password) <= 15) {
+ $ntlmHash = $this->md4Encrypt($password);
+ $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));
+
+ $lmPass = bin2hex($this->md5Encrypt($ntml2Hash, $challenge.$client).$client);
+ }
+
+ return $this->createByte($lmPass, 24);
+ }
+
+ /**
+ * Create NTLMv2 response.
+ *
+ * @param string $password
+ * @param string $username
+ * @param string $domain
+ * @param string $challenge Hex values
+ * @param string $targetInfo Hex values
+ * @param string $timestamp
+ * @param string $client Random bytes
+ *
+ * @return string
+ *
+ * @see http://davenport.sourceforge.net/ntlm.html#theNtlmResponse
+ */
+ protected function createNTLMv2Hash($password, $username, $domain, $challenge, $targetInfo, $timestamp, $client)
+ {
+ $ntlmHash = $this->md4Encrypt($password);
+ $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));
+
+ // create blob
+ $blob = $this->createBlob($timestamp, $client, $targetInfo);
+
+ $ntlmv2Response = $this->md5Encrypt($ntml2Hash, $challenge.$blob);
+
+ return $ntlmv2Response.$blob;
+ }
+
+ protected function createDesKey($key)
+ {
+ $material = array(bin2hex($key[0]));
+ $len = strlen($key);
+ for ($i = 1; $i < $len; ++$i) {
+ list($high, $low) = str_split(bin2hex($key[$i]));
+ $v = $this->castToByte(ord($key[$i - 1]) << (7 + 1 - $i) | $this->uRShift(hexdec(dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xf)), $i));
+ $material[] = str_pad(substr(dechex($v), -2), 2, '0', STR_PAD_LEFT); // cast to byte
+ }
+ $material[] = str_pad(substr(dechex($this->castToByte(ord($key[6]) << 1)), -2), 2, '0');
+
+ // odd parity
+ foreach ($material as $k => $v) {
+ $b = $this->castToByte(hexdec($v));
+ $needsParity = (($this->uRShift($b, 7) ^ $this->uRShift($b, 6) ^ $this->uRShift($b, 5)
+ ^ $this->uRShift($b, 4) ^ $this->uRShift($b, 3) ^ $this->uRShift($b, 2)
+ ^ $this->uRShift($b, 1)) & 0x01) == 0;
+
+ list($high, $low) = str_split($v);
+ if ($needsParity) {
+ $material[$k] = dechex(hexdec($high) | 0x0).dechex(hexdec($low) | 0x1);
+ } else {
+ $material[$k] = dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xe);
+ }
+ }
+
+ return $this->hex2bin(implode('', $material));
+ }
+
+ /** HELPER FUNCTIONS */
+
+ /**
+ * Create our security buffer depending on length and offset.
+ *
+ * @param string $value Value we want to put in
+ * @param int $offset start of value
+ * @param bool $is16 Do we 16bit string or not?
+ *
+ * @return string
+ */
+ protected function createSecurityBuffer($value, $offset, $is16 = false)
+ {
+ $length = strlen(bin2hex($value));
+ $length = $is16 ? $length / 2 : $length;
+ $length = $this->createByte(str_pad(dechex($length), 2, '0', STR_PAD_LEFT), 2);
+
+ return $length.$length.$this->createByte(dechex($offset), 4);
+ }
+
+ /**
+ * Read our security buffer to fetch length and offset of our value.
+ *
+ * @param string $value Securitybuffer in hex
+ *
+ * @return array array with length and offset
+ */
+ protected function readSecurityBuffer($value)
+ {
+ $length = floor(hexdec(substr($value, 0, 4)) / 256) * 2;
+ $offset = floor(hexdec(substr($value, 8, 4)) / 256) * 2;
+
+ return array($length, $offset);
+ }
+
+ /**
+ * Cast to byte java equivalent to (byte).
+ *
+ * @param int $v
+ *
+ * @return int
+ */
+ protected function castToByte($v)
+ {
+ return (($v + 128) % 256) - 128;
+ }
+
+ /**
+ * Java unsigned right bitwise
+ * $a >>> $b.
+ *
+ * @param int $a
+ * @param int $b
+ *
+ * @return int
+ */
+ protected function uRShift($a, $b)
+ {
+ if ($b == 0) {
+ return $a;
+ }
+
+ return ($a >> $b) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($b - 1));
+ }
+
+ /**
+ * Right padding with 0 to certain length.
+ *
+ * @param string $input
+ * @param int $bytes Length of bytes
+ * @param bool $isHex Did we provided hex value
+ *
+ * @return string
+ */
+ protected function createByte($input, $bytes = 4, $isHex = true)
+ {
+ if ($isHex) {
+ $byte = $this->hex2bin(str_pad($input, $bytes * 2, '00'));
+ } else {
+ $byte = str_pad($input, $bytes, "\x00");
+ }
+
+ return $byte;
+ }
+
+ /**
+ * Create random bytes.
+ *
+ * @param $length
+ *
+ * @return string
+ */
+ protected function getRandomBytes($length)
+ {
+ $bytes = openssl_random_pseudo_bytes($length, $strong);
+
+ if (false !== $bytes && true === $strong) {
+ return $bytes;
+ }
+
+ throw new RuntimeException('OpenSSL did not produce a secure random number.');
+ }
+
+ /** ENCRYPTION ALGORITHMS */
+
+ /**
+ * DES Encryption.
+ *
+ * @param string $value An 8-byte string
+ * @param string $key
+ *
+ * @return string
+ */
+ protected function desEncrypt($value, $key)
+ {
+ // 1 == OPENSSL_RAW_DATA - but constant is only available as of PHP 5.4.
+ return substr(openssl_encrypt($value, 'DES-ECB', $key, 1), 0, 8);
+ }
+
+ /**
+ * MD5 Encryption.
+ *
+ * @param string $key Encryption key
+ * @param string $msg Message to encrypt
+ *
+ * @return string
+ */
+ protected function md5Encrypt($key, $msg)
+ {
+ $blocksize = 64;
+ if (strlen($key) > $blocksize) {
+ $key = pack('H*', md5($key));
+ }
+
+ $key = str_pad($key, $blocksize, "\0");
+ $ipadk = $key ^ str_repeat("\x36", $blocksize);
+ $opadk = $key ^ str_repeat("\x5c", $blocksize);
+
+ return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg))));
+ }
+
+ /**
+ * MD4 Encryption.
+ *
+ * @param string $input
+ *
+ * @return string
+ *
+ * @see http://php.net/manual/en/ref.hash.php
+ */
+ protected function md4Encrypt($input)
+ {
+ $input = $this->convertTo16bit($input);
+
+ return function_exists('hash') ? $this->hex2bin(hash('md4', $input)) : mhash(MHASH_MD4, $input);
+ }
+
+ /**
+ * Convert UTF-8 to UTF-16.
+ *
+ * @param string $input
+ *
+ * @return string
+ */
+ protected function convertTo16bit($input)
+ {
+ return iconv('UTF-8', 'UTF-16LE', $input);
+ }
+
+ /**
+ * Hex2bin replacement for < PHP 5.4.
+ *
+ * @param string $hex
+ *
+ * @return string Binary
+ */
+ protected function hex2bin($hex)
+ {
+ if (function_exists('hex2bin')) {
+ return hex2bin($hex);
+ } else {
+ return pack('H*', $hex);
+ }
+ }
+
+ /**
+ * @param string $message
+ */
+ protected function debug($message)
+ {
+ $message = bin2hex($message);
+ $messageId = substr($message, 16, 8);
+ echo substr($message, 0, 16)." NTLMSSP Signature<br />\n";
+ echo $messageId." Type Indicator<br />\n";
+
+ if ($messageId == '02000000') {
+ $map = array(
+ 'Challenge',
+ 'Context',
+ 'Target Information Security Buffer',
+ 'Target Name Data',
+ 'NetBIOS Domain Name',
+ 'NetBIOS Server Name',
+ 'DNS Domain Name',
+ 'DNS Server Name',
+ 'BLOB',
+ 'Target Information Terminator',
+ );
+
+ $data = $this->parseMessage2($this->hex2bin($message));
+
+ foreach ($map as $key => $value) {
+ echo bin2hex($data[$key]).' - '.$data[$key].' ||| '.$value."<br />\n";
+ }
+ } elseif ($messageId == '03000000') {
+ $i = 0;
+ $data[$i++] = substr($message, 24, 16);
+ list($lmLength, $lmOffset) = $this->readSecurityBuffer($data[$i - 1]);
+
+ $data[$i++] = substr($message, 40, 16);
+ list($ntmlLength, $ntmlOffset) = $this->readSecurityBuffer($data[$i - 1]);
+
+ $data[$i++] = substr($message, 56, 16);
+ list($targetLength, $targetOffset) = $this->readSecurityBuffer($data[$i - 1]);
+
+ $data[$i++] = substr($message, 72, 16);
+ list($userLength, $userOffset) = $this->readSecurityBuffer($data[$i - 1]);
+
+ $data[$i++] = substr($message, 88, 16);
+ list($workLength, $workOffset) = $this->readSecurityBuffer($data[$i - 1]);
+
+ $data[$i++] = substr($message, 104, 16);
+ $data[$i++] = substr($message, 120, 8);
+ $data[$i++] = substr($message, $targetOffset, $targetLength);
+ $data[$i++] = substr($message, $userOffset, $userLength);
+ $data[$i++] = substr($message, $workOffset, $workLength);
+ $data[$i++] = substr($message, $lmOffset, $lmLength);
+ $data[$i] = substr($message, $ntmlOffset, $ntmlLength);
+
+ $map = array(
+ 'LM Response Security Buffer',
+ 'NTLM Response Security Buffer',
+ 'Target Name Security Buffer',
+ 'User Name Security Buffer',
+ 'Workstation Name Security Buffer',
+ 'Session Key Security Buffer',
+ 'Flags',
+ 'Target Name Data',
+ 'User Name Data',
+ 'Workstation Name Data',
+ 'LM Response Data',
+ 'NTLM Response Data',
+ );
+
+ foreach ($map as $key => $value) {
+ echo $data[$key].' - '.$this->hex2bin($data[$key]).' ||| '.$value."<br />\n";
+ }
+ }
+
+ echo '<br /><br />';
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php
new file mode 100644
index 0000000..43219f9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles PLAIN authentication.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_Esmtp_Auth_PlainAuthenticator implements Swift_Transport_Esmtp_Authenticator
+{
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword()
+ {
+ return 'PLAIN';
+ }
+
+ /**
+ * Try to authenticate the user with $username and $password.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
+ {
+ try {
+ $message = base64_encode($username.chr(0).$username.chr(0).$password);
+ $agent->executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), array(235));
+
+ return true;
+ } catch (Swift_TransportException $e) {
+ $agent->executeCommand("RSET\r\n", array(250));
+
+ return false;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php
new file mode 100644
index 0000000..ca35e7b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handles XOAUTH2 authentication.
+ *
+ * Example:
+ * <code>
+ * $transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 587, 'tls')
+ * ->setAuthMode('XOAUTH2')
+ * ->setUsername('YOUR_EMAIL_ADDRESS')
+ * ->setPassword('YOUR_ACCESS_TOKEN');
+ * </code>
+ *
+ * @author xu.li<AthenaLightenedMyPath@gmail.com>
+ *
+ * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol
+ */
+class Swift_Transport_Esmtp_Auth_XOAuth2Authenticator implements Swift_Transport_Esmtp_Authenticator
+{
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword()
+ {
+ return 'XOAUTH2';
+ }
+
+ /**
+ * Try to authenticate the user with $email and $token.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $email
+ * @param string $token
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $email, $token)
+ {
+ try {
+ $param = $this->constructXOAuth2Params($email, $token);
+ $agent->executeCommand('AUTH XOAUTH2 '.$param."\r\n", array(235));
+
+ return true;
+ } catch (Swift_TransportException $e) {
+ $agent->executeCommand("RSET\r\n", array(250));
+
+ return false;
+ }
+ }
+
+ /**
+ * Construct the auth parameter.
+ *
+ * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism
+ */
+ protected function constructXOAuth2Params($email, $token)
+ {
+ return base64_encode("user=$email\1auth=Bearer $token\1\1");
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php
new file mode 100644
index 0000000..cb36133
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php
@@ -0,0 +1,263 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An ESMTP handler for AUTH support.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_Esmtp_AuthHandler implements Swift_Transport_EsmtpHandler
+{
+ /**
+ * Authenticators available to process the request.
+ *
+ * @var Swift_Transport_Esmtp_Authenticator[]
+ */
+ private $_authenticators = array();
+
+ /**
+ * The username for authentication.
+ *
+ * @var string
+ */
+ private $_username;
+
+ /**
+ * The password for authentication.
+ *
+ * @var string
+ */
+ private $_password;
+
+ /**
+ * The auth mode for authentication.
+ *
+ * @var string
+ */
+ private $_auth_mode;
+
+ /**
+ * The ESMTP AUTH parameters available.
+ *
+ * @var string[]
+ */
+ private $_esmtpParams = array();
+
+ /**
+ * Create a new AuthHandler with $authenticators for support.
+ *
+ * @param Swift_Transport_Esmtp_Authenticator[] $authenticators
+ */
+ public function __construct(array $authenticators)
+ {
+ $this->setAuthenticators($authenticators);
+ }
+
+ /**
+ * Set the Authenticators which can process a login request.
+ *
+ * @param Swift_Transport_Esmtp_Authenticator[] $authenticators
+ */
+ public function setAuthenticators(array $authenticators)
+ {
+ $this->_authenticators = $authenticators;
+ }
+
+ /**
+ * Get the Authenticators which can process a login request.
+ *
+ * @return Swift_Transport_Esmtp_Authenticator[]
+ */
+ public function getAuthenticators()
+ {
+ return $this->_authenticators;
+ }
+
+ /**
+ * Set the username to authenticate with.
+ *
+ * @param string $username
+ */
+ public function setUsername($username)
+ {
+ $this->_username = $username;
+ }
+
+ /**
+ * Get the username to authenticate with.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->_username;
+ }
+
+ /**
+ * Set the password to authenticate with.
+ *
+ * @param string $password
+ */
+ public function setPassword($password)
+ {
+ $this->_password = $password;
+ }
+
+ /**
+ * Get the password to authenticate with.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->_password;
+ }
+
+ /**
+ * Set the auth mode to use to authenticate.
+ *
+ * @param string $mode
+ */
+ public function setAuthMode($mode)
+ {
+ $this->_auth_mode = $mode;
+ }
+
+ /**
+ * Get the auth mode to use to authenticate.
+ *
+ * @return string
+ */
+ public function getAuthMode()
+ {
+ return $this->_auth_mode;
+ }
+
+ /**
+ * Get the name of the ESMTP extension this handles.
+ *
+ * @return bool
+ */
+ public function getHandledKeyword()
+ {
+ return 'AUTH';
+ }
+
+ /**
+ * Set the parameters which the EHLO greeting indicated.
+ *
+ * @param string[] $parameters
+ */
+ public function setKeywordParams(array $parameters)
+ {
+ $this->_esmtpParams = $parameters;
+ }
+
+ /**
+ * Runs immediately after a EHLO has been issued.
+ *
+ * @param Swift_Transport_SmtpAgent $agent to read/write
+ */
+ public function afterEhlo(Swift_Transport_SmtpAgent $agent)
+ {
+ if ($this->_username) {
+ $count = 0;
+ foreach ($this->_getAuthenticatorsForAgent() as $authenticator) {
+ if (in_array(strtolower($authenticator->getAuthKeyword()),
+ array_map('strtolower', $this->_esmtpParams))) {
+ ++$count;
+ if ($authenticator->authenticate($agent, $this->_username, $this->_password)) {
+ return;
+ }
+ }
+ }
+ throw new Swift_TransportException(
+ 'Failed to authenticate on SMTP server with username "'.
+ $this->_username.'" using '.$count.' possible authenticators'
+ );
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function getMailParams()
+ {
+ return array();
+ }
+
+ /**
+ * Not used.
+ */
+ public function getRcptParams()
+ {
+ return array();
+ }
+
+ /**
+ * Not used.
+ */
+ public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false)
+ {
+ }
+
+ /**
+ * Returns +1, -1 or 0 according to the rules for usort().
+ *
+ * This method is called to ensure extensions can be execute in an appropriate order.
+ *
+ * @param string $esmtpKeyword to compare with
+ *
+ * @return int
+ */
+ public function getPriorityOver($esmtpKeyword)
+ {
+ return 0;
+ }
+
+ /**
+ * Returns an array of method names which are exposed to the Esmtp class.
+ *
+ * @return string[]
+ */
+ public function exposeMixinMethods()
+ {
+ return array('setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode');
+ }
+
+ /**
+ * Not used.
+ */
+ public function resetState()
+ {
+ }
+
+ /**
+ * Returns the authenticator list for the given agent.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ *
+ * @return array
+ */
+ protected function _getAuthenticatorsForAgent()
+ {
+ if (!$mode = strtolower($this->_auth_mode)) {
+ return $this->_authenticators;
+ }
+
+ foreach ($this->_authenticators as $authenticator) {
+ if (strtolower($authenticator->getAuthKeyword()) == $mode) {
+ return array($authenticator);
+ }
+ }
+
+ throw new Swift_TransportException('Auth mode '.$mode.' is invalid');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php
new file mode 100644
index 0000000..12a9abf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An Authentication mechanism.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport_Esmtp_Authenticator
+{
+ /**
+ * Get the name of the AUTH mechanism this Authenticator handles.
+ *
+ * @return string
+ */
+ public function getAuthKeyword();
+
+ /**
+ * Try to authenticate the user with $username and $password.
+ *
+ * @param Swift_Transport_SmtpAgent $agent
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php
new file mode 100644
index 0000000..c17ef8f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * An ESMTP handler.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport_EsmtpHandler
+{
+ /**
+ * Get the name of the ESMTP extension this handles.
+ *
+ * @return bool
+ */
+ public function getHandledKeyword();
+
+ /**
+ * Set the parameters which the EHLO greeting indicated.
+ *
+ * @param string[] $parameters
+ */
+ public function setKeywordParams(array $parameters);
+
+ /**
+ * Runs immediately after a EHLO has been issued.
+ *
+ * @param Swift_Transport_SmtpAgent $agent to read/write
+ */
+ public function afterEhlo(Swift_Transport_SmtpAgent $agent);
+
+ /**
+ * Get params which are appended to MAIL FROM:<>.
+ *
+ * @return string[]
+ */
+ public function getMailParams();
+
+ /**
+ * Get params which are appended to RCPT TO:<>.
+ *
+ * @return string[]
+ */
+ public function getRcptParams();
+
+ /**
+ * Runs when a command is due to be sent.
+ *
+ * @param Swift_Transport_SmtpAgent $agent to read/write
+ * @param string $command to send
+ * @param int[] $codes expected in response
+ * @param string[] $failedRecipients to collect failures
+ * @param bool $stop to be set true by-reference if the command is now sent
+ */
+ public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false);
+
+ /**
+ * Returns +1, -1 or 0 according to the rules for usort().
+ *
+ * This method is called to ensure extensions can be execute in an appropriate order.
+ *
+ * @param string $esmtpKeyword to compare with
+ *
+ * @return int
+ */
+ public function getPriorityOver($esmtpKeyword);
+
+ /**
+ * Returns an array of method names which are exposed to the Esmtp class.
+ *
+ * @return string[]
+ */
+ public function exposeMixinMethods();
+
+ /**
+ * Tells this handler to clear any buffers and reset its state.
+ */
+ public function resetState();
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php
new file mode 100644
index 0000000..156e2cf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php
@@ -0,0 +1,411 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages over SMTP with ESMTP support.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_EsmtpTransport extends Swift_Transport_AbstractSmtpTransport implements Swift_Transport_SmtpAgent
+{
+ /**
+ * ESMTP extension handlers.
+ *
+ * @var Swift_Transport_EsmtpHandler[]
+ */
+ private $_handlers = array();
+
+ /**
+ * ESMTP capabilities.
+ *
+ * @var string[]
+ */
+ private $_capabilities = array();
+
+ /**
+ * Connection buffer parameters.
+ *
+ * @var array
+ */
+ private $_params = array(
+ 'protocol' => 'tcp',
+ 'host' => 'localhost',
+ 'port' => 25,
+ 'timeout' => 30,
+ 'blocking' => 1,
+ 'tls' => false,
+ 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+ 'stream_context_options' => array(),
+ );
+
+ /**
+ * Creates a new EsmtpTransport using the given I/O buffer.
+ *
+ * @param Swift_Transport_IoBuffer $buf
+ * @param Swift_Transport_EsmtpHandler[] $extensionHandlers
+ * @param Swift_Events_EventDispatcher $dispatcher
+ */
+ public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher)
+ {
+ parent::__construct($buf, $dispatcher);
+ $this->setExtensionHandlers($extensionHandlers);
+ }
+
+ /**
+ * Set the host to connect to.
+ *
+ * @param string $host
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->_params['host'] = $host;
+
+ return $this;
+ }
+
+ /**
+ * Get the host to connect to.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->_params['host'];
+ }
+
+ /**
+ * Set the port to connect to.
+ *
+ * @param int $port
+ *
+ * @return $this
+ */
+ public function setPort($port)
+ {
+ $this->_params['port'] = (int) $port;
+
+ return $this;
+ }
+
+ /**
+ * Get the port to connect to.
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->_params['port'];
+ }
+
+ /**
+ * Set the connection timeout.
+ *
+ * @param int $timeout seconds
+ *
+ * @return $this
+ */
+ public function setTimeout($timeout)
+ {
+ $this->_params['timeout'] = (int) $timeout;
+ $this->_buffer->setParam('timeout', (int) $timeout);
+
+ return $this;
+ }
+
+ /**
+ * Get the connection timeout.
+ *
+ * @return int
+ */
+ public function getTimeout()
+ {
+ return $this->_params['timeout'];
+ }
+
+ /**
+ * Set the encryption type (tls or ssl).
+ *
+ * @param string $encryption
+ *
+ * @return $this
+ */
+ public function setEncryption($encryption)
+ {
+ $encryption = strtolower($encryption);
+ if ('tls' == $encryption) {
+ $this->_params['protocol'] = 'tcp';
+ $this->_params['tls'] = true;
+ } else {
+ $this->_params['protocol'] = $encryption;
+ $this->_params['tls'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the encryption type.
+ *
+ * @return string
+ */
+ public function getEncryption()
+ {
+ return $this->_params['tls'] ? 'tls' : $this->_params['protocol'];
+ }
+
+ /**
+ * Sets the stream context options.
+ *
+ * @param array $options
+ *
+ * @return $this
+ */
+ public function setStreamOptions($options)
+ {
+ $this->_params['stream_context_options'] = $options;
+
+ return $this;
+ }
+
+ /**
+ * Returns the stream context options.
+ *
+ * @return array
+ */
+ public function getStreamOptions()
+ {
+ return $this->_params['stream_context_options'];
+ }
+
+ /**
+ * Sets the source IP.
+ *
+ * @param string $source
+ *
+ * @return $this
+ */
+ public function setSourceIp($source)
+ {
+ $this->_params['sourceIp'] = $source;
+
+ return $this;
+ }
+
+ /**
+ * Returns the IP used to connect to the destination.
+ *
+ * @return string
+ */
+ public function getSourceIp()
+ {
+ return isset($this->_params['sourceIp']) ? $this->_params['sourceIp'] : null;
+ }
+
+ /**
+ * Set ESMTP extension handlers.
+ *
+ * @param Swift_Transport_EsmtpHandler[] $handlers
+ *
+ * @return $this
+ */
+ public function setExtensionHandlers(array $handlers)
+ {
+ $assoc = array();
+ foreach ($handlers as $handler) {
+ $assoc[$handler->getHandledKeyword()] = $handler;
+ }
+
+ @uasort($assoc, array($this, '_sortHandlers'));
+ $this->_handlers = $assoc;
+ $this->_setHandlerParams();
+
+ return $this;
+ }
+
+ /**
+ * Get ESMTP extension handlers.
+ *
+ * @return Swift_Transport_EsmtpHandler[]
+ */
+ public function getExtensionHandlers()
+ {
+ return array_values($this->_handlers);
+ }
+
+ /**
+ * Run a command against the buffer, expecting the given response codes.
+ *
+ * If no response codes are given, the response will not be validated.
+ * If codes are given, an exception will be thrown on an invalid response.
+ *
+ * @param string $command
+ * @param int[] $codes
+ * @param string[] $failures An array of failures by-reference
+ *
+ * @return string
+ */
+ public function executeCommand($command, $codes = array(), &$failures = null)
+ {
+ $failures = (array) $failures;
+ $stopSignal = false;
+ $response = null;
+ foreach ($this->_getActiveHandlers() as $handler) {
+ $response = $handler->onCommand(
+ $this, $command, $codes, $failures, $stopSignal
+ );
+ if ($stopSignal) {
+ return $response;
+ }
+ }
+
+ return parent::executeCommand($command, $codes, $failures);
+ }
+
+ /** Mixin handling method for ESMTP handlers */
+ public function __call($method, $args)
+ {
+ foreach ($this->_handlers as $handler) {
+ if (in_array(strtolower($method),
+ array_map('strtolower', (array) $handler->exposeMixinMethods())
+ )) {
+ $return = call_user_func_array(array($handler, $method), $args);
+ // Allow fluid method calls
+ if (null === $return && substr($method, 0, 3) == 'set') {
+ return $this;
+ } else {
+ return $return;
+ }
+ }
+ }
+ trigger_error('Call to undefined method '.$method, E_USER_ERROR);
+ }
+
+ /** Get the params to initialize the buffer */
+ protected function _getBufferParams()
+ {
+ return $this->_params;
+ }
+
+ /** Overridden to perform EHLO instead */
+ protected function _doHeloCommand()
+ {
+ try {
+ $response = $this->executeCommand(
+ sprintf("EHLO %s\r\n", $this->_domain), array(250)
+ );
+ } catch (Swift_TransportException $e) {
+ return parent::_doHeloCommand();
+ }
+
+ if ($this->_params['tls']) {
+ try {
+ $this->executeCommand("STARTTLS\r\n", array(220));
+
+ if (!$this->_buffer->startTLS()) {
+ throw new Swift_TransportException('Unable to connect with TLS encryption');
+ }
+
+ try {
+ $response = $this->executeCommand(
+ sprintf("EHLO %s\r\n", $this->_domain), array(250)
+ );
+ } catch (Swift_TransportException $e) {
+ return parent::_doHeloCommand();
+ }
+ } catch (Swift_TransportException $e) {
+ $this->_throwException($e);
+ }
+ }
+
+ $this->_capabilities = $this->_getCapabilities($response);
+ $this->_setHandlerParams();
+ foreach ($this->_getActiveHandlers() as $handler) {
+ $handler->afterEhlo($this);
+ }
+ }
+
+ /** Overridden to add Extension support */
+ protected function _doMailFromCommand($address)
+ {
+ $handlers = $this->_getActiveHandlers();
+ $params = array();
+ foreach ($handlers as $handler) {
+ $params = array_merge($params, (array) $handler->getMailParams());
+ }
+ $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
+ $this->executeCommand(
+ sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), array(250)
+ );
+ }
+
+ /** Overridden to add Extension support */
+ protected function _doRcptToCommand($address)
+ {
+ $handlers = $this->_getActiveHandlers();
+ $params = array();
+ foreach ($handlers as $handler) {
+ $params = array_merge($params, (array) $handler->getRcptParams());
+ }
+ $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
+ $this->executeCommand(
+ sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), array(250, 251, 252)
+ );
+ }
+
+ /** Determine ESMTP capabilities by function group */
+ private function _getCapabilities($ehloResponse)
+ {
+ $capabilities = array();
+ $ehloResponse = trim($ehloResponse);
+ $lines = explode("\r\n", $ehloResponse);
+ array_shift($lines);
+ foreach ($lines as $line) {
+ if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
+ $keyword = strtoupper($matches[1]);
+ $paramStr = strtoupper(ltrim($matches[2], ' ='));
+ $params = !empty($paramStr) ? explode(' ', $paramStr) : array();
+ $capabilities[$keyword] = $params;
+ }
+ }
+
+ return $capabilities;
+ }
+
+ /** Set parameters which are used by each extension handler */
+ private function _setHandlerParams()
+ {
+ foreach ($this->_handlers as $keyword => $handler) {
+ if (array_key_exists($keyword, $this->_capabilities)) {
+ $handler->setKeywordParams($this->_capabilities[$keyword]);
+ }
+ }
+ }
+
+ /** Get ESMTP handlers which are currently ok to use */
+ private function _getActiveHandlers()
+ {
+ $handlers = array();
+ foreach ($this->_handlers as $keyword => $handler) {
+ if (array_key_exists($keyword, $this->_capabilities)) {
+ $handlers[] = $handler;
+ }
+ }
+
+ return $handlers;
+ }
+
+ /** Custom sort for extension handler ordering */
+ private function _sortHandlers($a, $b)
+ {
+ return $a->getPriorityOver($b->getHandledKeyword());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php
new file mode 100644
index 0000000..311a0f2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Contains a list of redundant Transports so when one fails, the next is used.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_FailoverTransport extends Swift_Transport_LoadBalancedTransport
+{
+ /**
+ * Registered transport currently used.
+ *
+ * @var Swift_Transport
+ */
+ private $_currentTransport;
+
+ // needed as __construct is called from elsewhere explicitly
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients who were accepted for delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $maxTransports = count($this->_transports);
+ $sent = 0;
+ $this->_lastUsedTransport = null;
+
+ for ($i = 0; $i < $maxTransports
+ && $transport = $this->_getNextTransport(); ++$i) {
+ try {
+ if (!$transport->isStarted()) {
+ $transport->start();
+ }
+
+ if ($sent = $transport->send($message, $failedRecipients)) {
+ $this->_lastUsedTransport = $transport;
+
+ return $sent;
+ }
+ } catch (Swift_TransportException $e) {
+ $this->_killCurrentTransport();
+ }
+ }
+
+ if (count($this->_transports) == 0) {
+ throw new Swift_TransportException(
+ 'All Transports in FailoverTransport failed, or no Transports available'
+ );
+ }
+
+ return $sent;
+ }
+
+ protected function _getNextTransport()
+ {
+ if (!isset($this->_currentTransport)) {
+ $this->_currentTransport = parent::_getNextTransport();
+ }
+
+ return $this->_currentTransport;
+ }
+
+ protected function _killCurrentTransport()
+ {
+ $this->_currentTransport = null;
+ parent::_killCurrentTransport();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php
new file mode 100644
index 0000000..af97adf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Buffers input and output to a resource.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream
+{
+ /** A socket buffer over TCP */
+ const TYPE_SOCKET = 0x0001;
+
+ /** A process buffer with I/O support */
+ const TYPE_PROCESS = 0x0010;
+
+ /**
+ * Perform any initialization needed, using the given $params.
+ *
+ * Parameters will vary depending upon the type of IoBuffer used.
+ *
+ * @param array $params
+ */
+ public function initialize(array $params);
+
+ /**
+ * Set an individual param on the buffer (e.g. switching to SSL).
+ *
+ * @param string $param
+ * @param mixed $value
+ */
+ public function setParam($param, $value);
+
+ /**
+ * Perform any shutdown logic needed.
+ */
+ public function terminate();
+
+ /**
+ * Set an array of string replacements which should be made on data written
+ * to the buffer.
+ *
+ * This could replace LF with CRLF for example.
+ *
+ * @param string[] $replacements
+ */
+ public function setWriteTranslations(array $replacements);
+
+ /**
+ * Get a line of output (including any CRLF).
+ *
+ * The $sequence number comes from any writes and may or may not be used
+ * depending upon the implementation.
+ *
+ * @param int $sequence of last write to scan from
+ *
+ * @return string
+ */
+ public function readLine($sequence);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php
new file mode 100644
index 0000000..e2adc56
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php
@@ -0,0 +1,183 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Redundantly and rotationally uses several Transports when sending.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_LoadBalancedTransport implements Swift_Transport
+{
+ /**
+ * Transports which are deemed useless.
+ *
+ * @var Swift_Transport[]
+ */
+ private $_deadTransports = array();
+
+ /**
+ * The Transports which are used in rotation.
+ *
+ * @var Swift_Transport[]
+ */
+ protected $_transports = array();
+
+ /**
+ * The Transport used in the last successful send operation.
+ *
+ * @var Swift_Transport
+ */
+ protected $_lastUsedTransport = null;
+
+ // needed as __construct is called from elsewhere explicitly
+ public function __construct()
+ {
+ }
+
+ /**
+ * Set $transports to delegate to.
+ *
+ * @param Swift_Transport[] $transports
+ */
+ public function setTransports(array $transports)
+ {
+ $this->_transports = $transports;
+ $this->_deadTransports = array();
+ }
+
+ /**
+ * Get $transports to delegate to.
+ *
+ * @return Swift_Transport[]
+ */
+ public function getTransports()
+ {
+ return array_merge($this->_transports, $this->_deadTransports);
+ }
+
+ /**
+ * Get the Transport used in the last successful send operation.
+ *
+ * @return Swift_Transport
+ */
+ public function getLastUsedTransport()
+ {
+ return $this->_lastUsedTransport;
+ }
+
+ /**
+ * Test if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return count($this->_transports) > 0;
+ }
+
+ /**
+ * Start this Transport mechanism.
+ */
+ public function start()
+ {
+ $this->_transports = array_merge($this->_transports, $this->_deadTransports);
+ }
+
+ /**
+ * Stop this Transport mechanism.
+ */
+ public function stop()
+ {
+ foreach ($this->_transports as $transport) {
+ $transport->stop();
+ }
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients who were accepted for delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $maxTransports = count($this->_transports);
+ $sent = 0;
+ $this->_lastUsedTransport = null;
+
+ for ($i = 0; $i < $maxTransports
+ && $transport = $this->_getNextTransport(); ++$i) {
+ try {
+ if (!$transport->isStarted()) {
+ $transport->start();
+ }
+ if ($sent = $transport->send($message, $failedRecipients)) {
+ $this->_lastUsedTransport = $transport;
+ break;
+ }
+ } catch (Swift_TransportException $e) {
+ $this->_killCurrentTransport();
+ }
+ }
+
+ if (count($this->_transports) == 0) {
+ throw new Swift_TransportException(
+ 'All Transports in LoadBalancedTransport failed, or no Transports available'
+ );
+ }
+
+ return $sent;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ foreach ($this->_transports as $transport) {
+ $transport->registerPlugin($plugin);
+ }
+ }
+
+ /**
+ * Rotates the transport list around and returns the first instance.
+ *
+ * @return Swift_Transport
+ */
+ protected function _getNextTransport()
+ {
+ if ($next = array_shift($this->_transports)) {
+ $this->_transports[] = $next;
+ }
+
+ return $next;
+ }
+
+ /**
+ * Tag the currently used (top of stack) transport as dead/useless.
+ */
+ protected function _killCurrentTransport()
+ {
+ if ($transport = array_pop($this->_transports)) {
+ try {
+ $transport->stop();
+ } catch (Exception $e) {
+ }
+ $this->_deadTransports[] = $transport;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
new file mode 100644
index 0000000..77489ce
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * This interface intercepts calls to the mail() function.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport_MailInvoker
+{
+ /**
+ * Send mail via the mail() function.
+ *
+ * This method takes the same arguments as PHP mail().
+ *
+ * @param string $to
+ * @param string $subject
+ * @param string $body
+ * @param string $headers
+ * @param string $extraParams
+ *
+ * @return bool
+ */
+ public function mail($to, $subject, $body, $headers = null, $extraParams = null);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php
new file mode 100644
index 0000000..48ef4a7
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php
@@ -0,0 +1,297 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Sends Messages using the mail() function.
+ *
+ * It is advised that users do not use this transport if at all possible
+ * since a number of plugin features cannot be used in conjunction with this
+ * transport due to the internal interface in PHP itself.
+ *
+ * The level of error reporting with this transport is incredibly weak, again
+ * due to limitations of PHP's internal mail() function. You'll get an
+ * all-or-nothing result from sending.
+ *
+ * @author Chris Corbyn
+ *
+ * @deprecated since 5.4.5 (to be removed in 6.0)
+ */
+class Swift_Transport_MailTransport implements Swift_Transport
+{
+ /** Additional parameters to pass to mail() */
+ private $_extraParams = '-f%s';
+
+ /** The event dispatcher from the plugin API */
+ private $_eventDispatcher;
+
+ /** An invoker that calls the mail() function */
+ private $_invoker;
+
+ /**
+ * Create a new MailTransport with the $log.
+ *
+ * @param Swift_Transport_MailInvoker $invoker
+ * @param Swift_Events_EventDispatcher $eventDispatcher
+ */
+ public function __construct(Swift_Transport_MailInvoker $invoker, Swift_Events_EventDispatcher $eventDispatcher)
+ {
+ @trigger_error(sprintf('The %s class is deprecated since version 5.4.5 and will be removed in 6.0. Use the Sendmail or SMTP transport instead.', __CLASS__), E_USER_DEPRECATED);
+
+ $this->_invoker = $invoker;
+ $this->_eventDispatcher = $eventDispatcher;
+ }
+
+ /**
+ * Not used.
+ */
+ public function isStarted()
+ {
+ return false;
+ }
+
+ /**
+ * Not used.
+ */
+ public function start()
+ {
+ }
+
+ /**
+ * Not used.
+ */
+ public function stop()
+ {
+ }
+
+ /**
+ * Set the additional parameters used on the mail() function.
+ *
+ * This string is formatted for sprintf() where %s is the sender address.
+ *
+ * @param string $params
+ *
+ * @return $this
+ */
+ public function setExtraParams($params)
+ {
+ $this->_extraParams = $params;
+
+ return $this;
+ }
+
+ /**
+ * Get the additional parameters used on the mail() function.
+ *
+ * This string is formatted for sprintf() where %s is the sender address.
+ *
+ * @return string
+ */
+ public function getExtraParams()
+ {
+ return $this->_extraParams;
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients who were accepted for delivery.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $failedRecipients = (array) $failedRecipients;
+
+ if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+ if ($evt->bubbleCancelled()) {
+ return 0;
+ }
+ }
+
+ $count = (
+ count((array) $message->getTo())
+ + count((array) $message->getCc())
+ + count((array) $message->getBcc())
+ );
+
+ $toHeader = $message->getHeaders()->get('To');
+ $subjectHeader = $message->getHeaders()->get('Subject');
+
+ if (0 === $count) {
+ $this->_throwException(new Swift_TransportException('Cannot send message without a recipient'));
+ }
+ $to = $toHeader ? $toHeader->getFieldBody() : '';
+ $subject = $subjectHeader ? $subjectHeader->getFieldBody() : '';
+
+ $reversePath = $this->_getReversePath($message);
+
+ // Remove headers that would otherwise be duplicated
+ $message->getHeaders()->remove('To');
+ $message->getHeaders()->remove('Subject');
+
+ $messageStr = $message->toString();
+
+ if ($toHeader) {
+ $message->getHeaders()->set($toHeader);
+ }
+ $message->getHeaders()->set($subjectHeader);
+
+ // Separate headers from body
+ if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) {
+ $headers = substr($messageStr, 0, $endHeaders)."\r\n"; //Keep last EOL
+ $body = substr($messageStr, $endHeaders + 4);
+ } else {
+ $headers = $messageStr."\r\n";
+ $body = '';
+ }
+
+ unset($messageStr);
+
+ if ("\r\n" != PHP_EOL) {
+ // Non-windows (not using SMTP)
+ $headers = str_replace("\r\n", PHP_EOL, $headers);
+ $subject = str_replace("\r\n", PHP_EOL, $subject);
+ $body = str_replace("\r\n", PHP_EOL, $body);
+ $to = str_replace("\r\n", PHP_EOL, $to);
+ } else {
+ // Windows, using SMTP
+ $headers = str_replace("\r\n.", "\r\n..", $headers);
+ $subject = str_replace("\r\n.", "\r\n..", $subject);
+ $body = str_replace("\r\n.", "\r\n..", $body);
+ $to = str_replace("\r\n.", "\r\n..", $to);
+ }
+
+ if ($this->_invoker->mail($to, $subject, $body, $headers, $this->_formatExtraParams($this->_extraParams, $reversePath))) {
+ if ($evt) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
+ $evt->setFailedRecipients($failedRecipients);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+ } else {
+ $failedRecipients = array_merge(
+ $failedRecipients,
+ array_keys((array) $message->getTo()),
+ array_keys((array) $message->getCc()),
+ array_keys((array) $message->getBcc())
+ );
+
+ if ($evt) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
+ $evt->setFailedRecipients($failedRecipients);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ $message->generateId();
+
+ $count = 0;
+ }
+
+ return $count;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ $this->_eventDispatcher->bindEventListener($plugin);
+ }
+
+ /** Throw a TransportException, first sending it to any listeners */
+ protected function _throwException(Swift_TransportException $e)
+ {
+ if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
+ if (!$evt->bubbleCancelled()) {
+ throw $e;
+ }
+ } else {
+ throw $e;
+ }
+ }
+
+ /** Determine the best-use reverse path for this message */
+ private function _getReversePath(Swift_Mime_Message $message)
+ {
+ $return = $message->getReturnPath();
+ $sender = $message->getSender();
+ $from = $message->getFrom();
+ $path = null;
+ if (!empty($return)) {
+ $path = $return;
+ } elseif (!empty($sender)) {
+ $keys = array_keys($sender);
+ $path = array_shift($keys);
+ } elseif (!empty($from)) {
+ $keys = array_keys($from);
+ $path = array_shift($keys);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Fix CVE-2016-10074 by disallowing potentially unsafe shell characters.
+ *
+ * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
+ *
+ * @param string $string The string to be validated
+ *
+ * @return bool
+ */
+ private function _isShellSafe($string)
+ {
+ // Future-proof
+ if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) {
+ return false;
+ }
+
+ $length = strlen($string);
+ for ($i = 0; $i < $length; ++$i) {
+ $c = $string[$i];
+ // All other characters have a special meaning in at least one common shell, including = and +.
+ // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
+ // Note that this does permit non-Latin alphanumeric characters based on the current locale.
+ if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Return php mail extra params to use for invoker->mail.
+ *
+ * @param $extraParams
+ * @param $reversePath
+ *
+ * @return string|null
+ */
+ private function _formatExtraParams($extraParams, $reversePath)
+ {
+ if (false !== strpos($extraParams, '-f%s')) {
+ if (empty($reversePath) || false === $this->_isShellSafe($reversePath)) {
+ $extraParams = str_replace('-f%s', '', $extraParams);
+ } else {
+ $extraParams = sprintf($extraParams, $reversePath);
+ }
+ }
+
+ return !empty($extraParams) ? $extraParams : null;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php
new file mode 100644
index 0000000..ad20e0e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Pretends messages have been sent, but just ignores them.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_Transport_NullTransport implements Swift_Transport
+{
+ /** The event dispatcher from the plugin API */
+ private $_eventDispatcher;
+
+ /**
+ * Constructor.
+ */
+ public function __construct(Swift_Events_EventDispatcher $eventDispatcher)
+ {
+ $this->_eventDispatcher = $eventDispatcher;
+ }
+
+ /**
+ * Tests if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return true;
+ }
+
+ /**
+ * Starts this Transport mechanism.
+ */
+ public function start()
+ {
+ }
+
+ /**
+ * Stops this Transport mechanism.
+ */
+ public function stop()
+ {
+ }
+
+ /**
+ * Sends the given message.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of sent emails
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+ if ($evt->bubbleCancelled()) {
+ return 0;
+ }
+ }
+
+ if ($evt) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ $count = (
+ count((array) $message->getTo())
+ + count((array) $message->getCc())
+ + count((array) $message->getBcc())
+ );
+
+ return $count;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ $this->_eventDispatcher->bindEventListener($plugin);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php
new file mode 100644
index 0000000..6430d5f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php
@@ -0,0 +1,160 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
+ *
+ * Supported modes are -bs and -t, with any additional flags desired.
+ * It is advised to use -bs mode since error reporting with -t mode is not
+ * possible.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_SendmailTransport extends Swift_Transport_AbstractSmtpTransport
+{
+ /**
+ * Connection buffer parameters.
+ *
+ * @var array
+ */
+ private $_params = array(
+ 'timeout' => 30,
+ 'blocking' => 1,
+ 'command' => '/usr/sbin/sendmail -bs',
+ 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS,
+ );
+
+ /**
+ * Create a new SendmailTransport with $buf for I/O.
+ *
+ * @param Swift_Transport_IoBuffer $buf
+ * @param Swift_Events_EventDispatcher $dispatcher
+ */
+ public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher)
+ {
+ parent::__construct($buf, $dispatcher);
+ }
+
+ /**
+ * Start the standalone SMTP session if running in -bs mode.
+ */
+ public function start()
+ {
+ if (false !== strpos($this->getCommand(), ' -bs')) {
+ parent::start();
+ }
+ }
+
+ /**
+ * Set the command to invoke.
+ *
+ * If using -t mode you are strongly advised to include -oi or -i in the flags.
+ * For example: /usr/sbin/sendmail -oi -t
+ * Swift will append a -f<sender> flag if one is not present.
+ *
+ * The recommended mode is "-bs" since it is interactive and failure notifications
+ * are hence possible.
+ *
+ * @param string $command
+ *
+ * @return $this
+ */
+ public function setCommand($command)
+ {
+ $this->_params['command'] = $command;
+
+ return $this;
+ }
+
+ /**
+ * Get the sendmail command which will be invoked.
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->_params['command'];
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ *
+ * The return value is the number of recipients who were accepted for delivery.
+ * NOTE: If using 'sendmail -t' you will not be aware of any failures until
+ * they bounce (i.e. send() will always return 100% success).
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ $failedRecipients = (array) $failedRecipients;
+ $command = $this->getCommand();
+ $buffer = $this->getBuffer();
+ $count = 0;
+
+ if (false !== strpos($command, ' -t')) {
+ if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+ if ($evt->bubbleCancelled()) {
+ return 0;
+ }
+ }
+
+ if (false === strpos($command, ' -f')) {
+ $command .= ' -f'.escapeshellarg($this->_getReversePath($message));
+ }
+
+ $buffer->initialize(array_merge($this->_params, array('command' => $command)));
+
+ if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) {
+ $buffer->setWriteTranslations(array("\r\n" => "\n", "\n." => "\n.."));
+ } else {
+ $buffer->setWriteTranslations(array("\r\n" => "\n"));
+ }
+
+ $count = count((array) $message->getTo())
+ + count((array) $message->getCc())
+ + count((array) $message->getBcc())
+ ;
+ $message->toByteStream($buffer);
+ $buffer->flushBuffers();
+ $buffer->setWriteTranslations(array());
+ $buffer->terminate();
+
+ if ($evt) {
+ $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
+ $evt->setFailedRecipients($failedRecipients);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ $message->generateId();
+ } elseif (false !== strpos($command, ' -bs')) {
+ $count = parent::send($message, $failedRecipients);
+ } else {
+ $this->_throwException(new Swift_TransportException(
+ 'Unsupported sendmail command flags ['.$command.']. '.
+ 'Must be one of "-bs" or "-t" but can include additional flags.'
+ ));
+ }
+
+ return $count;
+ }
+
+ /** Get the params to initialize the buffer */
+ protected function _getBufferParams()
+ {
+ return $this->_params;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
new file mode 100644
index 0000000..4cab66b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * This is the implementation class for {@link Swift_Transport_MailInvoker}.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_SimpleMailInvoker implements Swift_Transport_MailInvoker
+{
+ /**
+ * Send mail via the mail() function.
+ *
+ * This method takes the same arguments as PHP mail().
+ *
+ * @param string $to
+ * @param string $subject
+ * @param string $body
+ * @param string $headers
+ * @param string $extraParams
+ *
+ * @return bool
+ */
+ public function mail($to, $subject, $body, $headers = null, $extraParams = null)
+ {
+ if (!ini_get('safe_mode')) {
+ return @mail($to, $subject, $body, $headers, $extraParams);
+ }
+
+ return @mail($to, $subject, $body, $headers);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php
new file mode 100644
index 0000000..90e913f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Wraps an IoBuffer to send/receive SMTP commands/responses.
+ *
+ * @author Chris Corbyn
+ */
+interface Swift_Transport_SmtpAgent
+{
+ /**
+ * Get the IoBuffer where read/writes are occurring.
+ *
+ * @return Swift_Transport_IoBuffer
+ */
+ public function getBuffer();
+
+ /**
+ * Run a command against the buffer, expecting the given response codes.
+ *
+ * If no response codes are given, the response will not be validated.
+ * If codes are given, an exception will be thrown on an invalid response.
+ *
+ * @param string $command
+ * @param int[] $codes
+ * @param string[] $failures An array of failures by-reference
+ */
+ public function executeCommand($command, $codes = array(), &$failures = null);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.php
new file mode 100644
index 0000000..e4b87f3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.php
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Stores Messages in a queue.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_Transport_SpoolTransport implements Swift_Transport
+{
+ /** The spool instance */
+ private $_spool;
+
+ /** The event dispatcher from the plugin API */
+ private $_eventDispatcher;
+
+ /**
+ * Constructor.
+ */
+ public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null)
+ {
+ $this->_eventDispatcher = $eventDispatcher;
+ $this->_spool = $spool;
+ }
+
+ /**
+ * Sets the spool object.
+ *
+ * @param Swift_Spool $spool
+ *
+ * @return $this
+ */
+ public function setSpool(Swift_Spool $spool)
+ {
+ $this->_spool = $spool;
+
+ return $this;
+ }
+
+ /**
+ * Get the spool object.
+ *
+ * @return Swift_Spool
+ */
+ public function getSpool()
+ {
+ return $this->_spool;
+ }
+
+ /**
+ * Tests if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return true;
+ }
+
+ /**
+ * Starts this Transport mechanism.
+ */
+ public function start()
+ {
+ }
+
+ /**
+ * Stops this Transport mechanism.
+ */
+ public function stop()
+ {
+ }
+
+ /**
+ * Sends the given message.
+ *
+ * @param Swift_Mime_Message $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int The number of sent e-mail's
+ */
+ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
+ {
+ if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+ $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+ if ($evt->bubbleCancelled()) {
+ return 0;
+ }
+ }
+
+ $success = $this->_spool->queueMessage($message);
+
+ if ($evt) {
+ $evt->setResult($success ? Swift_Events_SendEvent::RESULT_SPOOLED : Swift_Events_SendEvent::RESULT_FAILED);
+ $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ return 1;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin)
+ {
+ $this->_eventDispatcher->bindEventListener($plugin);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
new file mode 100644
index 0000000..3a9fe76
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
@@ -0,0 +1,334 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * A generic IoBuffer implementation supporting remote sockets and local processes.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_Transport_StreamBuffer extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_Transport_IoBuffer
+{
+ /** A primary socket */
+ private $_stream;
+
+ /** The input stream */
+ private $_in;
+
+ /** The output stream */
+ private $_out;
+
+ /** Buffer initialization parameters */
+ private $_params = array();
+
+ /** The ReplacementFilterFactory */
+ private $_replacementFactory;
+
+ /** Translations performed on data being streamed into the buffer */
+ private $_translations = array();
+
+ /**
+ * Create a new StreamBuffer using $replacementFactory for transformations.
+ *
+ * @param Swift_ReplacementFilterFactory $replacementFactory
+ */
+ public function __construct(Swift_ReplacementFilterFactory $replacementFactory)
+ {
+ $this->_replacementFactory = $replacementFactory;
+ }
+
+ /**
+ * Perform any initialization needed, using the given $params.
+ *
+ * Parameters will vary depending upon the type of IoBuffer used.
+ *
+ * @param array $params
+ */
+ public function initialize(array $params)
+ {
+ $this->_params = $params;
+ switch ($params['type']) {
+ case self::TYPE_PROCESS:
+ $this->_establishProcessConnection();
+ break;
+ case self::TYPE_SOCKET:
+ default:
+ $this->_establishSocketConnection();
+ break;
+ }
+ }
+
+ /**
+ * Set an individual param on the buffer (e.g. switching to SSL).
+ *
+ * @param string $param
+ * @param mixed $value
+ */
+ public function setParam($param, $value)
+ {
+ if (isset($this->_stream)) {
+ switch ($param) {
+ case 'timeout':
+ if ($this->_stream) {
+ stream_set_timeout($this->_stream, $value);
+ }
+ break;
+
+ case 'blocking':
+ if ($this->_stream) {
+ stream_set_blocking($this->_stream, 1);
+ }
+ }
+ }
+ $this->_params[$param] = $value;
+ }
+
+ public function startTLS()
+ {
+ // STREAM_CRYPTO_METHOD_TLS_CLIENT only allow tls1.0 connections (some php versions)
+ // To support modern tls we allow explicit tls1.0, tls1.1, tls1.2
+ // Ssl3 and older are not allowed because they are vulnerable
+ // @TODO make tls arguments configurable
+ $cryptoType = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+ if (PHP_VERSION_ID >= 50600) {
+ $cryptoType = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ }
+
+ return stream_socket_enable_crypto($this->_stream, true, $cryptoType);
+ }
+
+ /**
+ * Perform any shutdown logic needed.
+ */
+ public function terminate()
+ {
+ if (isset($this->_stream)) {
+ switch ($this->_params['type']) {
+ case self::TYPE_PROCESS:
+ fclose($this->_in);
+ fclose($this->_out);
+ proc_close($this->_stream);
+ break;
+ case self::TYPE_SOCKET:
+ default:
+ fclose($this->_stream);
+ break;
+ }
+ }
+ $this->_stream = null;
+ $this->_out = null;
+ $this->_in = null;
+ }
+
+ /**
+ * Set an array of string replacements which should be made on data written
+ * to the buffer.
+ *
+ * This could replace LF with CRLF for example.
+ *
+ * @param string[] $replacements
+ */
+ public function setWriteTranslations(array $replacements)
+ {
+ foreach ($this->_translations as $search => $replace) {
+ if (!isset($replacements[$search])) {
+ $this->removeFilter($search);
+ unset($this->_translations[$search]);
+ }
+ }
+
+ foreach ($replacements as $search => $replace) {
+ if (!isset($this->_translations[$search])) {
+ $this->addFilter(
+ $this->_replacementFactory->createFilter($search, $replace), $search
+ );
+ $this->_translations[$search] = true;
+ }
+ }
+ }
+
+ /**
+ * Get a line of output (including any CRLF).
+ *
+ * The $sequence number comes from any writes and may or may not be used
+ * depending upon the implementation.
+ *
+ * @param int $sequence of last write to scan from
+ *
+ * @throws Swift_IoException
+ *
+ * @return string
+ */
+ public function readLine($sequence)
+ {
+ if (isset($this->_out) && !feof($this->_out)) {
+ $line = fgets($this->_out);
+ if (strlen($line) == 0) {
+ $metas = stream_get_meta_data($this->_out);
+ if ($metas['timed_out']) {
+ throw new Swift_IoException(
+ 'Connection to '.
+ $this->_getReadConnectionDescription().
+ ' Timed Out'
+ );
+ }
+ }
+
+ return $line;
+ }
+ }
+
+ /**
+ * Reads $length bytes from the stream into a string and moves the pointer
+ * through the stream by $length.
+ *
+ * If less bytes exist than are requested the remaining bytes are given instead.
+ * If no bytes are remaining at all, boolean false is returned.
+ *
+ * @param int $length
+ *
+ * @throws Swift_IoException
+ *
+ * @return string|bool
+ */
+ public function read($length)
+ {
+ if (isset($this->_out) && !feof($this->_out)) {
+ $ret = fread($this->_out, $length);
+ if (strlen($ret) == 0) {
+ $metas = stream_get_meta_data($this->_out);
+ if ($metas['timed_out']) {
+ throw new Swift_IoException(
+ 'Connection to '.
+ $this->_getReadConnectionDescription().
+ ' Timed Out'
+ );
+ }
+ }
+
+ return $ret;
+ }
+ }
+
+ /** Not implemented */
+ public function setReadPointer($byteOffset)
+ {
+ }
+
+ /** Flush the stream contents */
+ protected function _flush()
+ {
+ if (isset($this->_in)) {
+ fflush($this->_in);
+ }
+ }
+
+ /** Write this bytes to the stream */
+ protected function _commit($bytes)
+ {
+ if (isset($this->_in)) {
+ $bytesToWrite = strlen($bytes);
+ $totalBytesWritten = 0;
+
+ while ($totalBytesWritten < $bytesToWrite) {
+ $bytesWritten = fwrite($this->_in, substr($bytes, $totalBytesWritten));
+ if (false === $bytesWritten || 0 === $bytesWritten) {
+ break;
+ }
+
+ $totalBytesWritten += $bytesWritten;
+ }
+
+ if ($totalBytesWritten > 0) {
+ return ++$this->_sequence;
+ }
+ }
+ }
+
+ /**
+ * Establishes a connection to a remote server.
+ */
+ private function _establishSocketConnection()
+ {
+ $host = $this->_params['host'];
+ if (!empty($this->_params['protocol'])) {
+ $host = $this->_params['protocol'].'://'.$host;
+ }
+ $timeout = 15;
+ if (!empty($this->_params['timeout'])) {
+ $timeout = $this->_params['timeout'];
+ }
+ $options = array();
+ if (!empty($this->_params['sourceIp'])) {
+ $options['socket']['bindto'] = $this->_params['sourceIp'].':0';
+ }
+ if (isset($this->_params['stream_context_options'])) {
+ $options = array_merge($options, $this->_params['stream_context_options']);
+ }
+ $streamContext = stream_context_create($options);
+ $this->_stream = @stream_socket_client($host.':'.$this->_params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext);
+ if (false === $this->_stream) {
+ throw new Swift_TransportException(
+ 'Connection could not be established with host '.$this->_params['host'].
+ ' ['.$errstr.' #'.$errno.']'
+ );
+ }
+ if (!empty($this->_params['blocking'])) {
+ stream_set_blocking($this->_stream, 1);
+ } else {
+ stream_set_blocking($this->_stream, 0);
+ }
+ stream_set_timeout($this->_stream, $timeout);
+ $this->_in = &$this->_stream;
+ $this->_out = &$this->_stream;
+ }
+
+ /**
+ * Opens a process for input/output.
+ */
+ private function _establishProcessConnection()
+ {
+ $command = $this->_params['command'];
+ $descriptorSpec = array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w'),
+ 2 => array('pipe', 'w'),
+ );
+ $pipes = array();
+ $this->_stream = proc_open($command, $descriptorSpec, $pipes);
+ stream_set_blocking($pipes[2], 0);
+ if ($err = stream_get_contents($pipes[2])) {
+ throw new Swift_TransportException(
+ 'Process could not be started ['.$err.']'
+ );
+ }
+ $this->_in = &$pipes[0];
+ $this->_out = &$pipes[1];
+ }
+
+ private function _getReadConnectionDescription()
+ {
+ switch ($this->_params['type']) {
+ case self::TYPE_PROCESS:
+ return 'Process '.$this->_params['command'];
+ break;
+
+ case self::TYPE_SOCKET:
+ default:
+ $host = $this->_params['host'];
+ if (!empty($this->_params['protocol'])) {
+ $host = $this->_params['protocol'].'://'.$host;
+ }
+ $host .= ':'.$this->_params['port'];
+
+ return $host;
+ break;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php
new file mode 100644
index 0000000..4ae2412
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * TransportException thrown when an error occurs in the Transport subsystem.
+ *
+ * @author Chris Corbyn
+ */
+class Swift_TransportException extends Swift_IoException
+{
+ /**
+ * Create a new TransportException with $message.
+ *
+ * @param string $message
+ * @param int $code
+ * @param Exception $previous
+ */
+ public function __construct($message, $code = 0, Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Validate.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Validate.php
new file mode 100644
index 0000000..e16c212
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Validate.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Utility Class allowing users to simply check expressions again Swift Grammar.
+ *
+ * @author Xavier De Cock <xdecock@gmail.com>
+ */
+class Swift_Validate
+{
+ /**
+ * Grammar Object.
+ *
+ * @var Swift_Mime_Grammar
+ */
+ private static $grammar = null;
+
+ /**
+ * Checks if an e-mail address matches the current grammars.
+ *
+ * @param string $email
+ *
+ * @return bool
+ */
+ public static function email($email)
+ {
+ if (self::$grammar === null) {
+ self::$grammar = Swift_DependencyContainer::getInstance()
+ ->lookup('mime.grammar');
+ }
+
+ return (bool) preg_match(
+ '/^'.self::$grammar->getDefinition('addr-spec').'$/D',
+ $email
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php
new file mode 100644
index 0000000..6023448
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php
@@ -0,0 +1,23 @@
+<?php
+
+Swift_DependencyContainer::getInstance()
+ ->register('cache')
+ ->asAliasOf('cache.array')
+
+ ->register('tempdir')
+ ->asValue('/tmp')
+
+ ->register('cache.null')
+ ->asSharedInstanceOf('Swift_KeyCache_NullKeyCache')
+
+ ->register('cache.array')
+ ->asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache')
+ ->withDependencies(array('cache.inputstream'))
+
+ ->register('cache.disk')
+ ->asSharedInstanceOf('Swift_KeyCache_DiskKeyCache')
+ ->withDependencies(array('cache.inputstream', 'tempdir'))
+
+ ->register('cache.inputstream')
+ ->asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream')
+;
diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php
new file mode 100644
index 0000000..64d69d2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php
@@ -0,0 +1,9 @@
+<?php
+
+Swift_DependencyContainer::getInstance()
+ ->register('message.message')
+ ->asNewInstanceOf('Swift_Message')
+
+ ->register('message.mimepart')
+ ->asNewInstanceOf('Swift_MimePart')
+;
diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php
new file mode 100644
index 0000000..d575e4f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php
@@ -0,0 +1,123 @@
+<?php
+
+require __DIR__.'/../mime_types.php';
+
+Swift_DependencyContainer::getInstance()
+ ->register('properties.charset')
+ ->asValue('utf-8')
+
+ ->register('mime.grammar')
+ ->asSharedInstanceOf('Swift_Mime_Grammar')
+
+ ->register('mime.message')
+ ->asNewInstanceOf('Swift_Mime_SimpleMessage')
+ ->withDependencies(array(
+ 'mime.headerset',
+ 'mime.qpcontentencoder',
+ 'cache',
+ 'mime.grammar',
+ 'properties.charset',
+ ))
+
+ ->register('mime.part')
+ ->asNewInstanceOf('Swift_Mime_MimePart')
+ ->withDependencies(array(
+ 'mime.headerset',
+ 'mime.qpcontentencoder',
+ 'cache',
+ 'mime.grammar',
+ 'properties.charset',
+ ))
+
+ ->register('mime.attachment')
+ ->asNewInstanceOf('Swift_Mime_Attachment')
+ ->withDependencies(array(
+ 'mime.headerset',
+ 'mime.base64contentencoder',
+ 'cache',
+ 'mime.grammar',
+ ))
+ ->addConstructorValue($swift_mime_types)
+
+ ->register('mime.embeddedfile')
+ ->asNewInstanceOf('Swift_Mime_EmbeddedFile')
+ ->withDependencies(array(
+ 'mime.headerset',
+ 'mime.base64contentencoder',
+ 'cache',
+ 'mime.grammar',
+ ))
+ ->addConstructorValue($swift_mime_types)
+
+ ->register('mime.headerfactory')
+ ->asNewInstanceOf('Swift_Mime_SimpleHeaderFactory')
+ ->withDependencies(array(
+ 'mime.qpheaderencoder',
+ 'mime.rfc2231encoder',
+ 'mime.grammar',
+ 'properties.charset',
+ ))
+
+ ->register('mime.headerset')
+ ->asNewInstanceOf('Swift_Mime_SimpleHeaderSet')
+ ->withDependencies(array('mime.headerfactory', 'properties.charset'))
+
+ ->register('mime.qpheaderencoder')
+ ->asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder')
+ ->withDependencies(array('mime.charstream'))
+
+ ->register('mime.base64headerencoder')
+ ->asNewInstanceOf('Swift_Mime_HeaderEncoder_Base64HeaderEncoder')
+ ->withDependencies(array('mime.charstream'))
+
+ ->register('mime.charstream')
+ ->asNewInstanceOf('Swift_CharacterStream_NgCharacterStream')
+ ->withDependencies(array('mime.characterreaderfactory', 'properties.charset'))
+
+ ->register('mime.bytecanonicalizer')
+ ->asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter')
+ ->addConstructorValue(array(array(0x0D, 0x0A), array(0x0D), array(0x0A)))
+ ->addConstructorValue(array(array(0x0A), array(0x0A), array(0x0D, 0x0A)))
+
+ ->register('mime.characterreaderfactory')
+ ->asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory')
+
+ ->register('mime.safeqpcontentencoder')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder')
+ ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer'))
+
+ ->register('mime.rawcontentencoder')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_RawContentEncoder')
+
+ ->register('mime.nativeqpcontentencoder')
+ ->withDependencies(array('properties.charset'))
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_NativeQpContentEncoder')
+
+ ->register('mime.qpcontentencoderproxy')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoderProxy')
+ ->withDependencies(array('mime.safeqpcontentencoder', 'mime.nativeqpcontentencoder', 'properties.charset'))
+
+ ->register('mime.7bitcontentencoder')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder')
+ ->addConstructorValue('7bit')
+ ->addConstructorValue(true)
+
+ ->register('mime.8bitcontentencoder')
+ ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder')
+ ->addConstructorValue('8bit')
+ ->addConstructorValue(true)
+
+ ->register('mime.base64contentencoder')
+ ->asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder')
+
+ ->register('mime.rfc2231encoder')
+ ->asNewInstanceOf('Swift_Encoder_Rfc2231Encoder')
+ ->withDependencies(array('mime.charstream'))
+
+ // As of PHP 5.4.7, the quoted_printable_encode() function behaves correctly.
+ // see https://github.com/php/php-src/commit/18bb426587d62f93c54c40bf8535eb8416603629
+ ->register('mime.qpcontentencoder')
+ ->asAliasOf(PHP_VERSION_ID >= 50407 ? 'mime.qpcontentencoderproxy' : 'mime.safeqpcontentencoder')
+;
+
+unset($swift_mime_types);
diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php
new file mode 100644
index 0000000..77e432c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php
@@ -0,0 +1,76 @@
+<?php
+
+Swift_DependencyContainer::getInstance()
+ ->register('transport.smtp')
+ ->asNewInstanceOf('Swift_Transport_EsmtpTransport')
+ ->withDependencies(array(
+ 'transport.buffer',
+ array('transport.authhandler'),
+ 'transport.eventdispatcher',
+ ))
+
+ ->register('transport.sendmail')
+ ->asNewInstanceOf('Swift_Transport_SendmailTransport')
+ ->withDependencies(array(
+ 'transport.buffer',
+ 'transport.eventdispatcher',
+ ))
+
+ ->register('transport.mail')
+ ->asNewInstanceOf('Swift_Transport_MailTransport')
+ ->withDependencies(array('transport.mailinvoker', 'transport.eventdispatcher'))
+
+ ->register('transport.loadbalanced')
+ ->asNewInstanceOf('Swift_Transport_LoadBalancedTransport')
+
+ ->register('transport.failover')
+ ->asNewInstanceOf('Swift_Transport_FailoverTransport')
+
+ ->register('transport.spool')
+ ->asNewInstanceOf('Swift_Transport_SpoolTransport')
+ ->withDependencies(array('transport.eventdispatcher'))
+
+ ->register('transport.null')
+ ->asNewInstanceOf('Swift_Transport_NullTransport')
+ ->withDependencies(array('transport.eventdispatcher'))
+
+ ->register('transport.mailinvoker')
+ ->asSharedInstanceOf('Swift_Transport_SimpleMailInvoker')
+
+ ->register('transport.buffer')
+ ->asNewInstanceOf('Swift_Transport_StreamBuffer')
+ ->withDependencies(array('transport.replacementfactory'))
+
+ ->register('transport.authhandler')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler')
+ ->withDependencies(array(
+ array(
+ 'transport.crammd5auth',
+ 'transport.loginauth',
+ 'transport.plainauth',
+ 'transport.ntlmauth',
+ 'transport.xoauth2auth',
+ ),
+ ))
+
+ ->register('transport.crammd5auth')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator')
+
+ ->register('transport.loginauth')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator')
+
+ ->register('transport.plainauth')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator')
+
+ ->register('transport.xoauth2auth')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_XOAuth2Authenticator')
+
+ ->register('transport.ntlmauth')
+ ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_NTLMAuthenticator')
+
+ ->register('transport.eventdispatcher')
+ ->asNewInstanceOf('Swift_Events_SimpleEventDispatcher')
+
+ ->register('transport.replacementfactory')
+ ->asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory')
+;
diff --git a/vendor/swiftmailer/swiftmailer/lib/mime_types.php b/vendor/swiftmailer/swiftmailer/lib/mime_types.php
new file mode 100644
index 0000000..b42c1cc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/mime_types.php
@@ -0,0 +1,1007 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * autogenerated using https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+ * and https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml
+ */
+
+/*
+ * List of MIME type automatically detected in Swift Mailer.
+ */
+
+// You may add or take away what you like (lowercase required)
+
+$swift_mime_types = array(
+ '3dml' => 'text/vnd.in3d.3dml',
+ '3ds' => 'image/x-3ds',
+ '3g2' => 'video/3gpp2',
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aab' => 'application/x-authorware-bin',
+ 'aac' => 'audio/x-aac',
+ 'aam' => 'application/x-authorware-map',
+ 'aas' => 'application/x-authorware-seg',
+ 'abw' => 'application/x-abiword',
+ 'ac' => 'application/pkix-attr-cert',
+ 'acc' => 'application/vnd.americandynamics.acc',
+ 'ace' => 'application/x-ace-compressed',
+ 'acu' => 'application/vnd.acucobol',
+ 'acutc' => 'application/vnd.acucorp',
+ 'adp' => 'audio/adpcm',
+ 'aep' => 'application/vnd.audiograph',
+ 'afm' => 'application/x-font-type1',
+ 'afp' => 'application/vnd.ibm.modcap',
+ 'ahead' => 'application/vnd.ahead.space',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
+ 'ait' => 'application/vnd.dvb.ait',
+ 'ami' => 'application/vnd.amiga.ami',
+ 'apk' => 'application/vnd.android.package-archive',
+ 'appcache' => 'text/cache-manifest',
+ 'apr' => 'application/vnd.lotus-approach',
+ 'aps' => 'application/postscript',
+ 'arc' => 'application/x-freearc',
+ 'asc' => 'application/pgp-signature',
+ 'asf' => 'video/x-ms-asf',
+ 'asm' => 'text/x-asm',
+ 'aso' => 'application/vnd.accpac.simply.aso',
+ 'asx' => 'video/x-ms-asf',
+ 'atc' => 'application/vnd.acucorp',
+ 'atom' => 'application/atom+xml',
+ 'atomcat' => 'application/atomcat+xml',
+ 'atomsvc' => 'application/atomsvc+xml',
+ 'atx' => 'application/vnd.antix.game-component',
+ 'au' => 'audio/basic',
+ 'avi' => 'video/x-msvideo',
+ 'aw' => 'application/applixware',
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
+ 'azw' => 'application/vnd.amazon.ebook',
+ 'bat' => 'application/x-msdownload',
+ 'bcpio' => 'application/x-bcpio',
+ 'bdf' => 'application/x-font-bdf',
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
+ 'bed' => 'application/vnd.realvnc.bed',
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
+ 'bin' => 'application/octet-stream',
+ 'blb' => 'application/x-blorb',
+ 'blorb' => 'application/x-blorb',
+ 'bmi' => 'application/vnd.bmi',
+ 'bmp' => 'image/bmp',
+ 'book' => 'application/vnd.framemaker',
+ 'box' => 'application/vnd.previewsystems.box',
+ 'boz' => 'application/x-bzip2',
+ 'bpk' => 'application/octet-stream',
+ 'btif' => 'image/prs.btif',
+ 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2',
+ 'c' => 'text/x-c',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'c4d' => 'application/vnd.clonk.c4group',
+ 'c4f' => 'application/vnd.clonk.c4group',
+ 'c4g' => 'application/vnd.clonk.c4group',
+ 'c4p' => 'application/vnd.clonk.c4group',
+ 'c4u' => 'application/vnd.clonk.c4group',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+ 'caf' => 'audio/x-caf',
+ 'cap' => 'application/vnd.tcpdump.pcap',
+ 'car' => 'application/vnd.curl.car',
+ 'cat' => 'application/vnd.ms-pki.seccat',
+ 'cb7' => 'application/x-cbr',
+ 'cba' => 'application/x-cbr',
+ 'cbr' => 'application/x-cbr',
+ 'cbt' => 'application/x-cbr',
+ 'cbz' => 'application/x-cbr',
+ 'cc' => 'text/x-c',
+ 'cct' => 'application/x-director',
+ 'ccxml' => 'application/ccxml+xml',
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
+ 'cdf' => 'application/x-netcdf',
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
+ 'cdmia' => 'application/cdmi-capability',
+ 'cdmic' => 'application/cdmi-container',
+ 'cdmid' => 'application/cdmi-domain',
+ 'cdmio' => 'application/cdmi-object',
+ 'cdmiq' => 'application/cdmi-queue',
+ 'cdx' => 'chemical/x-cdx',
+ 'cdxml' => 'application/vnd.chemdraw+xml',
+ 'cdy' => 'application/vnd.cinderella',
+ 'cer' => 'application/pkix-cert',
+ 'cfs' => 'application/x-cfs-compressed',
+ 'cgm' => 'image/cgm',
+ 'chat' => 'application/x-chat',
+ 'chm' => 'application/vnd.ms-htmlhelp',
+ 'chrt' => 'application/vnd.kde.kchart',
+ 'cif' => 'chemical/x-cif',
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
+ 'cil' => 'application/vnd.ms-artgalry',
+ 'cla' => 'application/vnd.claymore',
+ 'class' => 'application/java-vm',
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
+ 'clkp' => 'application/vnd.crick.clicker.palette',
+ 'clkt' => 'application/vnd.crick.clicker.template',
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
+ 'clkx' => 'application/vnd.crick.clicker',
+ 'clp' => 'application/x-msclip',
+ 'cmc' => 'application/vnd.cosmocaller',
+ 'cmdf' => 'chemical/x-cmdf',
+ 'cml' => 'chemical/x-cml',
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
+ 'cmx' => 'image/x-cmx',
+ 'cod' => 'application/vnd.rim.cod',
+ 'com' => 'application/x-msdownload',
+ 'conf' => 'text/plain',
+ 'cpio' => 'application/x-cpio',
+ 'cpp' => 'text/x-c',
+ 'cpt' => 'application/mac-compactpro',
+ 'crd' => 'application/x-mscardfile',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'csh' => 'application/x-csh',
+ 'csml' => 'chemical/x-csml',
+ 'csp' => 'application/vnd.commonspace',
+ 'css' => 'text/css',
+ 'cst' => 'application/x-director',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'curl' => 'text/vnd.curl',
+ 'cww' => 'application/prs.cww',
+ 'cxt' => 'application/x-director',
+ 'cxx' => 'text/x-c',
+ 'dae' => 'model/vnd.collada+xml',
+ 'daf' => 'application/vnd.mobius.daf',
+ 'dart' => 'application/vnd.dart',
+ 'dataless' => 'application/vnd.fdsn.seed',
+ 'davmount' => 'application/davmount+xml',
+ 'dbk' => 'application/docbook+xml',
+ 'dcr' => 'application/x-director',
+ 'dcurl' => 'text/vnd.curl.dcurl',
+ 'dd2' => 'application/vnd.oma.dd2+xml',
+ 'ddd' => 'application/vnd.fujixerox.ddd',
+ 'deb' => 'application/x-debian-package',
+ 'def' => 'text/plain',
+ 'deploy' => 'application/octet-stream',
+ 'der' => 'application/x-x509-ca-cert',
+ 'dfac' => 'application/vnd.dreamfactory',
+ 'dgc' => 'application/x-dgc-compressed',
+ 'dic' => 'text/x-c',
+ 'dir' => 'application/x-director',
+ 'dis' => 'application/vnd.mobius.dis',
+ 'dist' => 'application/octet-stream',
+ 'distz' => 'application/octet-stream',
+ 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'dll' => 'application/x-msdownload',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'dmp' => 'application/vnd.tcpdump.pcap',
+ 'dms' => 'application/octet-stream',
+ 'dna' => 'application/vnd.dna',
+ 'doc' => 'application/msword',
+ 'docm' => 'application/vnd.ms-word.document.macroenabled.12',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dot' => 'application/msword',
+ 'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'dp' => 'application/vnd.osgi.dp',
+ 'dpg' => 'application/vnd.dpgraph',
+ 'dra' => 'audio/vnd.dra',
+ 'dsc' => 'text/prs.lines.tag',
+ 'dssc' => 'application/dssc+der',
+ 'dtb' => 'application/x-dtbook+xml',
+ 'dtd' => 'application/xml-dtd',
+ 'dts' => 'audio/vnd.dts',
+ 'dtshd' => 'audio/vnd.dts.hd',
+ 'dump' => 'application/octet-stream',
+ 'dvb' => 'video/vnd.dvb.file',
+ 'dvi' => 'application/x-dvi',
+ 'dwf' => 'model/vnd.dwf',
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'dxp' => 'application/vnd.spotfire.dxp',
+ 'dxr' => 'application/x-director',
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
+ 'ecma' => 'application/ecmascript',
+ 'edm' => 'application/vnd.novadigm.edm',
+ 'edx' => 'application/vnd.novadigm.edx',
+ 'efif' => 'application/vnd.picsel',
+ 'ei6' => 'application/vnd.pg.osasli',
+ 'elc' => 'application/octet-stream',
+ 'emf' => 'application/x-msmetafile',
+ 'eml' => 'message/rfc822',
+ 'emma' => 'application/emma+xml',
+ 'emz' => 'application/x-msmetafile',
+ 'eol' => 'audio/vnd.digital-winds',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'es3' => 'application/vnd.eszigno3+xml',
+ 'esa' => 'application/vnd.osgi.subsystem',
+ 'esf' => 'application/vnd.epson.esf',
+ 'et3' => 'application/vnd.eszigno3+xml',
+ 'etx' => 'text/x-setext',
+ 'eva' => 'application/x-eva',
+ 'evy' => 'application/x-envoy',
+ 'exe' => 'application/x-msdownload',
+ 'exi' => 'application/exi',
+ 'ext' => 'application/vnd.novadigm.ext',
+ 'ez' => 'application/andrew-inset',
+ 'ez2' => 'application/vnd.ezpix-album',
+ 'ez3' => 'application/vnd.ezpix-package',
+ 'f' => 'text/x-fortran',
+ 'f4v' => 'video/x-f4v',
+ 'f77' => 'text/x-fortran',
+ 'f90' => 'text/x-fortran',
+ 'fbs' => 'image/vnd.fastbidsheet',
+ 'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
+ 'fcs' => 'application/vnd.isac.fcs',
+ 'fdf' => 'application/vnd.fdf',
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
+ 'fgd' => 'application/x-director',
+ 'fh' => 'image/x-freehand',
+ 'fh4' => 'image/x-freehand',
+ 'fh5' => 'image/x-freehand',
+ 'fh7' => 'image/x-freehand',
+ 'fhc' => 'image/x-freehand',
+ 'fig' => 'application/x-xfig',
+ 'flac' => 'audio/x-flac',
+ 'fli' => 'video/x-fli',
+ 'flo' => 'application/vnd.micrografx.flo',
+ 'flv' => 'video/x-flv',
+ 'flw' => 'application/vnd.kde.kivio',
+ 'flx' => 'text/vnd.fmi.flexstor',
+ 'fly' => 'text/vnd.fly',
+ 'fm' => 'application/vnd.framemaker',
+ 'fnc' => 'application/vnd.frogans.fnc',
+ 'for' => 'text/x-fortran',
+ 'fpx' => 'image/vnd.fpx',
+ 'frame' => 'application/vnd.framemaker',
+ 'fsc' => 'application/vnd.fsc.weblaunch',
+ 'fst' => 'image/vnd.fst',
+ 'ftc' => 'application/vnd.fluxtime.clip',
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
+ 'fvt' => 'video/vnd.fvt',
+ 'fxp' => 'application/vnd.adobe.fxp',
+ 'fxpl' => 'application/vnd.adobe.fxp',
+ 'fzs' => 'application/vnd.fuzzysheet',
+ 'g2w' => 'application/vnd.geoplan',
+ 'g3' => 'image/g3fax',
+ 'g3w' => 'application/vnd.geospace',
+ 'gac' => 'application/vnd.groove-account',
+ 'gam' => 'application/x-tads',
+ 'gbr' => 'application/rpki-ghostbusters',
+ 'gca' => 'application/x-gca-compressed',
+ 'gdl' => 'model/vnd.gdl',
+ 'geo' => 'application/vnd.dynageo',
+ 'gex' => 'application/vnd.geometry-explorer',
+ 'ggb' => 'application/vnd.geogebra.file',
+ 'ggt' => 'application/vnd.geogebra.tool',
+ 'ghf' => 'application/vnd.groove-help',
+ 'gif' => 'image/gif',
+ 'gim' => 'application/vnd.groove-identity-message',
+ 'gml' => 'application/gml+xml',
+ 'gmx' => 'application/vnd.gmx',
+ 'gnumeric' => 'application/x-gnumeric',
+ 'gph' => 'application/vnd.flographit',
+ 'gpx' => 'application/gpx+xml',
+ 'gqf' => 'application/vnd.grafeq',
+ 'gqs' => 'application/vnd.grafeq',
+ 'gram' => 'application/srgs',
+ 'gramps' => 'application/x-gramps-xml',
+ 'gre' => 'application/vnd.geometry-explorer',
+ 'grv' => 'application/vnd.groove-injector',
+ 'grxml' => 'application/srgs+xml',
+ 'gsf' => 'application/x-font-ghostscript',
+ 'gtar' => 'application/x-gtar',
+ 'gtm' => 'application/vnd.groove-tool-message',
+ 'gtw' => 'model/vnd.gtw',
+ 'gv' => 'text/vnd.graphviz',
+ 'gxf' => 'application/gxf',
+ 'gxt' => 'application/vnd.geonext',
+ 'gz' => 'application/x-gzip',
+ 'h' => 'text/x-c',
+ 'h261' => 'video/h261',
+ 'h263' => 'video/h263',
+ 'h264' => 'video/h264',
+ 'hal' => 'application/vnd.hal+xml',
+ 'hbci' => 'application/vnd.hbci',
+ 'hdf' => 'application/x-hdf',
+ 'hh' => 'text/x-c',
+ 'hlp' => 'application/winhlp',
+ 'hpgl' => 'application/vnd.hp-hpgl',
+ 'hpid' => 'application/vnd.hp-hpid',
+ 'hps' => 'application/vnd.hp-hps',
+ 'hqx' => 'application/mac-binhex40',
+ 'htke' => 'application/vnd.kenameaapp',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
+ 'hvs' => 'application/vnd.yamaha.hv-script',
+ 'i2g' => 'application/vnd.intergeo',
+ 'icc' => 'application/vnd.iccprofile',
+ 'ice' => 'x-conference/x-cooltalk',
+ 'icm' => 'application/vnd.iccprofile',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ief' => 'image/ief',
+ 'ifb' => 'text/calendar',
+ 'ifm' => 'application/vnd.shana.informed.formdata',
+ 'iges' => 'model/iges',
+ 'igl' => 'application/vnd.igloader',
+ 'igm' => 'application/vnd.insors.igm',
+ 'igs' => 'model/iges',
+ 'igx' => 'application/vnd.micrografx.igx',
+ 'iif' => 'application/vnd.shana.informed.interchange',
+ 'imp' => 'application/vnd.accpac.simply.imp',
+ 'ims' => 'application/vnd.ms-ims',
+ 'in' => 'text/plain',
+ 'ink' => 'application/inkml+xml',
+ 'inkml' => 'application/inkml+xml',
+ 'install' => 'application/x-install-instructions',
+ 'iota' => 'application/vnd.astraea-software.iota',
+ 'ipfix' => 'application/ipfix',
+ 'ipk' => 'application/vnd.shana.informed.package',
+ 'irm' => 'application/vnd.ibm.rights-management',
+ 'irp' => 'application/vnd.irepository.package+xml',
+ 'iso' => 'application/x-iso9660-image',
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
+ 'ivp' => 'application/vnd.immervision-ivp',
+ 'ivu' => 'application/vnd.immervision-ivu',
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
+ 'jam' => 'application/vnd.jam',
+ 'jar' => 'application/java-archive',
+ 'java' => 'text/x-java-source',
+ 'jisp' => 'application/vnd.jisp',
+ 'jlt' => 'application/vnd.hp-jlyt',
+ 'jnlp' => 'application/x-java-jnlp-file',
+ 'joda' => 'application/vnd.joost.joda-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'jpgm' => 'video/jpm',
+ 'jpgv' => 'video/jpeg',
+ 'jpm' => 'video/jpm',
+ 'js' => 'application/javascript',
+ 'json' => 'application/json',
+ 'jsonml' => 'application/jsonml+json',
+ 'kar' => 'audio/midi',
+ 'karbon' => 'application/vnd.kde.karbon',
+ 'kfo' => 'application/vnd.kde.kformula',
+ 'kia' => 'application/vnd.kidspiration',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'kne' => 'application/vnd.kinar',
+ 'knp' => 'application/vnd.kinar',
+ 'kon' => 'application/vnd.kde.kontour',
+ 'kpr' => 'application/vnd.kde.kpresenter',
+ 'kpt' => 'application/vnd.kde.kpresenter',
+ 'kpxx' => 'application/vnd.ds-keypoint',
+ 'ksp' => 'application/vnd.kde.kspread',
+ 'ktr' => 'application/vnd.kahootz',
+ 'ktx' => 'image/ktx',
+ 'ktz' => 'application/vnd.kahootz',
+ 'kwd' => 'application/vnd.kde.kword',
+ 'kwt' => 'application/vnd.kde.kword',
+ 'lasxml' => 'application/vnd.las.las+xml',
+ 'latex' => 'application/x-latex',
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ 'les' => 'application/vnd.hhe.lesson-player',
+ 'lha' => 'application/x-lzh-compressed',
+ 'link66' => 'application/vnd.route66.link66+xml',
+ 'list' => 'text/plain',
+ 'list3820' => 'application/vnd.ibm.modcap',
+ 'listafp' => 'application/vnd.ibm.modcap',
+ 'lnk' => 'application/x-ms-shortcut',
+ 'log' => 'text/plain',
+ 'lostxml' => 'application/lost+xml',
+ 'lrf' => 'application/octet-stream',
+ 'lrm' => 'application/vnd.ms-lrm',
+ 'ltf' => 'application/vnd.frogans.ltf',
+ 'lvp' => 'audio/vnd.lucent.voice',
+ 'lwp' => 'application/vnd.lotus-wordpro',
+ 'lzh' => 'application/x-lzh-compressed',
+ 'm13' => 'application/x-msmediaview',
+ 'm14' => 'application/x-msmediaview',
+ 'm1v' => 'video/mpeg',
+ 'm21' => 'application/mp21',
+ 'm2a' => 'audio/mpeg',
+ 'm2v' => 'video/mpeg',
+ 'm3a' => 'audio/mpeg',
+ 'm3u' => 'audio/x-mpegurl',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'm4a' => 'audio/mp4',
+ 'm4u' => 'video/vnd.mpegurl',
+ 'm4v' => 'video/x-m4v',
+ 'ma' => 'application/mathematica',
+ 'mads' => 'application/mads+xml',
+ 'mag' => 'application/vnd.ecowin.chart',
+ 'maker' => 'application/vnd.framemaker',
+ 'man' => 'text/troff',
+ 'mar' => 'application/octet-stream',
+ 'mathml' => 'application/mathml+xml',
+ 'mb' => 'application/mathematica',
+ 'mbk' => 'application/vnd.mobius.mbk',
+ 'mbox' => 'application/mbox',
+ 'mc1' => 'application/vnd.medcalcdata',
+ 'mcd' => 'application/vnd.mcd',
+ 'mcurl' => 'text/vnd.curl.mcurl',
+ 'mdb' => 'application/x-msaccess',
+ 'mdi' => 'image/vnd.ms-modi',
+ 'me' => 'text/troff',
+ 'mesh' => 'model/mesh',
+ 'meta4' => 'application/metalink4+xml',
+ 'metalink' => 'application/metalink+xml',
+ 'mets' => 'application/mets+xml',
+ 'mfm' => 'application/vnd.mfmp',
+ 'mft' => 'application/rpki-manifest',
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
+ 'mgz' => 'application/vnd.proteus.magazine',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mie' => 'application/x-mie',
+ 'mif' => 'application/vnd.mif',
+ 'mime' => 'message/rfc822',
+ 'mj2' => 'video/mj2',
+ 'mjp2' => 'video/mj2',
+ 'mk3d' => 'video/x-matroska',
+ 'mka' => 'audio/x-matroska',
+ 'mks' => 'video/x-matroska',
+ 'mkv' => 'video/x-matroska',
+ 'mlp' => 'application/vnd.dolby.mlp',
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
+ 'mmf' => 'application/vnd.smaf',
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
+ 'mng' => 'video/x-mng',
+ 'mny' => 'application/x-msmoney',
+ 'mobi' => 'application/x-mobipocket-ebook',
+ 'mods' => 'application/mods+xml',
+ 'mov' => 'video/quicktime',
+ 'movie' => 'video/x-sgi-movie',
+ 'mp2' => 'audio/mpeg',
+ 'mp21' => 'application/mp21',
+ 'mp2a' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4s' => 'application/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpc' => 'application/vnd.mophun.certificate',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'mpga' => 'audio/mpeg',
+ 'mpkg' => 'application/vnd.apple.installer+xml',
+ 'mpm' => 'application/vnd.blueice.multipass',
+ 'mpn' => 'application/vnd.mophun.application',
+ 'mpp' => 'application/vnd.ms-project',
+ 'mpt' => 'application/vnd.ms-project',
+ 'mpy' => 'application/vnd.ibm.minipay',
+ 'mqy' => 'application/vnd.mobius.mqy',
+ 'mrc' => 'application/marc',
+ 'mrcx' => 'application/marcxml+xml',
+ 'ms' => 'text/troff',
+ 'mscml' => 'application/mediaservercontrol+xml',
+ 'mseed' => 'application/vnd.fdsn.mseed',
+ 'mseq' => 'application/vnd.mseq',
+ 'msf' => 'application/vnd.epson.msf',
+ 'msh' => 'model/mesh',
+ 'msi' => 'application/x-msdownload',
+ 'msl' => 'application/vnd.mobius.msl',
+ 'msty' => 'application/vnd.muvee.style',
+ 'mts' => 'model/vnd.mts',
+ 'mus' => 'application/vnd.musician',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ 'mvb' => 'application/x-msmediaview',
+ 'mwf' => 'application/vnd.mfer',
+ 'mxf' => 'application/mxf',
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'mxml' => 'application/xv+xml',
+ 'mxs' => 'application/vnd.triscape.mxs',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
+ 'n3' => 'text/n3',
+ 'nb' => 'application/mathematica',
+ 'nbp' => 'application/vnd.wolfram.player',
+ 'nc' => 'application/x-netcdf',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'nfo' => 'text/x-nfo',
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
+ 'nitf' => 'application/vnd.nitf',
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
+ 'nml' => 'application/vnd.enliven',
+ 'nnd' => 'application/vnd.noblenet-directory',
+ 'nns' => 'application/vnd.noblenet-sealer',
+ 'nnw' => 'application/vnd.noblenet-web',
+ 'npx' => 'image/vnd.net-fpx',
+ 'nsc' => 'application/x-conference',
+ 'nsf' => 'application/vnd.lotus-notes',
+ 'ntf' => 'application/vnd.nitf',
+ 'nzb' => 'application/x-nzb',
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
+ 'oas' => 'application/vnd.fujitsu.oasys',
+ 'obd' => 'application/x-msbinder',
+ 'obj' => 'application/x-tgif',
+ 'oda' => 'application/oda',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'omdoc' => 'application/omdoc+xml',
+ 'onepkg' => 'application/onenote',
+ 'onetmp' => 'application/onenote',
+ 'onetoc' => 'application/onenote',
+ 'onetoc2' => 'application/onenote',
+ 'opf' => 'application/oebps-package+xml',
+ 'opml' => 'text/x-opml',
+ 'oprc' => 'application/vnd.palm',
+ 'org' => 'application/vnd.lotus-organizer',
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
+ 'otf' => 'application/x-font-otf',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'oxps' => 'application/oxps',
+ 'oxt' => 'application/vnd.openofficeorg.extension',
+ 'p' => 'text/x-pascal',
+ 'p10' => 'application/pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'p7b' => 'application/x-pkcs7-certificates',
+ 'p7c' => 'application/pkcs7-mime',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'p7s' => 'application/pkcs7-signature',
+ 'p8' => 'application/pkcs8',
+ 'pas' => 'text/x-pascal',
+ 'paw' => 'application/vnd.pawaafile',
+ 'pbd' => 'application/vnd.powerbuilder6',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pcap' => 'application/vnd.tcpdump.pcap',
+ 'pcf' => 'application/x-font-pcf',
+ 'pcl' => 'application/vnd.hp-pcl',
+ 'pclxl' => 'application/vnd.hp-pclxl',
+ 'pct' => 'image/x-pict',
+ 'pcurl' => 'application/vnd.curl.pcurl',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/vnd.palm',
+ 'pdf' => 'application/pdf',
+ 'pfa' => 'application/x-font-type1',
+ 'pfb' => 'application/x-font-type1',
+ 'pfm' => 'application/x-font-type1',
+ 'pfr' => 'application/font-tdpfr',
+ 'pfx' => 'application/x-pkcs12',
+ 'pgm' => 'image/x-portable-graymap',
+ 'pgn' => 'application/x-chess-pgn',
+ 'pgp' => 'application/pgp-encrypted',
+ 'php' => 'application/x-php',
+ 'php3' => 'application/x-php',
+ 'php4' => 'application/x-php',
+ 'php5' => 'application/x-php',
+ 'pic' => 'image/x-pict',
+ 'pkg' => 'application/octet-stream',
+ 'pki' => 'application/pkixcmp',
+ 'pkipath' => 'application/pkix-pkipath',
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
+ 'plc' => 'application/vnd.mobius.plc',
+ 'plf' => 'application/vnd.pocketlearn',
+ 'pls' => 'application/pls+xml',
+ 'pml' => 'application/vnd.ctc-posml',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'portpkg' => 'application/vnd.macports.portpkg',
+ 'pot' => 'application/vnd.ms-powerpoint',
+ 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12',
+ 'ppd' => 'application/vnd.cups-ppd',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'pqa' => 'application/vnd.palm',
+ 'prc' => 'application/x-mobipocket-ebook',
+ 'pre' => 'application/vnd.lotus-freelance',
+ 'prf' => 'application/pics-rules',
+ 'ps' => 'application/postscript',
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
+ 'psd' => 'image/vnd.adobe.photoshop',
+ 'psf' => 'application/x-font-linux-psf',
+ 'pskcxml' => 'application/pskc+xml',
+ 'ptid' => 'application/vnd.pvi.ptid1',
+ 'pub' => 'application/x-mspublisher',
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
+ 'pwn' => 'application/vnd.3m.post-it-notes',
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
+ 'qam' => 'application/vnd.epson.quickanime',
+ 'qbo' => 'application/vnd.intu.qbo',
+ 'qfx' => 'application/vnd.intu.qfx',
+ 'qps' => 'application/vnd.publishare-delta-tree',
+ 'qt' => 'video/quicktime',
+ 'qwd' => 'application/vnd.quark.quarkxpress',
+ 'qwt' => 'application/vnd.quark.quarkxpress',
+ 'qxb' => 'application/vnd.quark.quarkxpress',
+ 'qxd' => 'application/vnd.quark.quarkxpress',
+ 'qxl' => 'application/vnd.quark.quarkxpress',
+ 'qxt' => 'application/vnd.quark.quarkxpress',
+ 'ra' => 'audio/x-pn-realaudio',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
+ 'rdf' => 'application/rdf+xml',
+ 'rdz' => 'application/vnd.data-vision.rdz',
+ 'rep' => 'application/vnd.businessobjects',
+ 'res' => 'application/x-dtbresource+xml',
+ 'rgb' => 'image/x-rgb',
+ 'rif' => 'application/reginfo+xml',
+ 'rip' => 'audio/vnd.rip',
+ 'ris' => 'application/x-research-info-systems',
+ 'rl' => 'application/resource-lists+xml',
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
+ 'rld' => 'application/resource-lists-diff+xml',
+ 'rm' => 'application/vnd.rn-realmedia',
+ 'rmi' => 'audio/midi',
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
+ 'rmvb' => 'application/vnd.rn-realmedia-vbr',
+ 'rnc' => 'application/relax-ng-compact-syntax',
+ 'roa' => 'application/rpki-roa',
+ 'roff' => 'text/troff',
+ 'rp9' => 'application/vnd.cloanto.rp9',
+ 'rpss' => 'application/vnd.nokia.radio-presets',
+ 'rpst' => 'application/vnd.nokia.radio-preset',
+ 'rq' => 'application/sparql-query',
+ 'rs' => 'application/rls-services+xml',
+ 'rsd' => 'application/rsd+xml',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'rtx' => 'text/richtext',
+ 's' => 'text/x-asm',
+ 's3m' => 'audio/s3m',
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
+ 'sbml' => 'application/sbml+xml',
+ 'sc' => 'application/vnd.ibm.secure-container',
+ 'scd' => 'application/x-msschedule',
+ 'scm' => 'application/vnd.lotus-screencam',
+ 'scq' => 'application/scvp-cv-request',
+ 'scs' => 'application/scvp-cv-response',
+ 'scurl' => 'text/vnd.curl.scurl',
+ 'sda' => 'application/vnd.stardivision.draw',
+ 'sdc' => 'application/vnd.stardivision.calc',
+ 'sdd' => 'application/vnd.stardivision.impress',
+ 'sdkd' => 'application/vnd.solent.sdkm+xml',
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
+ 'sdp' => 'application/sdp',
+ 'sdw' => 'application/vnd.stardivision.writer',
+ 'see' => 'application/vnd.seemail',
+ 'seed' => 'application/vnd.fdsn.seed',
+ 'sema' => 'application/vnd.sema',
+ 'semd' => 'application/vnd.semd',
+ 'semf' => 'application/vnd.semf',
+ 'ser' => 'application/java-serialized-object',
+ 'setpay' => 'application/set-payment-initiation',
+ 'setreg' => 'application/set-registration-initiation',
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
+ 'sfs' => 'application/vnd.spotfire.sfs',
+ 'sfv' => 'text/x-sfv',
+ 'sgi' => 'image/sgi',
+ 'sgl' => 'application/vnd.stardivision.writer-global',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'shf' => 'application/shf+xml',
+ 'sid' => 'image/x-mrsid-image',
+ 'sig' => 'application/pgp-signature',
+ 'sil' => 'audio/silk',
+ 'silo' => 'model/mesh',
+ 'sis' => 'application/vnd.symbian.install',
+ 'sisx' => 'application/vnd.symbian.install',
+ 'sit' => 'application/x-stuffit',
+ 'sitx' => 'application/x-stuffitx',
+ 'skd' => 'application/vnd.koan',
+ 'skm' => 'application/vnd.koan',
+ 'skp' => 'application/vnd.koan',
+ 'skt' => 'application/vnd.koan',
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'slt' => 'application/vnd.epson.salt',
+ 'sm' => 'application/vnd.stepmania.stepchart',
+ 'smf' => 'application/vnd.stardivision.math',
+ 'smi' => 'application/smil+xml',
+ 'smil' => 'application/smil+xml',
+ 'smv' => 'video/x-smv',
+ 'smzip' => 'application/vnd.stepmania.package',
+ 'snd' => 'audio/basic',
+ 'snf' => 'application/x-font-snf',
+ 'so' => 'application/octet-stream',
+ 'spc' => 'application/x-pkcs7-certificates',
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
+ 'spl' => 'application/x-futuresplash',
+ 'spot' => 'text/vnd.in3d.spot',
+ 'spp' => 'application/scvp-vp-response',
+ 'spq' => 'application/scvp-vp-request',
+ 'spx' => 'audio/ogg',
+ 'sql' => 'application/x-sql',
+ 'src' => 'application/x-wais-source',
+ 'srt' => 'application/x-subrip',
+ 'sru' => 'application/sru+xml',
+ 'srx' => 'application/sparql-results+xml',
+ 'ssdl' => 'application/ssdl+xml',
+ 'sse' => 'application/vnd.kodak-descriptor',
+ 'ssf' => 'application/vnd.epson.ssf',
+ 'ssml' => 'application/ssml+xml',
+ 'st' => 'application/vnd.sailingtracker.track',
+ 'stc' => 'application/vnd.sun.xml.calc.template',
+ 'std' => 'application/vnd.sun.xml.draw.template',
+ 'stf' => 'application/vnd.wt.stf',
+ 'sti' => 'application/vnd.sun.xml.impress.template',
+ 'stk' => 'application/hyperstudio',
+ 'stl' => 'application/vnd.ms-pki.stl',
+ 'str' => 'application/vnd.pg.format',
+ 'stw' => 'application/vnd.sun.xml.writer.template',
+ 'sub' => 'text/vnd.dvb.subtitle',
+ 'sus' => 'application/vnd.sus-calendar',
+ 'susp' => 'application/vnd.sus-calendar',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 'svc' => 'application/vnd.dvb.service',
+ 'svd' => 'application/vnd.svd',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg+xml',
+ 'swa' => 'application/x-director',
+ 'swf' => 'application/x-shockwave-flash',
+ 'swi' => 'application/vnd.aristanetworks.swi',
+ 'sxc' => 'application/vnd.sun.xml.calc',
+ 'sxd' => 'application/vnd.sun.xml.draw',
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
+ 'sxi' => 'application/vnd.sun.xml.impress',
+ 'sxm' => 'application/vnd.sun.xml.math',
+ 'sxw' => 'application/vnd.sun.xml.writer',
+ 't' => 'text/troff',
+ 't3' => 'application/x-t3vm-image',
+ 'taglet' => 'application/vnd.mynfc',
+ 'tao' => 'application/vnd.tao.intent-module-archive',
+ 'tar' => 'application/x-tar',
+ 'tcap' => 'application/vnd.3gpp2.tcap',
+ 'tcl' => 'application/x-tcl',
+ 'teacher' => 'application/vnd.smart.teacher',
+ 'tei' => 'application/tei+xml',
+ 'teicorpus' => 'application/tei+xml',
+ 'tex' => 'application/x-tex',
+ 'texi' => 'application/x-texinfo',
+ 'texinfo' => 'application/x-texinfo',
+ 'text' => 'text/plain',
+ 'tfi' => 'application/thraud+xml',
+ 'tfm' => 'application/x-tex-tfm',
+ 'tga' => 'image/x-tga',
+ 'thmx' => 'application/vnd.ms-officetheme',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'tmo' => 'application/vnd.tmobile-livetv',
+ 'torrent' => 'application/x-bittorrent',
+ 'tpl' => 'application/vnd.groove-tool-template',
+ 'tpt' => 'application/vnd.trid.tpt',
+ 'tr' => 'text/troff',
+ 'tra' => 'application/vnd.trueapp',
+ 'trm' => 'application/x-msterminal',
+ 'tsd' => 'application/timestamped-data',
+ 'tsv' => 'text/tab-separated-values',
+ 'ttc' => 'application/x-font-ttf',
+ 'ttf' => 'application/x-font-ttf',
+ 'ttl' => 'text/turtle',
+ 'twd' => 'application/vnd.simtech-mindmapper',
+ 'twds' => 'application/vnd.simtech-mindmapper',
+ 'txd' => 'application/vnd.genomatix.tuxedo',
+ 'txf' => 'application/vnd.mobius.txf',
+ 'txt' => 'text/plain',
+ 'u32' => 'application/x-authorware-bin',
+ 'udeb' => 'application/x-debian-package',
+ 'ufd' => 'application/vnd.ufdl',
+ 'ufdl' => 'application/vnd.ufdl',
+ 'ulx' => 'application/x-glulx',
+ 'umj' => 'application/vnd.umajin',
+ 'unityweb' => 'application/vnd.unity',
+ 'uoml' => 'application/vnd.uoml+xml',
+ 'uri' => 'text/uri-list',
+ 'uris' => 'text/uri-list',
+ 'urls' => 'text/uri-list',
+ 'ustar' => 'application/x-ustar',
+ 'utz' => 'application/vnd.uiq.theme',
+ 'uu' => 'text/x-uuencode',
+ 'uva' => 'audio/vnd.dece.audio',
+ 'uvd' => 'application/vnd.dece.data',
+ 'uvf' => 'application/vnd.dece.data',
+ 'uvg' => 'image/vnd.dece.graphic',
+ 'uvh' => 'video/vnd.dece.hd',
+ 'uvi' => 'image/vnd.dece.graphic',
+ 'uvm' => 'video/vnd.dece.mobile',
+ 'uvp' => 'video/vnd.dece.pd',
+ 'uvs' => 'video/vnd.dece.sd',
+ 'uvt' => 'application/vnd.dece.ttml+xml',
+ 'uvu' => 'video/vnd.uvvu.mp4',
+ 'uvv' => 'video/vnd.dece.video',
+ 'uvva' => 'audio/vnd.dece.audio',
+ 'uvvd' => 'application/vnd.dece.data',
+ 'uvvf' => 'application/vnd.dece.data',
+ 'uvvg' => 'image/vnd.dece.graphic',
+ 'uvvh' => 'video/vnd.dece.hd',
+ 'uvvi' => 'image/vnd.dece.graphic',
+ 'uvvm' => 'video/vnd.dece.mobile',
+ 'uvvp' => 'video/vnd.dece.pd',
+ 'uvvs' => 'video/vnd.dece.sd',
+ 'uvvt' => 'application/vnd.dece.ttml+xml',
+ 'uvvu' => 'video/vnd.uvvu.mp4',
+ 'uvvv' => 'video/vnd.dece.video',
+ 'uvvx' => 'application/vnd.dece.unspecified',
+ 'uvvz' => 'application/vnd.dece.zip',
+ 'uvx' => 'application/vnd.dece.unspecified',
+ 'uvz' => 'application/vnd.dece.zip',
+ 'vcard' => 'text/vcard',
+ 'vcd' => 'application/x-cdlink',
+ 'vcf' => 'text/x-vcard',
+ 'vcg' => 'application/vnd.groove-vcard',
+ 'vcs' => 'text/x-vcalendar',
+ 'vcx' => 'application/vnd.vcx',
+ 'vis' => 'application/vnd.visionary',
+ 'viv' => 'video/vnd.vivo',
+ 'vob' => 'video/x-ms-vob',
+ 'vor' => 'application/vnd.stardivision.writer',
+ 'vox' => 'application/x-authorware-bin',
+ 'vrml' => 'model/vrml',
+ 'vsd' => 'application/vnd.visio',
+ 'vsf' => 'application/vnd.vsf',
+ 'vss' => 'application/vnd.visio',
+ 'vst' => 'application/vnd.visio',
+ 'vsw' => 'application/vnd.visio',
+ 'vtu' => 'model/vnd.vtu',
+ 'vxml' => 'application/voicexml+xml',
+ 'w3d' => 'application/x-director',
+ 'wad' => 'application/x-doom',
+ 'wav' => 'audio/x-wav',
+ 'wax' => 'audio/x-ms-wax',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
+ 'wbxml' => 'application/vnd.wap.wbxml',
+ 'wcm' => 'application/vnd.ms-works',
+ 'wdb' => 'application/vnd.ms-works',
+ 'wdp' => 'image/vnd.ms-photo',
+ 'weba' => 'audio/webm',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wg' => 'application/vnd.pmi.widget',
+ 'wgt' => 'application/widget',
+ 'wks' => 'application/vnd.ms-works',
+ 'wm' => 'video/x-ms-wm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmd' => 'application/x-ms-wmd',
+ 'wmf' => 'application/x-msmetafile',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmlc' => 'application/vnd.wap.wmlc',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wmx' => 'video/x-ms-wmx',
+ 'wmz' => 'application/x-msmetafile',
+ 'woff' => 'application/font-woff',
+ 'wpd' => 'application/vnd.wordperfect',
+ 'wpl' => 'application/vnd.ms-wpl',
+ 'wps' => 'application/vnd.ms-works',
+ 'wqd' => 'application/vnd.wqd',
+ 'wri' => 'application/x-mswrite',
+ 'wrl' => 'model/vrml',
+ 'wsdl' => 'application/wsdl+xml',
+ 'wspolicy' => 'application/wspolicy+xml',
+ 'wtb' => 'application/vnd.webturbo',
+ 'wvx' => 'video/x-ms-wvx',
+ 'x32' => 'application/x-authorware-bin',
+ 'x3d' => 'model/x3d+xml',
+ 'x3db' => 'model/x3d+binary',
+ 'x3dbz' => 'model/x3d+binary',
+ 'x3dv' => 'model/x3d+vrml',
+ 'x3dvz' => 'model/x3d+vrml',
+ 'x3dz' => 'model/x3d+xml',
+ 'xaml' => 'application/xaml+xml',
+ 'xap' => 'application/x-silverlight-app',
+ 'xar' => 'application/vnd.xara',
+ 'xbap' => 'application/x-ms-xbap',
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
+ 'xbm' => 'image/x-xbitmap',
+ 'xdf' => 'application/xcap-diff+xml',
+ 'xdm' => 'application/vnd.syncml.dm+xml',
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
+ 'xdssc' => 'application/dssc+xml',
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
+ 'xenc' => 'application/xenc+xml',
+ 'xer' => 'application/patch-ops-error+xml',
+ 'xfdf' => 'application/vnd.adobe.xfdf',
+ 'xfdl' => 'application/vnd.xfdl',
+ 'xht' => 'application/xhtml+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xhvml' => 'application/xv+xml',
+ 'xif' => 'image/vnd.xiff',
+ 'xla' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12',
+ 'xlc' => 'application/vnd.ms-excel',
+ 'xlf' => 'application/x-xliff+xml',
+ 'xlm' => 'application/vnd.ms-excel',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlw' => 'application/vnd.ms-excel',
+ 'xm' => 'audio/xm',
+ 'xml' => 'application/xml',
+ 'xo' => 'application/vnd.olpc-sugar',
+ 'xop' => 'application/xop+xml',
+ 'xpi' => 'application/x-xpinstall',
+ 'xpl' => 'application/xproc+xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xpr' => 'application/vnd.is-xpr',
+ 'xps' => 'application/vnd.ms-xpsdocument',
+ 'xpw' => 'application/vnd.intercon.formnet',
+ 'xpx' => 'application/vnd.intercon.formnet',
+ 'xsl' => 'application/xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xsm' => 'application/vnd.syncml+xml',
+ 'xspf' => 'application/xspf+xml',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xvm' => 'application/xv+xml',
+ 'xvml' => 'application/xv+xml',
+ 'xwd' => 'image/x-xwindowdump',
+ 'xyz' => 'chemical/x-xyz',
+ 'xz' => 'application/x-xz',
+ 'yang' => 'application/yang',
+ 'yin' => 'application/yin+xml',
+ 'z1' => 'application/x-zmachine',
+ 'z2' => 'application/x-zmachine',
+ 'z3' => 'application/x-zmachine',
+ 'z4' => 'application/x-zmachine',
+ 'z5' => 'application/x-zmachine',
+ 'z6' => 'application/x-zmachine',
+ 'z7' => 'application/x-zmachine',
+ 'z8' => 'application/x-zmachine',
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
+ 'zip' => 'application/zip',
+ 'zir' => 'application/vnd.zul',
+ 'zirz' => 'application/vnd.zul',
+ 'zmm' => 'application/vnd.handheld-entertainment+xml',
+ '123' => 'application/vnd.lotus-1-2-3',
+);
diff --git a/vendor/swiftmailer/swiftmailer/lib/preferences.php b/vendor/swiftmailer/swiftmailer/lib/preferences.php
new file mode 100644
index 0000000..0b430e6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/preferences.php
@@ -0,0 +1,25 @@
+<?php
+
+/****************************************************************************/
+/* */
+/* YOU MAY WISH TO MODIFY OR REMOVE THE FOLLOWING LINES WHICH SET DEFAULTS */
+/* */
+/****************************************************************************/
+
+$preferences = Swift_Preferences::getInstance();
+
+// Sets the default charset so that setCharset() is not needed elsewhere
+$preferences->setCharset('utf-8');
+
+// Without these lines the default caching mechanism is "array" but this uses a lot of memory.
+// If possible, use a disk cache to enable attaching large attachments etc.
+// You can override the default temporary directory by setting the TMPDIR environment variable.
+if (@is_writable($tmpDir = sys_get_temp_dir())) {
+ $preferences->setTempDir($tmpDir)->setCacheType('disk');
+}
+
+// this should only be done when Swiftmailer won't use the native QP content encoder
+// see mime_deps.php
+if (PHP_VERSION_ID < 50407) {
+ $preferences->setQPDotEscape(false);
+}
diff --git a/vendor/swiftmailer/swiftmailer/lib/swift_init.php b/vendor/swiftmailer/swiftmailer/lib/swift_init.php
new file mode 100644
index 0000000..ff71963
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/swift_init.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/*
+ * Dependency injection initialization for Swift Mailer.
+ */
+
+if (defined('SWIFT_INIT_LOADED')) {
+ return;
+}
+
+define('SWIFT_INIT_LOADED', true);
+
+// Load in dependency maps
+require __DIR__.'/dependency_maps/cache_deps.php';
+require __DIR__.'/dependency_maps/mime_deps.php';
+require __DIR__.'/dependency_maps/message_deps.php';
+require __DIR__.'/dependency_maps/transport_deps.php';
+
+// Load in global library preferences
+require __DIR__.'/preferences.php';
diff --git a/vendor/swiftmailer/swiftmailer/lib/swift_required.php b/vendor/swiftmailer/swiftmailer/lib/swift_required.php
new file mode 100644
index 0000000..03a72ce
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/swift_required.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/*
+ * Autoloader and dependency injection initialization for Swift Mailer.
+ */
+
+if (class_exists('Swift', false)) {
+ return;
+}
+
+// Load Swift utility class
+require __DIR__.'/classes/Swift.php';
+
+if (!function_exists('_swiftmailer_init')) {
+ function _swiftmailer_init()
+ {
+ require __DIR__.'/swift_init.php';
+ }
+}
+
+// Start the autoloader and lazy-load the init script to set up dependency injection
+Swift::registerAutoload('_swiftmailer_init');
diff --git a/vendor/swiftmailer/swiftmailer/lib/swift_required_pear.php b/vendor/swiftmailer/swiftmailer/lib/swift_required_pear.php
new file mode 100644
index 0000000..2b5181a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/swift_required_pear.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/*
+ * Autoloader and dependency injection initialization for Swift Mailer.
+ */
+
+if (class_exists('Swift', false)) {
+ return;
+}
+
+// Load Swift utility class
+require __DIR__.'/Swift.php';
+
+if (!function_exists('_swiftmailer_init')) {
+ function _swiftmailer_init()
+ {
+ require __DIR__.'/swift_init.php';
+ }
+}
+
+// Start the autoloader and lazy-load the init script to set up dependency injection
+Swift::registerAutoload('_swiftmailer_init');
diff --git a/vendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.php b/vendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.php
new file mode 100755
index 0000000..85f2d69
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.php
@@ -0,0 +1,193 @@
+#!/usr/bin/php
+
+<?php
+define('APACHE_MIME_TYPES_URL', 'https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');
+define('FREEDESKTOP_XML_URL', 'https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml');
+
+function generateUpToDateMimeArray()
+{
+ $preamble = "<?php\n\n";
+ $preamble .= "/*\n";
+ $preamble .= " * This file is part of SwiftMailer.\n";
+ $preamble .= " * (c) 2004-2009 Chris Corbyn\n *\n";
+ $preamble .= " * For the full copyright and license information, please view the LICENSE\n";
+ $preamble .= " * file that was distributed with this source code.\n *\n";
+ $preamble .= " * autogenerated using https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types\n";
+ $preamble .= " * and https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml\n";
+ $preamble .= " */\n\n";
+ $preamble .= "/*\n";
+ $preamble .= " * List of MIME type automatically detected in Swift Mailer.\n";
+ $preamble .= " */\n\n";
+ $preamble .= "// You may add or take away what you like (lowercase required)\n\n";
+
+ // get current mime types files
+ $mime_types = @file_get_contents(APACHE_MIME_TYPES_URL);
+ $mime_xml = @file_get_contents(FREEDESKTOP_XML_URL);
+
+ // prepare valid mime types
+ $valid_mime_types = array();
+
+ // split mime type and extensions eg. "video/x-matroska mkv mk3d mks"
+ if (preg_match_all('/^#?([a-z0-9\-\+\/\.]+)[\t]+(.*)$/miu', $mime_types, $matches) !== false) {
+ // collection of predefined mimetypes (bugfix for wrong resolved or missing mime types)
+ $valid_mime_types_preset = array(
+ 'php' => 'application/x-php',
+ 'php3' => 'application/x-php',
+ 'php4' => 'application/x-php',
+ 'php5' => 'application/x-php',
+ 'zip' => 'application/zip',
+ 'gif' => 'image/gif',
+ 'png' => 'image/png',
+ 'css' => 'text/css',
+ 'js' => 'text/javascript',
+ 'txt' => 'text/plain',
+ 'aif' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'avi' => 'video/avi',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bz2',
+ 'csv' => 'text/csv',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'eml' => 'message/rfc822',
+ 'aps' => 'application/postscript',
+ 'exe' => 'application/x-ms-dos-executable',
+ 'flv' => 'video/x-flv',
+ 'gz' => 'application/x-gzip',
+ 'hqx' => 'application/stuffit',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'jar' => 'application/x-java-archive',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'm3u' => 'audio/x-mpegurl',
+ 'm4a' => 'audio/mp4',
+ 'mdb' => 'application/x-msaccess',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'odg' => 'vnd.oasis.opendocument.graphics',
+ 'odp' => 'vnd.oasis.opendocument.presentation',
+ 'odt' => 'vnd.oasis.opendocument.text',
+ 'ods' => 'vnd.oasis.opendocument.spreadsheet',
+ 'ogg' => 'audio/ogg',
+ 'pdf' => 'application/pdf',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'rar' => 'application/x-rar-compressed',
+ 'rtf' => 'application/rtf',
+ 'tar' => 'application/x-tar',
+ 'sit' => 'application/x-stuffit',
+ 'svg' => 'image/svg+xml',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'ttf' => 'application/x-font-truetype',
+ 'vcf' => 'text/x-vcard',
+ 'wav' => 'audio/wav',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'audio/x-ms-wmv',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ );
+
+ // wrap array for generating file
+ foreach ($valid_mime_types_preset as $extension => $mime_type) {
+ // generate array for mimetype to extension resolver (only first match)
+ $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
+ }
+
+ // collect extensions
+ $valid_extensions = array();
+
+ // all extensions from second match
+ foreach ($matches[2] as $i => $extensions) {
+ // explode multiple extensions from string
+ $extensions = explode(' ', strtolower($extensions));
+
+ // force array for foreach
+ if (!is_array($extensions)) {
+ $extensions = array($extensions);
+ }
+
+ foreach ($extensions as $extension) {
+ // get mime type
+ $mime_type = $matches[1][$i];
+
+ // check if string length lower than 10
+ if (strlen($extension) < 10) {
+ // add extension
+ $valid_extensions[] = $extension;
+
+ if (!isset($valid_mime_types[$mime_type])) {
+ // generate array for mimetype to extension resolver (only first match)
+ $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
+ }
+ }
+ }
+ }
+ }
+
+ $xml = simplexml_load_string($mime_xml);
+
+ foreach ($xml as $node) {
+ // check if there is no pattern
+ if (!isset($node->glob['pattern'])) {
+ continue;
+ }
+
+ // get all matching extensions from match
+ foreach ((array) $node->glob['pattern'] as $extension) {
+ // skip none glob extensions
+ if (strpos($extension, '.') === false) {
+ continue;
+ }
+
+ // remove get only last part
+ $extension = explode('.', strtolower($extension));
+ $extension = end($extension);
+
+ // maximum length in database column
+ if (strlen($extension) <= 9) {
+ $valid_extensions[] = $extension;
+ }
+ }
+
+ if (isset($node->glob['pattern'][0])) {
+ // mime type
+ $mime_type = strtolower((string) $node['type']);
+
+ // get first extension
+ $extension = strtolower(trim($node->glob['ddpattern'][0], '*.'));
+
+ // skip none glob extensions and check if string length between 1 and 10
+ if (strpos($extension, '.') !== false || strlen($extension) < 1 || strlen($extension) > 9) {
+ continue;
+ }
+
+ // check if string length lower than 10
+ if (!isset($valid_mime_types[$mime_type])) {
+ // generate array for mimetype to extension resolver (only first match)
+ $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
+ }
+ }
+ }
+
+ // full list of valid extensions only
+ $valid_mime_types = array_unique($valid_mime_types);
+ ksort($valid_mime_types);
+
+ // combine mime types and extensions array
+ $output = "$preamble\$swift_mime_types = array(\n ".implode($valid_mime_types, ",\n ")."\n);";
+
+ // write mime_types.php config file
+ @file_put_contents('./mime_types.php', $output);
+}
+
+generateUpToDateMimeArray();
diff --git a/vendor/swiftmailer/swiftmailer/phpunit.xml.dist b/vendor/swiftmailer/swiftmailer/phpunit.xml.dist
new file mode 100644
index 0000000..606c5b4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/phpunit.xml.dist
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="tests/bootstrap.php"
+>
+ <php>
+ <ini name="intl.default_locale" value="en"/>
+ <ini name="intl.error_level" value="0"/>
+ <ini name="memory_limit" value="-1"/>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="SwiftMailer unit tests">
+ <directory>tests/unit</directory>
+ </testsuite>
+ <testsuite name="SwiftMailer acceptance tests">
+ <directory>tests/acceptance</directory>
+ </testsuite>
+ <testsuite name="SwiftMailer bug">
+ <directory>tests/bug</directory>
+ </testsuite>
+ <testsuite name="SwiftMailer smoke tests">
+ <directory>tests/smoke</directory>
+ </testsuite>
+ </testsuites>
+
+ <listeners>
+ <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
+ <listener class="Mockery\Adapter\Phpunit\TestListener" />
+ </listeners>
+</phpunit>
diff --git a/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php b/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php
new file mode 100644
index 0000000..069d11a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * A binary safe string comparison.
+ *
+ * @author Chris Corbyn
+ */
+class IdenticalBinaryConstraint extends \PHPUnit_Framework_Constraint
+{
+ protected $value;
+
+ public function __construct($value)
+ {
+ $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
+ */
+ public function matches($other)
+ {
+ $aHex = $this->asHexString($this->value);
+ $bHex = $this->asHexString($other);
+
+ return $aHex === $bHex;
+ }
+
+ /**
+ * Returns a string representation of the constraint.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'indentical binary';
+ }
+
+ /**
+ * Get the given string of bytes as a stirng of Hexadecimal sequences.
+ *
+ * @param string $binary
+ *
+ * @return string
+ */
+ private function asHexString($binary)
+ {
+ $hex = '';
+
+ $bytes = unpack('H*', $binary);
+
+ foreach ($bytes as &$byte) {
+ $byte = strtoupper($byte);
+ }
+
+ return implode('', $bytes);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php b/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php
new file mode 100644
index 0000000..7f079d9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php
@@ -0,0 +1,11 @@
+<?php
+
+class Swift_StreamCollector
+{
+ public $content = '';
+
+ public function __invoke($arg)
+ {
+ $this->content .= $arg;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php b/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php
new file mode 100644
index 0000000..71c5713
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Base test for smoke tests.
+ *
+ * @author Rouven Weßling
+ */
+class SwiftMailerSmokeTestCase extends SwiftMailerTestCase
+{
+ protected function setUp()
+ {
+ if (!defined('SWIFT_SMOKE_TRANSPORT_TYPE')) {
+ $this->markTestSkipped(
+ 'Smoke tests are skipped if tests/smoke.conf.php is not edited'
+ );
+ }
+ }
+
+ protected function _getMailer()
+ {
+ switch (SWIFT_SMOKE_TRANSPORT_TYPE) {
+ case 'smtp':
+ $transport = Swift_DependencyContainer::getInstance()->lookup('transport.smtp')
+ ->setHost(SWIFT_SMOKE_SMTP_HOST)
+ ->setPort(SWIFT_SMOKE_SMTP_PORT)
+ ->setUsername(SWIFT_SMOKE_SMTP_USER)
+ ->setPassword(SWIFT_SMOKE_SMTP_PASS)
+ ->setEncryption(SWIFT_SMOKE_SMTP_ENCRYPTION)
+ ;
+ break;
+ case 'sendmail':
+ $transport = Swift_DependencyContainer::getInstance()->lookup('transport.sendmail')
+ ->setCommand(SWIFT_SMOKE_SENDMAIL_COMMAND)
+ ;
+ break;
+ case 'mail':
+ case 'nativemail':
+ $transport = Swift_DependencyContainer::getInstance()->lookup('transport.mail');
+ break;
+ default:
+ throw new Exception('Undefined transport ['.SWIFT_SMOKE_TRANSPORT_TYPE.']');
+ }
+
+ return new Swift_Mailer($transport);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php b/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php
new file mode 100644
index 0000000..f0e2736
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * A base test case with some custom expectations.
+ *
+ * @author Rouven Weßling
+ */
+class SwiftMailerTestCase extends \PHPUnit_Framework_TestCase
+{
+ public static function regExp($pattern)
+ {
+ if (!is_string($pattern)) {
+ throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
+ }
+
+ return new PHPUnit_Framework_Constraint_PCREMatch($pattern);
+ }
+
+ public function assertIdenticalBinary($expected, $actual, $message = '')
+ {
+ $constraint = new IdenticalBinaryConstraint($expected);
+ self::assertThat($actual, $constraint, $message);
+ }
+
+ protected function tearDown()
+ {
+ \Mockery::close();
+ }
+
+ protected function getMockery($class)
+ {
+ return \Mockery::mock($class);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt
new file mode 100644
index 0000000..c2923de
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt
@@ -0,0 +1,11 @@
+ISO-2022-JPは、インターネット上(特に電子メール)などで使われる日本の文字用の文字符号化方式。ISO/IEC 2022のエスケープシーケンスを利用して文字集合を切り替える7ビットのコードであることを特徴とする (アナウンス機能のエスケープシーケンスは省略される)。俗に「JISコード」と呼ばれることもある。
+
+概要
+日本語表記への利用が想定されている文字コードであり、日本語の利用されるネットワークにおいて、日本の規格を応用したものである。また文字集合としては、日本語で用いられる漢字、ひらがな、カタカナはもちろん、ラテン文字、ギリシア文字、キリル文字なども含んでおり、学術や産業の分野での利用も考慮たものとなっている。規格名に、ISOの日本語の言語コードであるjaではなく、国・地域名コードのJPが示されているゆえんである。
+文字集合としてJIS X 0201のC0集合(制御文字)、JIS X 0201のラテン文字集合、ISO 646の国際基準版図形文字、JIS X 0208の1978年版(JIS C 6226-1978)と1983年および1990年版が利用できる。JIS X 0201の片仮名文字集合は利用できない。1986年以降、日本の電子メールで用いられてきたJUNETコードを、村井純・Mark Crispin・Erik van der Poelが1993年にRFC化したもの(RFC 1468)。後にJIS X 0208:1997の附属書2としてJISに規定された。MIMEにおける文字符号化方式の識別用の名前として IANA に登録されている。
+なお、符号化の仕様についてはISO/IEC 2022#ISO-2022-JPも参照。
+
+ISO-2022-JPと非標準的拡張使用
+「JISコード」(または「ISO-2022-JP」)というコード名の規定下では、その仕様通りの使用が求められる。しかし、Windows OS上では、実際にはCP932コード (MicrosoftによるShift JISを拡張した亜種。ISO-2022-JP規定外文字が追加されている。)による独自拡張(の文字)を断りなく使うアプリケーションが多い。この例としてInternet ExplorerやOutlook Expressがある。また、EmEditor、秀丸エディタやThunderbirdのようなMicrosoft社以外のWindowsアプリケーションでも同様の場合がある。この場合、ISO-2022-JPの範囲外の文字を使ってしまうと、異なる製品間では未定義不明文字として認識されるか、もしくは文字化けを起こす原因となる。そのため、Windows用の電子メールクライアントであっても独自拡張の文字を使用すると警告を出したり、あえて使えないように制限しているものも存在する。さらにはISO-2022-JPの範囲内であってもCP932は非標準文字(FULLWIDTH TILDE等)を持つので文字化けの原因になり得る。
+また、符号化方式名をISO-2022-JPとしているのに、文字集合としてはJIS X 0212 (いわゆる補助漢字) やJIS X 0201の片仮名文字集合 (いわゆる半角カナ) をも符号化している例があるが、ISO-2022-JPではこれらの文字を許容していない。これらの符号化は独自拡張の実装であり、中にはISO/IEC 2022の仕様に準拠すらしていないものもある[2]。従って受信側の電子メールクライアントがこれらの独自拡張に対応していない場合、その文字あるいはその文字を含む行、時にはテキスト全体が文字化けすることがある。
+
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt
new file mode 100644
index 0000000..3101178
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt
@@ -0,0 +1,19 @@
+Op mat eraus hinnen beschte, rou zënne schaddreg ké. Ké sin Eisen Kaffi prächteg, den haut esou Fielse wa, Well zielen d'Welt am dir. Aus grousse rëschten d'Stroos do, as dat Kléder gewëss d'Kàchen. Schied gehéiert d'Vioule net hu, rou ke zënter Säiten d'Hierz. Ze eise Fletschen mat, gei as gréng d'Lëtzebuerger. Wäit räich no mat.
+
+Säiten d'Liewen aus en. Un gëtt bléit lossen wee, da wéi alle weisen Kolrettchen. Et deser d'Pan d'Kirmes vun, en wuel Benn rëschten méi. En get drem ménger beschte, da wär Stad welle. Nun Dach d'Pied do, mä gét ruffen gehéiert. Ze onser ugedon fir, d'Liewen Plett'len ech no, si Räis wielen bereet wat. Iwer spilt fir jo.
+
+An hin däischter Margréitchen, eng ke Frot brommt, vu den Räis néierens. Da hir Hunn Frot nozegon, rout Fläiß Himmel zum si, net gutt Kaffi Gesträich fu. Vill lait Gaart sou wa, Land Mamm Schuebersonndeg rei do. Gei geet Minutt en, gei d'Leit beschte Kolrettchen et, Mamm fergiess un hun.
+
+Et gutt Heck kommen oft, Lann rëscht rei um, Hunn rëscht schéinste ke der. En lait zielen schnéiwäiss hir, fu rou botze éiweg Minutt, rem fest gudden schaddreg en. Noper bereet Margréitchen mat op, dem denkt d'Leit d'Vioule no, oft ké Himmel Hämmel. En denkt blénken Fréijor net, Gart Schiet d'Natur no wou. No hin Ierd Frot d'Kirmes. Hire aremt un rou, ké den éiweg wielen Milliounen.
+
+Mir si Hunn Blénkeg. Ké get ston derfir d'Kàchen. Haut d'Pan fu ons, dé frou löschteg d'Meereische rei. Sou op wuel Léift. Stret schlon grousse gin hu. Mä denkt d'Leit hinnen net, ké gét haut fort rëscht.
+
+Koum d'Pan hannendrun ass ké, ké den brét Kaffi geplot. Schéi Hären d'Pied fu gét, do d'Mier néierens bei. Rëm päift Hämmel am, wee Engel beschéngt mä. Brommt klinzecht der ke, wa rout jeitzt dén. Get Zalot d'Vioule däischter da, jo fir Bänk päift duerch, bei d'Beem schéinen Plett'len jo. Den haut Faarwen ze, eng en Biereg Kirmesdag, um sin alles Faarwen d'Vioule.
+
+Eng Hunn Schied et, wat wa Frot fest gebotzt. Bei jo bleiwe ruffen Klarinett. Un Feld klinzecht gét, rifft Margréitchen rem ke. Mir dé Noper duurch gewëss, ston sech kille sin en. Gei Stret d'Wise um, Haus Gart wee as. Monn ménger an blo, wat da Gart gefällt Hämmelsbrot.
+
+Brommt geplot och ze, dat wa Räis Well Kaffi. Do get spilt prächteg, as wär kille bleiwe gewalteg. Onser frësch Margréitchen rem ke, blo en huet ugedon. Onser Hemecht wär de, hu eraus d'Sonn dat, eise deser hannendrun da och.
+
+As durch Himmel hun, no fest iw'rem schéinste mir, Hunn séngt Hierz ke zum. Séngt iw'rem d'Natur zum an. Ke wär gutt Grénge. Kënnt gudden prächteg mä rei. Dé dir Blénkeg Klarinett Kolrettchen, da fort muerges d'Kanner wou, main Feld ruffen vu wéi. Da gin esou Zalot gewalteg, gét vill Hemecht blénken dé.
+
+Haut gréng nun et, nei vu Bass gréng d'Gaassen. Fest d'Beem uechter si gin. Oft vu sinn wellen kréien. Et ass lait Zalot schéinen. \ No newline at end of file
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt
new file mode 100644
index 0000000..26c94d5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt
@@ -0,0 +1,22 @@
+Код одно гринспана руководишь на. Его вы знания движение. Ты две начать
+одиночку, сказать основатель удовольствием но миф. Бы какие система тем.
+Полностью использует три мы, человек клоунов те нас, бы давать творческую
+эзотерическая шеф.
+
+Мог не помнить никакого сэкономленного, две либо какие пишите бы. Должен
+компанию кто те, этот заключалась проектировщик не ты. Глупые периоды ты
+для. Вам который хороший он. Те любых кремния концентрируются мог,
+собирать принадлежите без вы.
+
+Джоэла меньше хорошего вы миф, за тем году разработки. Даже управляющим
+руководители был не. Три коде выпускать заботиться ну. То его система
+удовольствием безостановочно, или ты главной процессорах. Мы без джоэл
+знания получат, статьи остальные мы ещё.
+
+Них русском касается поскольку по, образование должником
+систематизированный ну мои. Прийти кандидата университет но нас, для бы
+должны никакого, биг многие причин интервьюирования за.
+
+Тем до плиту почему. Вот учёт такие одного бы, об биг разным внешних
+промежуток. Вас до какому возможностей безответственный, были погодите бы
+его, по них глупые долгий количества.
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt
new file mode 100644
index 0000000..c81ccd5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt
@@ -0,0 +1,45 @@
+Αν ήδη διάβασε γλιτώσει μεταγλωτίσει, αυτήν θυμάμαι μου μα. Την κατάσταση χρησιμοποίησέ να! Τα διαφορά φαινόμενο διολισθήσεις πες, υψηλότερη προκαλείς περισσότερες όχι κι. Με ελέγχου γίνεται σας, μικρής δημιουργούν τη του. Τις τα γράψει εικόνες απαράδεκτη?
+
+Να ότι πρώτοι απαραίτητο. Άμεση πετάνε κακόκεφος τον ώς, να χώρου πιθανότητες του. Το μέχρι ορίστε λιγότερους σας. Πω ναί φυσικά εικόνες.
+
+Μου οι κώδικα αποκλειστικούς, λες το μάλλον συνεχώς. Νέου σημεία απίστευτα σας μα. Χρόνου μεταγλωτιστής σε νέα, τη τις πιάνει μπορούσες προγραμματιστές. Των κάνε βγαίνει εντυπωσιακό τα? Κρατάει τεσσαρών δυστυχώς της κι, ήδη υψηλότερη εξακολουθεί τα?
+
+Ώρα πετάνε μπορούσε λιγότερους αν, τα απαράδεκτη συγχωνευτεί ροή. Τη έγραψες συνηθίζουν σαν. Όλα με υλικό στήλες χειρότερα. Ανώδυνη δουλέψει επί ως, αν διαδίκτυο εσωτερικών παράγοντες από. Κεντρικό επιτυχία πες το.
+
+Πω ναι λέει τελειώσει, έξι ως έργων τελειώσει. Με αρχεία βουτήξουν ανταγωνιστής ώρα, πολύ γραφικά σελίδων τα στη. Όρο οέλεγχος δημιουργούν δε, ας θέλεις ελέγχου συντακτικό όρο! Της θυμάμαι επιδιόρθωση τα. Για μπορούσε περισσότερο αν, μέγιστη σημαίνει αποφάσισε τα του, άτομο αποτελέσει τι στα.
+
+Τι στην αφήσεις διοίκηση στη. Τα εσφαλμένη δημιουργια επιχείριση έξι! Βήμα μαγικά εκτελέσει ανά τη. Όλη αφήσεις συνεχώς εμπορικά αν, το λες κόλπα επιτυχία. Ότι οι ζώνη κειμένων. Όρο κι ρωτάει γραμμής πελάτες, τελειώσει διολισθήσεις καθυστερούσε αν εγώ? Τι πετούν διοίκηση προβλήματα ήδη.
+
+Τη γλιτώσει αποθηκευτικού μια. Πω έξι δημιουργια πιθανότητες, ως πέντε ελέγχους εκτελείται λες. Πως ερωτήσεις διοικητικό συγκεντρωμένοι οι, ας συνεχώς διοικητικό αποστηθίσει σαν. Δε πρώτες συνεχώς διολισθήσεις έχω, από τι κανένας βουτήξουν, γειτονιάς προσεκτικά ανταγωνιστής κι σαν.
+
+Δημιουργια συνηθίζουν κλπ τι? Όχι ποσοστό διακοπής κι. Κλπ φακέλους δεδομένη εξοργιστικά θα? Υποψήφιο καθορίζουν με όλη, στα πήρε προσοχή εταιρείες πω, ώς τον συνάδελφος διοικητικό δημιουργήσεις! Δούλευε επιτίθενται σας θα, με ένας παραγωγικής ένα, να ναι σημεία μέγιστη απαράδεκτη?
+
+Σας τεσσαρών συνεντεύξης τη, αρπάζεις σίγουρος μη για', επί τοπικές εντολές ακούσει θα? Ως δυστυχής μεταγλωτιστής όλη, να την είχαν σφάλμα απαραίτητο! Μην ώς άτομο διορθώσει χρησιμοποιούνταν. Δεν τα κόλπα πετάξαμε, μη που άγχος υόρκη άμεση, αφού δυστυχώς διακόψουμε όρο αν! Όλη μαγικά πετάνε επιδιορθώσεις δε, ροή φυσικά αποτελέσει πω.
+
+Άπειρα παραπάνω φαινόμενο πω ώρα, σαν πόρτες κρατήσουν συνηθίζουν ως. Κι ώρα τρέξει είχαμε εφαρμογή. Απλό σχεδιαστής μεταγλωτιστής ας επί, τις τα όταν έγραψες γραμμής? Όλα κάνεις συνάδελφος εργαζόμενοι θα, χαρτιού χαμηλός τα ροή. Ως ναι όροφο έρθει, μην πελάτες αποφάσισε μεταφραστής με, να βιαστικά εκδόσεις αναζήτησης λες. Των φταίει εκθέσεις προσπαθήσεις οι, σπίτι αποστηθίσει ας λες?
+
+Ώς που υπηρεσία απαραίτητο δημιουργείς. Μη άρα χαρά καθώς νύχτας, πω ματ μπουν είχαν. Άμεση δημιουργείς ώς ροή, γράψει γραμμής σίγουρος στα τι! Αν αφού πρώτοι εργαζόμενων ναί.
+
+Άμεση διορθώσεις με δύο? Έχουν παράδειγμα των θα, μου έρθει θυμάμαι περισσότερο το. Ότι θα αφού χρειάζονται περισσότερες. Σαν συνεχώς περίπου οι.
+
+Ώς πρώτης πετάξαμε λες, όρο κι πρώτες ζητήσεις δυστυχής. Ανά χρόνου διακοπή επιχειρηματίες ας, ώς μόλις άτομο χειρότερα όρο, κρατάει σχεδιαστής προσπαθήσεις νέο το. Πουλάς προσθέσει όλη πω, τύπου χαρακτηριστικό εγώ σε, πω πιο δούλευε αναζήτησης? Αναφορά δίνοντας σαν μη, μάθε δεδομένη εσωτερικών με ναι, αναφέρονται περιβάλλοντος ώρα αν. Και λέει απόλαυσε τα, που το όροφο προσπαθούν?
+
+Πάντα χρόνου χρήματα ναι το, σαν σωστά θυμάμαι σκεφτείς τα. Μα αποτελέσει ανεπιθύμητη την, πιο το τέτοιο ατόμου, τη των τρόπο εργαλείων επιδιόρθωσης. Περιβάλλον παραγωγικής σου κι, κλπ οι τύπου κακόκεφους αποστηθίσει, δε των πλέον τρόποι. Πιθανότητες χαρακτηριστικών σας κι, γραφικά δημιουργήσεις μια οι, πω πολλοί εξαρτάται προσεκτικά εδώ. Σταματάς παράγοντες για' ώς, στις ρωτάει το ναι! Καρέκλα ζητήσεις συνδυασμούς τη ήδη!
+
+Για μαγικά συνεχώς ακούσει το. Σταματάς προϊόντα βουτήξουν ώς ροή. Είχαν πρώτες οι ναι, μα λες αποστηθίσει ανακαλύπτεις. Όροφο άλγεβρα παραπάνω εδώ τη, πρόσληψη λαμβάνουν καταλάθος ήδη ας? Ως και εισαγωγή κρατήσουν, ένας κακόκεφους κι μας, όχι κώδικάς παίξουν πω. Πω νέα κρατάει εκφράσουν, τότε τελικών τη όχι, ας της τρέξει αλλάζοντας αποκλειστικούς.
+
+Ένας βιβλίο σε άρα, ναι ως γράψει ταξινομεί διορθώσεις! Εδώ να γεγονός συγγραφείς, ώς ήδη διακόψουμε επιχειρηματίες? Ότι πακέτων εσφαλμένη κι, θα όρο κόλπα παραγωγικής? Αν έχω κεντρικό υψηλότερη, κι δεν ίδιο πετάνε παρατηρούμενη! Που λοιπόν σημαντικό μα, προκαλείς χειροκροτήματα ως όλα, μα επί κόλπα άγχος γραμμές! Δε σου κάνεις βουτήξουν, μη έργων επενδυτής χρησιμοποίησέ στα, ως του πρώτες διάσημα σημαντικό.
+
+Βιβλίο τεράστιο προκύπτουν σαν το, σαν τρόπο επιδιόρθωση ας. Είχαν προσοχή προσπάθεια κι ματ, εδώ ως έτσι σελίδων συζήτηση. Και στην βγαίνει εσφαλμένη με, δυστυχής παράδειγμα δε μας, από σε υόρκη επιδιόρθωσης. Νέα πω νέου πιθανό, στήλες συγγραφείς μπαίνοντας μα για', το ρωτήσει κακόκεφους της? Μου σε αρέσει συγγραφής συγχωνευτεί, μη μου υόρκη ξέχασε διακοπής! Ώς επί αποφάσισε αποκλειστικούς χρησιμοποιώντας, χρήματα σελίδων ταξινομεί ναι με.
+
+Μη ανά γραμμή απόλαυσε, πω ναι μάτσο διασφαλίζεται. Τη έξι μόλις εργάστηκε δημιουργούν, έκδοση αναφορά δυσκολότερο οι νέο. Σας ως μπορούσε παράδειγμα, αν ότι δούλευε μπορούσε αποκλειστικούς, πιο λέει βουτήξουν διορθώσει ως. Έχω τελευταία κακόκεφους ας, όσο εργαζόμενων δημιουργήσεις τα.
+
+Του αν δουλέψει μπορούσε, πετούν χαμηλός εδώ ας? Κύκλο τύπους με που, δεν σε έχουν συνεχώς χειρότερα, τις τι απαράδεκτη συνηθίζουν? Θα μην τους αυτήν, τη ένα πήρε πακέτων, κι προκύπτουν περιβάλλον πως. Μα για δουλέψει απόλαυσε εφαμοργής, ώς εδώ σημαίνει μπορούσες, άμεση ακούσει προσοχή τη εδώ?
+
+Στα δώσε αθόρυβες λιγότερους οι, δε αναγκάζονται αποκλειστικούς όλα! Ας μπουν διοικητικό μια, πάντα ελέγχου διορθώσεις ώς τον. Ότι πήρε κανόνα μα. Που άτομα κάνεις δημιουργίες τα, οι μας αφού κόλπα προγραμματιστής, αφού ωραίο προκύπτουν στα ως. Θέμα χρησιμοποιήσει αν όλα, του τα άλγεβρα σελίδων. Τα ότι ανώδυνη δυστυχώς συνδυασμούς, μας οι πάντα γνωρίζουμε ανταγωνιστής, όχι τα δοκιμάσεις σχεδιαστής! Στην συνεντεύξης επιδιόρθωση πιο τα, μα από πουλάς περιβάλλον παραγωγικής.
+
+Έχουν μεταγλωτίσει σε σας, σε πάντα πρώτης μειώσει των, γράψει ρουτίνα δυσκολότερο ήδη μα? Ταξινομεί διορθώσεις να μας. Θα της προσπαθούν περιεχόμενα, δε έχω τοπικές στέλνοντάς. Ανά δε αλφα άμεση, κάποιο ρωτάει γνωρίζουμε πω στη, φράση μαγικά συνέχεια δε δύο! Αν είχαμε μειώσει ροή, μας μετράει καθυστερούσε επιδιορθώσεις μη. Χάος υόρκη κεντρικό έχω σε, ανά περίπου αναγκάζονται πω.
+
+Όσο επιστρέφουν χρονοδιαγράμματα μη. Πως ωραίο κακόκεφος διαχειριστής ως, τις να διακοπής αναζήτησης. Κάποιο ποσοστό ταξινομεί επί τη? Μάθε άμεση αλλάζοντας δύο με, μου νέου πάντα να.
+
+Πω του δυστυχώς πιθανότητες. Κι ρωτάει υψηλότερη δημιουργια ότι, πω εισαγωγή τελευταία απομόνωση ναι. Των ζητήσεις γνωρίζουμε ώς? Για' μη παραδοτέου αναφέρονται! Ύψος παραγωγικά ροή ως, φυσικά διάβασε εικόνες όσο σε? Δεν υόρκη διορθώσεις επεξεργασία θα, ως μέση σύστημα χρησιμοποιήσει τις. \ No newline at end of file
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt
new file mode 100644
index 0000000..2443fc4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt
@@ -0,0 +1,3 @@
+रखति आवश्यकत प्रेरना मुख्यतह हिंदी किएलोग असक्षम कार्यलय करते विवरण किके मानसिक दिनांक पुर्व संसाध एवम् कुशलता अमितकुमार प्रोत्साहित जनित देखने उदेशीत विकसित बलवान ब्रौशर किएलोग विश्लेषण लोगो कैसे जागरुक प्रव्रुति प्रोत्साहित सदस्य आवश्यकत प्रसारन उपलब्धता अथवा हिंदी जनित दर्शाता यन्त्रालय बलवान अतित सहयोग शुरुआत सभीकुछ माहितीवानीज्य लिये खरिदे है।अभी एकत्रित सम्पर्क रिती मुश्किल प्राथमिक भेदनक्षमता विश्व उन्हे गटको द्वारा तकरीबन
+
+विश्व द्वारा व्याख्या सके। आजपर वातावरण व्याख्यान पहोच। हमारी कीसे प्राथमिक विचारशिलता पुर्व करती कम्प्युटर भेदनक्षमता लिये बलवान और्४५० यायेका वार्तालाप सुचना भारत शुरुआत लाभान्वित पढाए संस्था वर्णित मार्गदर्शन चुनने \ No newline at end of file
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv
new file mode 100644
index 0000000..3bd381a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDZeUdi1RKnm9cRYNn6E24xxrRTouh3Va8JOEHQ5SB018lvbjwH
+2lW5mZ/I0kh/dHsTN0zcN0VE62WIbnLreMk/af/4Pg1i93+c9TmfXmoropsmdLos
+w0tjq50jGbBqtHZNJYAokP/u3uUuRw8g0V/O4zlQ3GlO/PDH7xDQzekl9wIDAQAB
+AoGAaoCBXD5a72hbb/BNb7HaUlgscZUjYWW93bcGTGYZef8/b+m9Tl83gjhgzvlk
+db62k1eOtX3/11uskp78eqLhctv7yWc0mQQhgOogY2qCwHTCH8wja8kJkUAnKQhs
+P9sa5iJvgckiuX3SdxgTMwib9d1VyGq6YywiORiZF9rxyhECQQD/xhiZSi7y0ciB
+g4bassy0GVMS7EDRumMHc8wC23E1H2mj5yPE/QLqkW4ddmCv2BbJnYmyNvOaK9tk
+T2W+mn3/AkEA2aqDGja9CaTlY4BCXfiT166n+xVl5+d+1DENQ4FK9O2jpSi1265J
+tjEkXVxUOpV1ZEcUVOdK6RpvsKpc7vVICQJBALEFO5UsQJ4SD0GD9Ft8kCy9sj9Q
+f/Qnmc5YmIQJuKpZmVW07Y6yxcfu61U8zuIlHnBftiM/4Q18+RTN1s86QaUCQHoL
+9MTfCnH85q46/XuJZQRbp07O+bvlfqTl+CTwuyHImaiCwi2ydRxWQ6ihm4zZvuAC
+RvEwWz2HGDc73y4RlFkCQDDdnN9e46l1nMDLDI4cyyGBVg4Z2IZ3IAu5GaoUCGjM
+a8w6kxE8f1d8DD5vvqVbmfK89TA/DjT+7/arBNBCiCM=
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub
new file mode 100644
index 0000000..b503a91
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZeUdi1RKnm9cRYNn6E24xxrRT
+ouh3Va8JOEHQ5SB018lvbjwH2lW5mZ/I0kh/dHsTN0zcN0VE62WIbnLreMk/af/4
+Pg1i93+c9TmfXmoropsmdLosw0tjq50jGbBqtHZNJYAokP/u3uUuRw8g0V/O4zlQ
+3GlO/PDH7xDQzekl9wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt
new file mode 100644
index 0000000..3f35021
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt
@@ -0,0 +1 @@
+<data> \ No newline at end of file
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png b/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png
new file mode 100644
index 0000000..1b95f61
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png
Binary files differ
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip b/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip
new file mode 100644
index 0000000..5a580ec
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip
Binary files differ
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl
new file mode 100644
index 0000000..dd9818a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl
@@ -0,0 +1 @@
+D42DA34CF90FA0DE
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt
new file mode 100644
index 0000000..695f814
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIJAKJCGQYLxWT1MA0GCSqGSIb3DQEBBQUAMEwxFzAVBgNV
+BAMMDlN3aWZ0bWFpbGVyIENBMRQwEgYDVQQKDAtTd2lmdG1haWxlcjEOMAwGA1UE
+BwwFUGFyaXMxCzAJBgNVBAYTAkZSMB4XDTEzMTEyNzA4MzkxMFoXDTE3MTEyNjA4
+MzkxMFowTDEXMBUGA1UEAwwOU3dpZnRtYWlsZXIgQ0ExFDASBgNVBAoMC1N3aWZ0
+bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC7RLdHE3OWo9aZwv1xA/cYyPui/gegxpTqClRp
+gGcVQ+jxIfnJQDQndyoAvFDiqOiZ+gAjZGJeUHDp9C/2IZp05MLh+omt9N8pBykm
+3nj/3ZwPXOAO0uyDPAOHhISITAxEuZCqDnq7iYujywtwfQ7bpW1hCK9PfNZYMStM
+kw7LsGr5BqcKkPuOWTvxE3+NqK8HxydYolsoApEGhgonyImVh1Pg1Kjkt5ojvwAX
+zOdjfw5poY5NArwuLORUH+XocetRo8DC6S42HkU/MoqcYxa9EuRuwuQh7GtE6baR
+PgrDsEYaY4Asy43sK81V51F/8Q1bHZKN/goQdxQwzv+/nOLTAgMBAAGjUDBOMB0G
+A1UdDgQWBBRHgqkl543tKhsVAvcx1I0JFU7JuDAfBgNVHSMEGDAWgBRHgqkl543t
+KhsVAvcx1I0JFU7JuDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAz
+OJiEQcygKGkkXXDiXGBvP/cSznj3nG9FolON0yHUBgdvLfNnctRMStGzPke0siLt
+RJvjqiL0Uw+blmLJU8lgMyLJ9ctXkiLJ/WflabN7VzmwYRWe5HzafGQJAg5uFjae
+VtAAHQgvbmdXB6brWvcMQmB8di7wjVedeigZvkt1z2V0FtBy8ybJaT5H6bX9Bf5C
+dS9r4mLhk/0ThthpRhRxsmupSL6e49nJaIk9q0UTEQVnorJXPcs4SPTIY51bCp6u
+cOebhNgndSxCiy0zSD7vRjNiyB/YNGZ9Uv/3DNTLleMZ9kZgfoKVpwYKrRL0IFT/
+cfS2OV1wxRxq668qaOfK
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key
new file mode 100644
index 0000000..df67470
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAu0S3RxNzlqPWmcL9cQP3GMj7ov4HoMaU6gpUaYBnFUPo8SH5
+yUA0J3cqALxQ4qjomfoAI2RiXlBw6fQv9iGadOTC4fqJrfTfKQcpJt54/92cD1zg
+DtLsgzwDh4SEiEwMRLmQqg56u4mLo8sLcH0O26VtYQivT3zWWDErTJMOy7Bq+Qan
+CpD7jlk78RN/jaivB8cnWKJbKAKRBoYKJ8iJlYdT4NSo5LeaI78AF8znY38OaaGO
+TQK8LizkVB/l6HHrUaPAwukuNh5FPzKKnGMWvRLkbsLkIexrROm2kT4Kw7BGGmOA
+LMuN7CvNVedRf/ENWx2Sjf4KEHcUMM7/v5zi0wIDAQABAoIBAGyaWkvu/O7Uz2TW
+z1JWgVuvWzfYaKYV5FCicvfITn/npVUKZikPge+NTR+mFqaMXHDHqoLb+axGrGUR
+hysPq9q0vEx/lo763tyVWYlAJh4E8Dd8njganK0zBbz23kGJEOheUYY95XGTQBda
+bqTq8c3x7zAB8GGBvXDh+wFqm38GLyMF6T+YEzWJZqXfg31f1ldRvf6+VFwlLfz6
+cvTR7oUpYIsUeGE47kBs13SN7Oju6a355o/7wy9tOCRiu+r/ikXFh8rFGLfeTiwv
+R1dhYjcEYGxZUD8u64U+Cj4qR1P0gHJL0kbh22VMMqgALOc8FpndkjNdg1Nun2X8
+BWpsPwECgYEA7C9PfTOIZfxGBlCl05rmWex++/h5E5PbH1Cw/NGjIH1HjmAkO3+5
+WyMXhySOJ8yWyCBQ/nxqc0w7+TO4C7wQcEdZdUak25KJ74v0sfmWWrVw6kcnLU6k
+oawW/L2F2w7ET3zDoxKh4fOF34pfHpSbZk7XJ68YOfHpYVnP4efkQVMCgYEAyvrM
+KA7xjnsKumWh206ag3QEI0M/9uPHWmrh2164p7w1MtawccZTxYYJ5o5SsjTwbxkf
+0cAamp4qLInmRUxU1gk76tPYC3Ndp6Yf1C+dt0q/vtzyJetCDrdz8HHT1SpKbW0l
+g6z1I5FMwa6oWvWsfS++W51vsxUheNsOJ4uxKIECgYBwM7GRiw+7U3N4wItm0Wmp
+Qp642Tu7vzwTzmOmV3klkB6UVrwfv/ewgiVFQGqAIcNn42JW44g2qfq70oQWnws4
+K80l15+t6Bm7QUPH4Qg6o4O26IKGFZxEadqpyudyP7um/2B5cfqRuvzYS4YQowyI
+N+AirB3YOUJjyyTk7yMSnQKBgGNLpSvDg6+ryWe96Bwcq8G6s3t8noHsk81LlAl4
+oOSNUYj5NX+zAbATDizXWuUKuMPgioxVaa5RyVfYbelgme/KvKD32Sxg12P4BIIM
+eR79VifMdjjOiZYhcHojdPlGovo89qkfpxwrLF1jT8CPhj4HaRvwPIBiyekRYC9A
+Sv4BAoGAXCIC1xxAJP15osUuQjcM8KdsL1qw+LiPB2+cJJ2VMAZGV7CR2K0aCsis
+OwRaYM0jZKUpxzp1uwtfrfqbhdYsv+jIBkfwoShYZuo6MhbUrj0sffkhJC3WrT2z
+xafCFLFv1idzGvvNxatlp1DNKrndG2NS3syVAox9MnL5OMsvGM8=
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh
new file mode 100644
index 0000000..0454f20
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+openssl genrsa -out CA.key 2048
+openssl req -x509 -new -nodes -key CA.key -days 1460 -subj '/CN=Swiftmailer CA/O=Swiftmailer/L=Paris/C=FR' -out CA.crt
+openssl x509 -in CA.crt -clrtrust -out CA.crt
+
+openssl genrsa -out sign.key 2048
+openssl req -new -key sign.key -subj '/CN=Swiftmailer-User/O=Swiftmailer/L=Paris/C=FR' -out sign.csr
+openssl x509 -req -in sign.csr -CA CA.crt -CAkey CA.key -out sign.crt -days 1460 -addtrust emailProtection
+openssl x509 -in sign.crt -clrtrust -out sign.crt
+
+rm sign.csr
+
+openssl genrsa -out intermediate.key 2048
+openssl req -new -key intermediate.key -subj '/CN=Swiftmailer Intermediate/O=Swiftmailer/L=Paris/C=FR' -out intermediate.csr
+openssl x509 -req -in intermediate.csr -CA CA.crt -CAkey CA.key -set_serial 01 -out intermediate.crt -days 1460
+openssl x509 -in intermediate.crt -clrtrust -out intermediate.crt
+
+rm intermediate.csr
+
+openssl genrsa -out sign2.key 2048
+openssl req -new -key sign2.key -subj '/CN=Swiftmailer-User2/O=Swiftmailer/L=Paris/C=FR' -out sign2.csr
+openssl x509 -req -in sign2.csr -CA intermediate.crt -CAkey intermediate.key -set_serial 01 -out sign2.crt -days 1460 -addtrust emailProtection
+openssl x509 -in sign2.crt -clrtrust -out sign2.crt
+
+rm sign2.csr
+
+openssl genrsa -out encrypt.key 2048
+openssl req -new -key encrypt.key -subj '/CN=Swiftmailer-User/O=Swiftmailer/L=Paris/C=FR' -out encrypt.csr
+openssl x509 -req -in encrypt.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out encrypt.crt -days 1460 -addtrust emailProtection
+openssl x509 -in encrypt.crt -clrtrust -out encrypt.crt
+
+rm encrypt.csr
+
+openssl genrsa -out encrypt2.key 2048
+openssl req -new -key encrypt2.key -subj '/CN=Swiftmailer-User2/O=Swiftmailer/L=Paris/C=FR' -out encrypt2.csr
+openssl x509 -req -in encrypt2.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out encrypt2.crt -days 1460 -addtrust emailProtection
+openssl x509 -in encrypt2.crt -clrtrust -out encrypt2.crt
+
+rm encrypt2.csr
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt
new file mode 100644
index 0000000..7435855
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFjCCAf4CCQDULaNM+Q+g3TANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T
+d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh
+cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTFaFw0xNzExMjYwODM5MTFa
+ME4xGTAXBgNVBAMMEFN3aWZ0bWFpbGVyLVVzZXIxFDASBgNVBAoMC1N3aWZ0bWFp
+bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCcNO+fVZBT2znmVwXXZ08n3G5WA1kyvqh9z4RBBZOD
+V46Gc1X9MMXr9+wzZBFkAckKaa6KsTkeUr4pC8XUBpQnakxH/kW9CaDPdOE+7wNo
+FkPfc6pjWWgpAVxdkrtk7pb4/aGQ++HUkqVu0cMpIcj/7ht7H+3QLZHybn+oMr2+
+FDnn8vPmHxVioinSrxKTlUITuLWS9ZZUTrDa0dG8UAv55A/Tba4T4McCPDpJSA4m
+9jrW321NGQUntQoItOJxagaueSvh6PveGV826gTXoU5X+YJ3I2OZUEQ2l6yByAzf
+nT+QlxPj5ikotFwL72HsenYtetynOO/k43FblAF/V/l7AgMBAAEwDQYJKoZIhvcN
+AQEFBQADggEBAJ048Sdb9Sw5OJM5L00OtGHgcT1B/phqdzSjkM/s64cg3Q20VN+F
+fZIIkOnxgyYWcpOWXcdNw2tm5OWhWPGsBcYgMac7uK/ukgoOJSjICg+TTS5kRo96
+iHtmImqkWc6WjNODh7uMnQ6DsZsscdl7Bkx5pKhgGnEdHr5GW8sztgXgyPQO5LUs
+YzCmR1RK1WoNMxwbPrGLgYdcpJw69ns5hJbZbMWwrdufiMjYWvTfBPABkk1JRCcY
+K6rRTAx4fApsw1kEIY8grGxyAzfRXLArpro7thJr0SIquZ8GpXkQT/mgRR8JD9Hp
+z9yhr98EnKzITE/yclGN4pUsuk9S3jiyzUU=
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key
new file mode 100644
index 0000000..aa620ca
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAnDTvn1WQU9s55lcF12dPJ9xuVgNZMr6ofc+EQQWTg1eOhnNV
+/TDF6/fsM2QRZAHJCmmuirE5HlK+KQvF1AaUJ2pMR/5FvQmgz3ThPu8DaBZD33Oq
+Y1loKQFcXZK7ZO6W+P2hkPvh1JKlbtHDKSHI/+4bex/t0C2R8m5/qDK9vhQ55/Lz
+5h8VYqIp0q8Sk5VCE7i1kvWWVE6w2tHRvFAL+eQP022uE+DHAjw6SUgOJvY61t9t
+TRkFJ7UKCLTicWoGrnkr4ej73hlfNuoE16FOV/mCdyNjmVBENpesgcgM350/kJcT
+4+YpKLRcC+9h7Hp2LXrcpzjv5ONxW5QBf1f5ewIDAQABAoIBADmuMm2botfUM+Ui
+bT3FIC2P8A5C3kUmsgEDB8sazAXL5w0uuanswKkJu2aepO1Q23PE4nbESlswIpf1
+iO9qHnsPfWt4MThEveTdO++JQrDEx/tTMq/M6/F4VysWa6wxjf4Taf2nhRSBsiTh
+wDcICri2q98jQyWELkhfFTR+yCHPsn6iNtzE2OpNv9ojKiSqck/sVjC39Z+uU/HD
+N4v0CPf9pDGkO+modaVGKf2TpvZT7Hpq/jsPzkk1h7BY7aWdZiIY4YkBkWYqZk8f
+0dsxKkOR2glfuEYNtcywG+4UGx3i1AY0mMu96hH5M1ACFmFrTCoodmWDnWy9wUpm
+leLmG8ECgYEAywWdryqcvLyhcmqHbnmUhCL9Vl4/5w5fr/5/FNvqArxSGwd2CxcN
+Jtkvu22cxWAUoe155eMc6GlPIdNRG8KdWg4sg0TN3Jb2jiHQ3QkHXUJlWU6onjP1
+g2n5h052JxVNGBEb7hr3U7ZMW6wnuYnGdYwCB9P3r5oGxxtfVRB8ygUCgYEAxPfy
+tAd3SNT8Sv/cciw76GYKbztUjJRXkLo6GOBGq/AQxP1NDWMuL2AES11YIahidMsF
+TMmM+zhkNHsd5P69p87FTMWx0cLoH0M9iQNK7Q6C1luTjLf5DTFuk+nHGErM4Drs
++6Ly1Z4KLXfXgBDD8Ce6U9+W3RrCc36poGZvjX8CgYEAna0P6WJr9r19mhIYevmc
+Gf/ex7xNXxMvx80dP8MIfPVrwyhJSpWtljVpt+SKtFRJ0fVRDfUUl4Bqf/fR74B3
+muCVO6ItTBxHAt5Ki9CeUpTlh7XqiWwLSvP8Y1TRuMr3ZDCtg4CYBAD6Ttxmwde6
+NcL2NMQwgsZaazrcEIHMmU0CgYEAl/Mn2tZ/oUIdt8YWzEVvmeNOXW0J1sGBo/bm
+ZtZt7qpuZWl7jb5bnNSXu4QxPxXljnAokIpUJmHke9AWydfze4c6EfXZLhcMd0Gq
+MQ7HOIWfTbqr4zzx9smRoq4Ql57s2nba521XpJAdDeKL7xH/9j7PsXCls8C3Dd5D
+AajEmgUCgYAGEdn6tYxIdX7jF39E3x7zHQf8jHIoQ7+cLTLtd944mSGgeqMfbiww
+CoUa+AAUqjdAD5ViAyJrA+gmDtWpkFnJZtToXYwfUF2o3zRo4k1DeBrVbFqwSQkE
+omrfiBGtviYIPdqQLE34LYpWEooNPraqO9qTyc+9w5038u2OFS+WmQ==
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt
new file mode 100644
index 0000000..6908165
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFzCCAf8CCQDULaNM+Q+g3jANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T
+d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh
+cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTJaFw0xNzExMjYwODM5MTJa
+ME8xGjAYBgNVBAMMEVN3aWZ0bWFpbGVyLVVzZXIyMRQwEgYDVQQKDAtTd2lmdG1h
+aWxlcjEOMAwGA1UEBwwFUGFyaXMxCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAw4AoYVYss2sa1BWJAJpK6gVemjXrp1mVXVpb1/z6
+SH15AGsp3kiNXsMpgvsdofbqC/5HXrw2G8gWqo+uh6GuK67+Tvp7tO2aD4+8CZzU
+K1cffj7Pbx95DUPwXckv79PT5ZcuyeFaVo92aug11+gS/P8n0WXSlzZxNuZ1f3G2
+r/IgwfNKZlarEf1Ih781L2SwmyveW/dtsV2pdrd4IZwsV5SOF2zBFIXSuhPN0c+m
+mtwSJe+Ow1udLX4KJkAX8sGVFJ5P5q4s2nS9vLkkj7X6YRQscbyJO9L7e1TksRqL
+DLxZwiko6gUhp4/bIs1wDj5tzkQBi4qXviRq3i7A9b2d0QIDAQABMA0GCSqGSIb3
+DQEBBQUAA4IBAQAj8iARhPB2DA3YfT5mJJrgU156Sm0Z3mekAECsr+VqFZtU/9Dz
+pPFYEf0hg61cjvwhLtOmaTB+50hu1KNNlu8QlxAfPJqNxtH85W0CYiZHJwW9eSTr
+z1swaHpRHLDUgo3oAXdh5syMbdl0MWos0Z14WP5yYu4IwJXs+j2JRW70BICyrNjm
+d+AjCzoYjKMdJkSj4uxQEOuW2/5veAoDyU+kHDdfT7SmbyoKu+Pw4Xg/XDuKoWYg
+w5/sRiw5vxsmOr9+anspDHdP9rUe1JEfwAJqZB3fwdqEyxu54Xw/GedG4wZBEJf0
+ZcS1eh31emcjYUHQa1IA93jcFSmXzJ+ftJrY
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key
new file mode 100644
index 0000000..e322a8f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAw4AoYVYss2sa1BWJAJpK6gVemjXrp1mVXVpb1/z6SH15AGsp
+3kiNXsMpgvsdofbqC/5HXrw2G8gWqo+uh6GuK67+Tvp7tO2aD4+8CZzUK1cffj7P
+bx95DUPwXckv79PT5ZcuyeFaVo92aug11+gS/P8n0WXSlzZxNuZ1f3G2r/IgwfNK
+ZlarEf1Ih781L2SwmyveW/dtsV2pdrd4IZwsV5SOF2zBFIXSuhPN0c+mmtwSJe+O
+w1udLX4KJkAX8sGVFJ5P5q4s2nS9vLkkj7X6YRQscbyJO9L7e1TksRqLDLxZwiko
+6gUhp4/bIs1wDj5tzkQBi4qXviRq3i7A9b2d0QIDAQABAoIBAH8RvK1PmqxfkEeL
+W8oVf13OcafgJjRW6NuNkKa5mmAlldFs1gDRvXl7dm7ZE3CjkYqMEw2DXdP+4KSp
+0TH9J7zi+A6ThnaZ/QniTcEdu1YUQbcH0kIS/dZec0wyKUNDtrXC5zl2jQY4Jyrj
+laOpBzaEDfhvq0p3q2yYrIRSgACpSEVEsfPoHrxtlLhfMkVNe8P0nkQkzdwou5MQ
+MZKV4JUopLHLgPH6IXQCqA1wzlU32yZ86w88GFcBVLkwlLJCKbuAo7yxMCD+nzvA
+xm5NuF1kzpP0gk+kZRXF+rFEV4av/2kSS+n8IeUBQZrxovLBuQHVDvJXoqcEjmlh
+ZUltznUCgYEA4inwieePfb7kh7L/ma5OLLn+uCNwzVw9LayzXT1dyPravOnkHl6h
+MgaoTspqDyU8k8pStedRrr5dVYbseni/A4WSMGvi4innqSXBQGp64TyeJy/e+LrS
+ypSWQ6RSJkCxI5t8s4mOpR7FMcdE34I5qeA4G5RS1HIacn7Hxc7uXtcCgYEA3Uqn
+E7EDfNfYdZm6AikvE6x64oihWI0x47rlkLu6lf6ihiF1dbfaEN+IAaIxQ/unGYwU
+130F0TUwarXnVkeBIRlij4fXhExyd7USSQH1VpqmIqDwsS2ojrzQVMo5UcH+A22G
+bbHPtwJNmw8a7yzTPWo2/vnjgV2OaXEQ9vCVG5cCgYEAu1kEoihJDGBijSqxY4wp
+xBE7OSxamDNtlnV2i6l3FDMBmfaieqnnHDq5l7NDklJFUSQLyhXZ60hUprHDGV0G
+1pMCW8wzQSh3d/4HjSXnrsd5N3sHWMHiNeBKbbQkPP3f/2AhN9SebpgDwE2S9xe4
+TsmnkOkYiFYRJIFzWaAmhDcCgYEAwxRCgZt0xaPKULG6RpljxOYyVm24PsYKCwYB
+xjuYWw5k2/W3BJWVCXblAPuojpPUVTMmVGkErc9D5W6Ch471iOZF+t334cs6xci8
+W9v8GeKvPqu+Q5NKmrpctcKoESkA8qik7yLnSCAhpeYFCn/roKJ35QMJyktddhqU
+p/yilfUCgYBxZ6YmFjYH6l5SxQdcfa5JQ2To8lZCfRJwB65EyWj4pKH4TaWFS7vb
+50WOGTBwJgyhTKLCO3lOmXIUyIwC+OO9xzaeRCBjqEhpup/Ih3MsfMEd6BZRVK5E
+IxtmIWba5HQ52k8FKHeRrRB7PSVSADUN2pUFkLudH+j/01kSZyJoLA==
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt
new file mode 100644
index 0000000..012f734
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFjCCAf4CAQEwDQYJKoZIhvcNAQEFBQAwTDEXMBUGA1UEAwwOU3dpZnRtYWls
+ZXIgQ0ExFDASBgNVBAoMC1N3aWZ0bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkG
+A1UEBhMCRlIwHhcNMTQxMTIwMTMyNTQxWhcNMTgxMTE5MTMyNTQxWjBWMSEwHwYD
+VQQDDBhTd2lmdG1haWxlciBJbnRlcm1lZGlhdGUxFDASBgNVBAoMC1N3aWZ0bWFp
+bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDSgEhftX6f1wV+uqWl4J+zwCn8fHaLZT6GZ0Gs9ThE
+4e+4mkLG1rvSEIJon8U0ic8Zph1UGa1Grveh5bgbldHlFxYSsCCyDGgixRvRWNhI
+KuO+SxaIZChqqKwVn3aNQ4BZOSo/MjJ/jQyr9BMgMmdxlHR3e1wkkeAkW//sOsfu
+xQGF1h9yeQvuu/GbG6K7vHSGOGd5O3G7bftfQ7l78TMqeJ7jV32AdJeuO5MD4dRn
+W4CQLTaeribLN0MKn35UdSiFoZxKHqqWcgtl5xcJWPOmq6CsAJ2Eo90kW/BHOrLv
+10h6Oan9R1PdXSvSCvVnXY3Kz30zofw305oA/KJk/hVzAgMBAAEwDQYJKoZIhvcN
+AQEFBQADggEBABijZ2NNd05Js5VFNr4uyaydam9Yqu/nnrxbPRbAXPlCduydu2Gd
+d1ekn3nblMJ87Bc7zVyHdAQD8/AfS1LOKuoWHpTzmlpIL+8T5sbCYG5J1jKdeLkh
+7L/UD5v1ACgA33oKtN8GzcrIq8Zp73r0n+c3hFCfDYRSZRCxGyIf3qgU2LBOD0A3
+wTff/N8E/p3WaJX9VnuQ7xyRMOubDuqJnlo5YsFv7wjyGOIAz9afZzcEbH6czt/t
+g0Xc/kGr/fkAjUu+z3ZfE4247Gut5m3hEVwWkpEEzQo4osX/BEX20Q2nPz9WBq4a
+pK3qNNGwAqS4gdE3ihOExMWxAKgr9d2CcU4=
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key
new file mode 100644
index 0000000..569eb0c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA0oBIX7V+n9cFfrqlpeCfs8Ap/Hx2i2U+hmdBrPU4ROHvuJpC
+xta70hCCaJ/FNInPGaYdVBmtRq73oeW4G5XR5RcWErAgsgxoIsUb0VjYSCrjvksW
+iGQoaqisFZ92jUOAWTkqPzIyf40Mq/QTIDJncZR0d3tcJJHgJFv/7DrH7sUBhdYf
+cnkL7rvxmxuiu7x0hjhneTtxu237X0O5e/EzKnie41d9gHSXrjuTA+HUZ1uAkC02
+nq4myzdDCp9+VHUohaGcSh6qlnILZecXCVjzpqugrACdhKPdJFvwRzqy79dIejmp
+/UdT3V0r0gr1Z12Nys99M6H8N9OaAPyiZP4VcwIDAQABAoIBAQDLJiKyu2XIvKsA
+8wCKZY262+mpUjTVso/1BhHL6Zy0XZgMgFORsgrxYB16+zZGzfiguD/1uhIP9Svn
+gtt7Q8udW/phbrkfG/okFDYUg7m3bCz+qVjFqGOZC8+Hzq2LB2oGsbSj6L3zexyP
+lq4elIZghvUfml4CrQW0EVWbld79/kF7XHABcIOk2+3f63XAQWkjdFNxj5+z6TR0
+52Rv7SmRioAsukW9wr77G3Luv/0cEzDFXgGW5s0wO+rJg28smlsIaj+Y0KsptTig
+reQvReAT/S5ZxEp4H6WtXQ1WmaliMB0Gcu4TKB0yE8DoTeCePuslo9DqGokXYT66
+oqtcVMqBAoGBAPoOL9byNNU/bBNDWSCiq8PqhSjl0M4vYBGqtgMXM4GFOJU+W2nX
+YRJbbxoSd/DKjnxEsR6V0vDTDHj4ZSkgmpEmVhEdAiwUwaZ0T8YUaCPhdiAENo5+
+zRBWVJcvAC2XKTK1hy5D7Z5vlC32HHygYqitU+JsK4ylvhrdeOcGx5cfAoGBANeB
+X0JbeuqBEwwEHZqYSpzmtB+IEiuYc9ARTttHEvIWgCThK4ldAzbXhDUIQy3Hm0sL
+PzDA33furNl2WwB+vmOuioYMNjArKrfg689Aim1byg4AHM5XVQcqoDSOABtI55iP
+E0hYDe/d4ema2gk1uR/mT4pnLnk2VzRKsHUbP9stAoGBAKjyIuJwPMADnMqbC0Hg
+hnrVHejW9TAJlDf7hgQqjdMppmQ3gF3PdjeH7VXJOp5GzOQrKRxIEABEJ74n3Xlf
+HO+K3kWrusb7syb6mNd0/DOZ5kyVbCL0iypJmdeXmuAyrFQlj9LzdD1Cl/RBv1d4
+qY/bo7xsZzQc24edMU2uJ/XzAoGBAMHChA95iK5HlwR6vtM8kfk4REMFaLDhxV8R
+8MCeyp33NQfzm91JT5aDd07nOt9yVGHInuwKveFrKuXq0C9FxZCCYfHcEOyGI0Zo
+aBxTfyKMIMMtvriXNM/Yt2oJMndVuUUlfsTQxtcfu/r5S4h0URopTOK3msVI4mcV
+sEnaUjORAoGAGDnslKYtROQMXTe4sh6CoJ32J8UZVV9M+8NLus9rO0v/eZ/pIFxo
+MXGrrrl51ScqahCQ+DXHzpLvInsdlAJvDP3ymhb7H2xGsyvb3x2YgsLmr1YVOnli
+ISbCssno3vZyFU1TDjeEIKqZHc92byHNMjMuhmlaA25g8kb0cCO76EA=
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt
new file mode 100644
index 0000000..15fd65d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFjCCAf4CCQDULaNM+Q+g3DANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T
+d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh
+cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTBaFw0xNzExMjYwODM5MTBa
+ME4xGTAXBgNVBAMMEFN3aWZ0bWFpbGVyLVVzZXIxFDASBgNVBAoMC1N3aWZ0bWFp
+bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCTe8ZouyjVGgqlljhaswYqLj7icMoHq+Qg13CE+zJg
+tl2/UzyPhAd3WWOIvlQ0lu+E/n0bXrS6+q28DrQ3UgJ9BskzzLz15qUO12b92AvG
+vLJ+9kKuiM5KXDljOAsXc7/A9UUGwEFA1D0mkeMmkHuiQavAMkzBLha22hGpg/hz
+VbE6W9MGna0szd8yh38IY1M5uR+OZ0dG3KbVZb7H3N0OLOP8j8n+4YtAGAW+Onz/
+2CGPfZ1kaDMvY/WTZwyGeA4FwCPy1D8tfeswqKnWDB9Sfl8hns5VxnoJ3dqKQHeX
+iC4OMfQ0U4CcuM5sVYJZRNNwP7/TeUh3HegnOnuZ1hy9AgMBAAEwDQYJKoZIhvcN
+AQEFBQADggEBAAEPjGt98GIK6ecAEat52aG+8UP7TuZaxoH3cbZdhFTafrP8187F
+Rk5G3LCPTeA/QIzbHppA4fPAiS07OVSwVCknpTJbtKKn0gmtTZxThacFHF2NlzTH
+XxM5bIbkK3jzIF+WattyTSj34UHHfaNAmvmS7Jyq6MhjSDbcQ+/dZ9eo2tF/AmrC
++MBhyH8aUYwKhTOQQh8yC11niziHhGO99FQ4tpuD9AKlun5snHq4uK9AOFe8VhoR
+q2CqX5g5v8OAtdlvzhp50IqD4BNOP+JrUxjGLHDG76BZZIK2Ai1eBz+GhRlIQru/
+8EhQzd94mdFEPblGbmuD2QXWLFFKLiYOwOc=
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key
new file mode 100644
index 0000000..b3d3c53
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAk3vGaLso1RoKpZY4WrMGKi4+4nDKB6vkINdwhPsyYLZdv1M8
+j4QHd1ljiL5UNJbvhP59G160uvqtvA60N1ICfQbJM8y89ealDtdm/dgLxryyfvZC
+rojOSlw5YzgLF3O/wPVFBsBBQNQ9JpHjJpB7okGrwDJMwS4WttoRqYP4c1WxOlvT
+Bp2tLM3fMod/CGNTObkfjmdHRtym1WW+x9zdDizj/I/J/uGLQBgFvjp8/9ghj32d
+ZGgzL2P1k2cMhngOBcAj8tQ/LX3rMKip1gwfUn5fIZ7OVcZ6Cd3aikB3l4guDjH0
+NFOAnLjObFWCWUTTcD+/03lIdx3oJzp7mdYcvQIDAQABAoIBAH2vrw/T6GFrlwU0
+twP8q1VJIghCDLpq77hZQafilzU6VTxWyDaaUu6QPDXt1b8Xnjnd02p+1FDAj0zD
+zyuR9VLtdIxzf9mj3KiAQ2IzOx3787YlUgCB0CQo4jM/MJyk5RahL1kogLOp7A8x
+pr5XxTUq+B6L/0Nmbq8XupOXRyWp53amZ5N8sgWDv4oKh9fqgAhxbSG6KUkTmhYs
+DLinWg86Q28pSn+eivf4dehR56YwtTBVguXW3WKO70+GW1RotSrS6e6SSxfKYksZ
+a7/J1hCmJkEE3+4C8BpcI0MelgaK66ocN0pOqDF9ByxphARqyD7tYCfoS2P8gi81
+XoiZJaECgYEAwqx4AnDX63AANsfKuKVsEQfMSAG47SnKOVwHB7prTAgchTRcDph1
+EVOPtJ+4ssanosXzLcN/dCRlvqLEqnKYAOizy3C56CyRguCpO1AGbRpJjRmHTRgA
+w8iArhM07HgJ3XLFn99V/0bsPCMxW8dje1ZMjKjoQtDrXRQMtWaVY+UCgYEAwfGi
+f0If6z7wJj9gQUkGimWDAg/bxDkvEeh3nSD/PQyNiW0XDclcb3roNPQsal2ZoMwt
+f1bwkclw7yUCIZBvXWEkZapjKCdseTp6nglScxr8GAzfN9p5KQl+OS3GzC6xZf6C
+BsZQ5ucsHTHsCAi3WbwGK829z9c7x0qRwgwu9/kCgYEAsqwEwYi8Q/RZ3e1lXC9H
+jiHwFi6ugc2XMyoJscghbnkLZB54V1UKLUraXFcz97FobnbsCJajxf8Z+uv9QMtI
+Q51QV2ow1q0BKHP2HuAF5eD4nK5Phix/lzHRGPO74UUTGNKcG22pylBXxaIvTSMl
+ZTABth/YfGqvepBKUbvDZRkCgYB5ykbUCW9H6D8glZ3ZgYU09ag+bD0CzTIs2cH7
+j1QZPz/GdBYNF00PyKv3TPpzVRH7cxyDIdJyioB7/M6Iy03T4wPbQBOCjLdGrZ2A
+jrQTCngSlkq6pVx+k7KLL57ua8gFF70JihIV3kfKkaX6KZcSJ8vsSAgRc8TbUo2T
+wNjh6QKBgDyxw4bG2ULs+LVaHcnp7nizLgRGXJsCkDICjla6y0eCgAnG8fSt8CcG
+s5DIfJeVs/NXe/NVNuVrfwsUx0gBOirtFwQStvi5wJnY/maGAyjmgafisNFgAroT
+aM5f+wyGPQeGCs7bj7JWY7Nx9lkyuUV7DdKBTZNMOe51K3+PTEL3
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt
new file mode 100644
index 0000000..44f4d9b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgECAQEwDQYJKoZIhvcNAQEFBQAwVjEhMB8GA1UEAwwYU3dpZnRtYWls
+ZXIgSW50ZXJtZWRpYXRlMRQwEgYDVQQKDAtTd2lmdG1haWxlcjEOMAwGA1UEBwwF
+UGFyaXMxCzAJBgNVBAYTAkZSMB4XDTE0MTEyMDEzMjYyNloXDTE4MTExOTEzMjYy
+NlowTzEaMBgGA1UEAwwRU3dpZnRtYWlsZXItVXNlcjIxFDASBgNVBAoMC1N3aWZ0
+bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDbr1m4z/rzFS/DxUUQIhKNx19oAeGYLt3niaEP
+twfvBMNB80gMgM9d+XtqrPAMPeY/2C8t5NlChNPKMcR70JBKdmlSH4/aTjaIfWmD
+PoZJjvRRXINZgSHNKIt4ZGAN/EPFr19CBisV4iPxzu+lyIbbkaZJ/qtyatlP7m/q
+8TnykFRlyxNEveCakpcXeRd3YTFGKWoED+/URhVc0cCPZVjoeSTtPHAYBnC29lG5
+VFbq6NBQiyF4tpjOHRarq6G8PtQFH9CpAZg5bPk3bqka9C8mEr5jWfrM4EHtUkTl
+CwVLOQRBsz/nMBT27pXZh18GU0hc3geNDN4kqaeqgNBo0mblAgMBAAEwDQYJKoZI
+hvcNAQEFBQADggEBAAHDMuv6oxWPsTQWWGWWFIk7QZu3iogMqFuxhhQxg8BE37CT
+Vt1mBVEjYGMkWhMSwWBMWuP6yuOZecWtpp6eOie/UKGg1XoW7Y7zq2aQaP7YPug0
+8Lgq1jIo7iO2b6gZeMtLiTZrxyte0z1XzS3wy7ZC9mZjYd7QE7mZ+/rzQ0x5zjOp
+G8b3msS/yYYJCMN+HtHln++HOGmm6uhvbsHTfvvZvtl7F5vJ5WhGGlUfjhanSEtZ
+1RKx+cbgIv1eFOGO1OTuZfEuKdLb0T38d/rjLeI99nVVKEIGtLmX4dj327GHe/D3
+aPr2blF2gOvlzkfN9Vz6ZUE2s3rVBeCg2AVseYQ=
+-----END CERTIFICATE-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key
new file mode 100644
index 0000000..ffb189b
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA269ZuM/68xUvw8VFECISjcdfaAHhmC7d54mhD7cH7wTDQfNI
+DIDPXfl7aqzwDD3mP9gvLeTZQoTTyjHEe9CQSnZpUh+P2k42iH1pgz6GSY70UVyD
+WYEhzSiLeGRgDfxDxa9fQgYrFeIj8c7vpciG25GmSf6rcmrZT+5v6vE58pBUZcsT
+RL3gmpKXF3kXd2ExRilqBA/v1EYVXNHAj2VY6Hkk7TxwGAZwtvZRuVRW6ujQUIsh
+eLaYzh0Wq6uhvD7UBR/QqQGYOWz5N26pGvQvJhK+Y1n6zOBB7VJE5QsFSzkEQbM/
+5zAU9u6V2YdfBlNIXN4HjQzeJKmnqoDQaNJm5QIDAQABAoIBAAM2FvuqnqJ7Bs23
+zoCj3t2PsodUr7WHydqemmoeZNFLoocORVlZcK6Q/QrcKE4lgX4hbN8g30QnqOjl
+vVeJ/vH3tSZsK7AnQIjSPH6cpV3h5xRhY9IlHxdepltGLFlH/L2hCKVwbaTOP3RD
+cCFeQwpmoKWoQV1UzoRqmdw3Vn+DMaUULomLVR9aSW9PnKeFL+tPWShf7GmVISfM
+2H6xKw/qT0XAX59ZHA1laxSFVvbV5ZcKrBOFMV407Vzw2d3ojmfEzNsHjUVBXX8j
+B5nK1VeJiTVmcoVhnRX7tXESDaZy+Kv38pqOmc8Svn70lDJ35SM2EpWnX39w5LsQ
+29NsIUECgYEA/vNKiMfVmmZNQrpcuHQe5adlmz9+I4xJ4wbRzrS7czpbKF0/iaPf
+dKoVz67yYHOJCBHTVaXWkElQsq1mkyuFt/cc0ReJXO8709+t+6ULsE50cLQm/HN5
+npg3gw0Ls/9dy/cHM5SdVIHMBm9oQ65rXup/dqWC8Dz2cAAOQhIPwx0CgYEA3Jbk
+DPdUlrj4sXcE3V/CtmBuK9Xq1xolJt026fYCrle0YhdMKmchRBDCc6BzM+F/vDyC
+llPfQu8TDXK40Oan7GbxMdoLqKK9gSIq1dvfG1YMMz8OrBcX8xKe61KFRWd7QSBJ
+BcY575NzYHapOHVGnUJ68j8zCow0gfb7q6iK4GkCgYEAz2mUuKSCxYL21hORfUqT
+HFjMU7oa38axEa6pn9XvLjZKlRMPruWP1HTPG9ADRa6Yy+TcnrA1V9sdeM+TRKXC
+usCiRAU27lF+xccS30gNs1iQaGRX10gGqJzDhK1nWP+nClmlFTSRrn+OQan/FBjh
+Jy31lsveM54VC1cwQlY5Vo0CgYEArtjfnLNzFiE55xjq/znHUd4vlYlzItrzddHE
+lEBOsbiNH29ODRI/2P7b0uDsT8Q/BoqEC/ohLqHn3TIA8nzRv91880HdGecdBL17
+bJZiSv2yn/AshhWsAxzQYMDBKFk05lNb7jrIc3DR9DU6PqketsoaP+f+Yi7t89I8
+fD0VD3kCgYAaJCoQshng/ijiHF/RJXLrXXHJSUmaOfbweX/mzFup0YR1LxUjcv85
+cxvwc41Y2iI5MwUXyX97/GYKeoobzWZy3XflNWtg04rcInVaPsb/OOFDDqI+MkzT
+B4PcCurOmjzcxHMVE34CYvl3YVwWrPb5JO1rYG9T2gKUJnLU6qG4Bw==
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default b/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default
new file mode 100644
index 0000000..5717c98
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ Swift Mailer V4 accpetance test configuration.
+
+ YOU ONLY NEED TO EDIT THIS FILE IF YOU WISH TO RUN THE ACCEPTANCE TESTS.
+
+ The acceptance tests are run by default when "All Tests" are run with the
+ testing suite, however, without configuration options here only the unit tests
+ will be run and the acceptance tests will be skipped.
+
+ You can fill out only the parts you know and leave the other bits.
+ */
+
+/*
+ Defines: The name and port of a SMTP server you can connect to.
+ Recommended: smtp.gmail.com:25
+ */
+define('SWIFT_SMTP_HOST', 'localhost:4456');
+
+/*
+ Defines: An SMTP server and port which uses TLS encryption.
+ Recommended: smtp.gmail.com:465
+ */
+define('SWIFT_TLS_HOST', 'smtp.gmail.com:465');
+
+/*
+ Defines: An SMTP server and port which uses SSL encryption.
+ Recommended: smtp.gmail.com:465
+ */
+define('SWIFT_SSL_HOST', 'smtp.gmail.com:465');
+
+/*
+ Defines: The path to a sendmail binary (one which can run in -bs mode).
+ Recommended: /usr/sbin/sendmail
+ */
+define('SWIFT_SENDMAIL_PATH', '/usr/sbin/sendmail -bs');
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/AttachmentAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/AttachmentAcceptanceTest.php
new file mode 100644
index 0000000..5c0b826
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/AttachmentAcceptanceTest.php
@@ -0,0 +1,12 @@
+<?php
+
+require_once 'swift_required.php';
+require_once __DIR__.'/Mime/AttachmentAcceptanceTest.php';
+
+class Swift_AttachmentAcceptanceTest extends Swift_Mime_AttachmentAcceptanceTest
+{
+ protected function _createAttachment()
+ {
+ return Swift_Attachment::newInstance();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php
new file mode 100644
index 0000000..49ad20a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php
@@ -0,0 +1,162 @@
+<?php
+
+class Swift_ByteStream_FileByteStreamAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_testFile;
+
+ protected function setUp()
+ {
+ $this->_testFile = sys_get_temp_dir().'/swift-test-file'.__CLASS__;
+ file_put_contents($this->_testFile, 'abcdefghijklm');
+ }
+
+ protected function tearDown()
+ {
+ unlink($this->_testFile);
+ }
+
+ public function testFileDataCanBeRead()
+ {
+ $file = $this->_createFileStream($this->_testFile);
+ $str = '';
+ while (false !== $bytes = $file->read(8192)) {
+ $str .= $bytes;
+ }
+ $this->assertEquals('abcdefghijklm', $str);
+ }
+
+ public function testFileDataCanBeReadSequentially()
+ {
+ $file = $this->_createFileStream($this->_testFile);
+ $this->assertEquals('abcde', $file->read(5));
+ $this->assertEquals('fghijklm', $file->read(8));
+ $this->assertFalse($file->read(1));
+ }
+
+ public function testFilenameIsReturned()
+ {
+ $file = $this->_createFileStream($this->_testFile);
+ $this->assertEquals($this->_testFile, $file->getPath());
+ }
+
+ public function testFileCanBeWrittenTo()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $file->write('foobar');
+ $this->assertEquals('foobar', $file->read(8192));
+ }
+
+ public function testReadingFromThenWritingToFile()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $file->write('foobar');
+ $this->assertEquals('foobar', $file->read(8192));
+ $file->write('zipbutton');
+ $this->assertEquals('zipbutton', $file->read(8192));
+ }
+
+ public function testWritingToFileWithCanonicalization()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF');
+ $file->write("foo\r\nbar\r");
+ $file->write("\nzip\r\ntest\r");
+ $file->flushBuffers();
+ $this->assertEquals("foo\nbar\nzip\ntest\n", file_get_contents($this->_testFile));
+ }
+
+ public function testWritingWithFulleMessageLengthOfAMultipleOf8192()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF');
+ $file->write('');
+ $file->flushBuffers();
+ $this->assertEquals('', file_get_contents($this->_testFile));
+ }
+
+ public function testBindingOtherStreamsMirrorsWriteOperations()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is2->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+
+ $file->bind($is1);
+ $file->bind($is2);
+
+ $file->write('x');
+ $file->write('y');
+ }
+
+ public function testBindingOtherStreamsMirrorsFlushOperations()
+ {
+ $file = $this->_createFileStream(
+ $this->_testFile, true
+ );
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->once())
+ ->method('flushBuffers');
+ $is2->expects($this->once())
+ ->method('flushBuffers');
+
+ $file->bind($is1);
+ $file->bind($is2);
+
+ $file->flushBuffers();
+ }
+
+ public function testUnbindingStreamPreventsFurtherWrites()
+ {
+ $file = $this->_createFileStream($this->_testFile, true);
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->once())
+ ->method('write')
+ ->with('x');
+
+ $file->bind($is1);
+ $file->bind($is2);
+
+ $file->write('x');
+
+ $file->unbind($is2);
+
+ $file->write('y');
+ }
+
+ private function _createFilter($search, $replace)
+ {
+ return new Swift_StreamFilters_StringReplacementFilter($search, $replace);
+ }
+
+ private function _createMockInputStream()
+ {
+ return $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ }
+
+ private function _createFileStream($file, $writable = false)
+ {
+ return new Swift_ByteStream_FileByteStream($file, $writable);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php
new file mode 100644
index 0000000..c13e570
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php
@@ -0,0 +1,179 @@
+<?php
+
+class Swift_CharacterReaderFactory_SimpleCharacterReaderFactoryAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_factory;
+ private $_prefix = 'Swift_CharacterReader_';
+
+ protected function setUp()
+ {
+ $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ }
+
+ public function testCreatingUtf8Reader()
+ {
+ foreach (array('utf8', 'utf-8', 'UTF-8', 'UTF8') as $utf8) {
+ $reader = $this->_factory->getReaderFor($utf8);
+ $this->assertInstanceOf($this->_prefix.'Utf8Reader', $reader);
+ }
+ }
+
+ public function testCreatingIso8859XReaders()
+ {
+ $charsets = array();
+ foreach (range(1, 16) as $number) {
+ foreach (array('iso', 'iec') as $body) {
+ $charsets[] = $body.'-8859-'.$number;
+ $charsets[] = $body.'8859-'.$number;
+ $charsets[] = strtoupper($body).'-8859-'.$number;
+ $charsets[] = strtoupper($body).'8859-'.$number;
+ }
+ }
+
+ foreach ($charsets as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingWindows125XReaders()
+ {
+ $charsets = array();
+ foreach (range(0, 8) as $number) {
+ $charsets[] = 'windows-125'.$number;
+ $charsets[] = 'windows125'.$number;
+ $charsets[] = 'WINDOWS-125'.$number;
+ $charsets[] = 'WINDOWS125'.$number;
+ }
+
+ foreach ($charsets as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingCodePageReaders()
+ {
+ $charsets = array();
+ foreach (range(0, 8) as $number) {
+ $charsets[] = 'cp-125'.$number;
+ $charsets[] = 'cp125'.$number;
+ $charsets[] = 'CP-125'.$number;
+ $charsets[] = 'CP125'.$number;
+ }
+
+ foreach (array(437, 737, 850, 855, 857, 858, 860,
+ 861, 863, 865, 866, 869, ) as $number) {
+ $charsets[] = 'cp-'.$number;
+ $charsets[] = 'cp'.$number;
+ $charsets[] = 'CP-'.$number;
+ $charsets[] = 'CP'.$number;
+ }
+
+ foreach ($charsets as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingAnsiReader()
+ {
+ foreach (array('ansi', 'ANSI') as $ansi) {
+ $reader = $this->_factory->getReaderFor($ansi);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingMacintoshReader()
+ {
+ foreach (array('macintosh', 'MACINTOSH') as $mac) {
+ $reader = $this->_factory->getReaderFor($mac);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingKOIReaders()
+ {
+ $charsets = array();
+ foreach (array('7', '8-r', '8-u', '8u', '8r') as $end) {
+ $charsets[] = 'koi-'.$end;
+ $charsets[] = 'koi'.$end;
+ $charsets[] = 'KOI-'.$end;
+ $charsets[] = 'KOI'.$end;
+ }
+
+ foreach ($charsets as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingIsciiReaders()
+ {
+ foreach (array('iscii', 'ISCII', 'viscii', 'VISCII') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingMIKReader()
+ {
+ foreach (array('mik', 'MIK') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingCorkReader()
+ {
+ foreach (array('cork', 'CORK', 't1', 'T1') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(1, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingUcs2Reader()
+ {
+ foreach (array('ucs-2', 'UCS-2', 'ucs2', 'UCS2') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(2, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingUtf16Reader()
+ {
+ foreach (array('utf-16', 'UTF-16', 'utf16', 'UTF16') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(2, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingUcs4Reader()
+ {
+ foreach (array('ucs-4', 'UCS-4', 'ucs4', 'UCS4') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(4, $reader->getInitialByteSize());
+ }
+ }
+
+ public function testCreatingUtf32Reader()
+ {
+ foreach (array('utf-32', 'UTF-32', 'utf32', 'UTF32') as $charset) {
+ $reader = $this->_factory->getReaderFor($charset);
+ $this->assertInstanceOf($this->_prefix.'GenericFixedWidthReader', $reader);
+ $this->assertEquals(4, $reader->getInitialByteSize());
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php
new file mode 100644
index 0000000..e83c2bf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php
@@ -0,0 +1,24 @@
+<?php
+
+require_once 'swift_required.php';
+
+//This is more of a "cross your fingers and hope it works" test!
+
+class Swift_DependencyContainerAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ public function testNoLookupsFail()
+ {
+ $di = Swift_DependencyContainer::getInstance();
+ foreach ($di->listItems() as $itemName) {
+ try {
+ // to be removed in 6.0
+ if ('transport.mail' === $itemName) {
+ continue;
+ }
+ $di->lookup($itemName);
+ } catch (Swift_DependencyException $e) {
+ $this->fail($e->getMessage());
+ }
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php
new file mode 100644
index 0000000..fc5a814
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php
@@ -0,0 +1,12 @@
+<?php
+
+require_once 'swift_required.php';
+require_once __DIR__.'/Mime/EmbeddedFileAcceptanceTest.php';
+
+class Swift_EmbeddedFileAcceptanceTest extends Swift_Mime_EmbeddedFileAcceptanceTest
+{
+ protected function _createEmbeddedFile()
+ {
+ return Swift_EmbeddedFile::newInstance();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php
new file mode 100644
index 0000000..bada509
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class Swift_Encoder_Base64EncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
+ $this->_encoder = new Swift_Encoder_Base64Encoder();
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+ $encodedText = $this->_encoder->encodeString($text);
+
+ $this->assertEquals(
+ base64_decode($encodedText), $text,
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php
new file mode 100644
index 0000000..442d9a9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php
@@ -0,0 +1,54 @@
+<?php
+
+class Swift_Encoder_QpEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_factory;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
+ $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $encoding = $encodingDir;
+ $charStream = new Swift_CharacterStream_ArrayCharacterStream(
+ $this->_factory, $encoding);
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+ $encodedText = $encoder->encodeString($text);
+
+ foreach (explode("\r\n", $encodedText) as $line) {
+ $this->assertLessThanOrEqual(76, strlen($line));
+ }
+
+ $this->assertEquals(
+ quoted_printable_decode($encodedText), $text,
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php
new file mode 100644
index 0000000..bcb6b95
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php
@@ -0,0 +1,50 @@
+<?php
+
+class Swift_Encoder_Rfc2231EncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_factory;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
+ $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $encoding = $encodingDir;
+ $charStream = new Swift_CharacterStream_ArrayCharacterStream(
+ $this->_factory, $encoding);
+ $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+ $encodedText = $encoder->encodeString($text);
+
+ $this->assertEquals(
+ urldecode(implode('', explode("\r\n", $encodedText))), $text,
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php
new file mode 100644
index 0000000..6a4d05d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php
@@ -0,0 +1,30 @@
+<?php
+
+require_once 'swift_required.php';
+
+class Swift_EncodingAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGet7BitEncodingReturns7BitEncoder()
+ {
+ $encoder = Swift_Encoding::get7BitEncoding();
+ $this->assertEquals('7bit', $encoder->getName());
+ }
+
+ public function testGet8BitEncodingReturns8BitEncoder()
+ {
+ $encoder = Swift_Encoding::get8BitEncoding();
+ $this->assertEquals('8bit', $encoder->getName());
+ }
+
+ public function testGetQpEncodingReturnsQpEncoder()
+ {
+ $encoder = Swift_Encoding::getQpEncoding();
+ $this->assertEquals('quoted-printable', $encoder->getName());
+ }
+
+ public function testGetBase64EncodingReturnsBase64Encoder()
+ {
+ $encoder = Swift_Encoding::getBase64Encoding();
+ $this->assertEquals('base64', $encoder->getName());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php
new file mode 100644
index 0000000..5fab14c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php
@@ -0,0 +1,173 @@
+<?php
+
+class Swift_KeyCache_ArrayKeyCacheAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_cache;
+ private $_key1 = 'key1';
+ private $_key2 = 'key2';
+
+ protected function setUp()
+ {
+ $this->_cache = new Swift_KeyCache_ArrayKeyCache(
+ new Swift_KeyCache_SimpleKeyCacheInputStream()
+ );
+ }
+
+ public function testStringDataCanBeSetAndFetched()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeOverwritten()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeAppended()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
+ );
+ $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testHasKeyReturnValue()
+ {
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyIsWellPartitioned()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo'));
+ }
+
+ public function testItemKeyIsWellPartitioned()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar'));
+ }
+
+ public function testByteStreamCanBeImported()
+ {
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write('abcdef');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamCanBeAppended()
+ {
+ $os1 = new Swift_ByteStream_ArrayByteStream();
+ $os1->write('abcdef');
+
+ $os2 = new Swift_ByteStream_ArrayByteStream();
+ $os2->write('xyzuvw');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
+ );
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
+ );
+
+ $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamAndStringCanBeAppended()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
+ );
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write('abcdef');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
+ );
+ $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testDataCanBeExportedToByteStream()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+
+ $this->_cache->exportToByteStream($this->_key1, 'foo', $is);
+
+ $string = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $string .= $bytes;
+ }
+
+ $this->assertEquals('test', $string);
+ }
+
+ public function testKeyCanBeCleared()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->_cache->clearKey($this->_key1, 'foo');
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyCanBeCleared()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar'));
+ $this->_cache->clearAll($this->_key1);
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar'));
+ }
+
+ public function testKeyCacheInputStream()
+ {
+ $is = $this->_cache->getInputByteStream($this->_key1, 'foo');
+ $is->write('abc');
+ $is->write('xyz');
+ $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo'));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php
new file mode 100644
index 0000000..0e027c2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php
@@ -0,0 +1,173 @@
+<?php
+
+class Swift_KeyCache_DiskKeyCacheAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_cache;
+ private $_key1;
+ private $_key2;
+
+ protected function setUp()
+ {
+ $this->_key1 = uniqid(microtime(true), true);
+ $this->_key2 = uniqid(microtime(true), true);
+ $this->_cache = new Swift_KeyCache_DiskKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream(), sys_get_temp_dir());
+ }
+
+ public function testStringDataCanBeSetAndFetched()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeOverwritten()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeAppended()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
+ );
+ $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testHasKeyReturnValue()
+ {
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyIsWellPartitioned()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo'));
+ }
+
+ public function testItemKeyIsWellPartitioned()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar'));
+ }
+
+ public function testByteStreamCanBeImported()
+ {
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write('abcdef');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamCanBeAppended()
+ {
+ $os1 = new Swift_ByteStream_ArrayByteStream();
+ $os1->write('abcdef');
+
+ $os2 = new Swift_ByteStream_ArrayByteStream();
+ $os2->write('xyzuvw');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
+ );
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
+ );
+
+ $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamAndStringCanBeAppended()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
+ );
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write('abcdef');
+
+ $this->_cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
+ );
+ $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testDataCanBeExportedToByteStream()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+
+ $this->_cache->exportToByteStream($this->_key1, 'foo', $is);
+
+ $string = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $string .= $bytes;
+ }
+
+ $this->assertEquals('test', $string);
+ }
+
+ public function testKeyCanBeCleared()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->_cache->clearKey($this->_key1, 'foo');
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyCanBeCleared()
+ {
+ $this->_cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->_cache->setString(
+ $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar'));
+ $this->_cache->clearAll($this->_key1);
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
+ $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar'));
+ }
+
+ public function testKeyCacheInputStream()
+ {
+ $is = $this->_cache->getInputByteStream($this->_key1, 'foo');
+ $is->write('abc');
+ $is->write('xyz');
+ $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo'));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php
new file mode 100644
index 0000000..5f4e983
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php
@@ -0,0 +1,55 @@
+<?php
+
+require_once 'swift_required.php';
+require_once __DIR__.'/Mime/SimpleMessageAcceptanceTest.php';
+
+class Swift_MessageAcceptanceTest extends Swift_Mime_SimpleMessageAcceptanceTest
+{
+ public function testAddPartWrapper()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = $message->getDate();
+ $boundary = $message->getBoundary();
+
+ $message->addPart('foo', 'text/plain', 'iso-8859-1');
+ $message->addPart('test <b>foo</b>', 'text/html', 'iso-8859-1');
+
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'test <b>foo</b>'.
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ protected function _createMessage()
+ {
+ Swift_DependencyContainer::getInstance()
+ ->register('properties.charset')->asValue(null);
+
+ return Swift_Message::newInstance();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php
new file mode 100644
index 0000000..7353d9d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php
@@ -0,0 +1,123 @@
+<?php
+
+class Swift_Mime_AttachmentAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_contentEncoder;
+ private $_cache;
+ private $_grammar;
+ private $_headers;
+
+ protected function setUp()
+ {
+ $this->_cache = new Swift_KeyCache_ArrayKeyCache(
+ new Swift_KeyCache_SimpleKeyCacheInputStream()
+ );
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $this->_grammar = new Swift_Mime_Grammar();
+ $this->_headers = new Swift_Mime_SimpleHeaderSet(
+ new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
+ );
+ }
+
+ public function testDispositionIsSetInHeader()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setDisposition('inline');
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: inline'."\r\n",
+ $attachment->toString()
+ );
+ }
+
+ public function testDispositionIsAttachmentByDefault()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment'."\r\n",
+ $attachment->toString()
+ );
+ }
+
+ public function testFilenameIsSetInHeader()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf'."\r\n",
+ $attachment->toString()
+ );
+ }
+
+ public function testSizeIsSetInHeader()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setSize(12340);
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; size=12340'."\r\n",
+ $attachment->toString()
+ );
+ }
+
+ public function testMultipleParametersInHeader()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setSize(12340);
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n",
+ $attachment->toString()
+ );
+ }
+
+ public function testEndToEnd()
+ {
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setSize(12340);
+ $attachment->setBody('abcd');
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n".
+ "\r\n".
+ base64_encode('abcd'),
+ $attachment->toString()
+ );
+ }
+
+ protected function _createAttachment()
+ {
+ $entity = new Swift_Mime_Attachment(
+ $this->_headers,
+ $this->_contentEncoder,
+ $this->_cache,
+ $this->_grammar
+ );
+
+ return $entity;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php
new file mode 100644
index 0000000..a72f5ff
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php
@@ -0,0 +1,56 @@
+<?php
+
+class Swift_Mime_ContentEncoder_Base64ContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
+ $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write($text);
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+
+ $this->_encoder->encodeByteStream($os, $is);
+
+ $encoded = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $encoded .= $bytes;
+ }
+
+ $this->assertEquals(
+ base64_decode($encoded), $text,
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php
new file mode 100644
index 0000000..0dfc4e2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php
@@ -0,0 +1,88 @@
+<?php
+
+class Swift_Mime_ContentEncoder_NativeQpContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ protected $_samplesDir;
+
+ /**
+ * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder
+ */
+ protected $_encoder;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
+ $this->_encoder = new Swift_Mime_ContentEncoder_NativeQpContentEncoder();
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write($text);
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+ $this->_encoder->encodeByteStream($os, $is);
+
+ $encoded = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $encoded .= $bytes;
+ }
+
+ $this->assertEquals(
+ quoted_printable_decode($encoded),
+ // CR and LF are converted to CRLF
+ preg_replace('~\r(?!\n)|(?<!\r)\n~', "\r\n", $text),
+ '%s: Encoded string should decode back to original string for sample '.$sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+
+ public function testEncodingAndDecodingSamplesFromDiConfiguredInstance()
+ {
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertSame('=C3=A4=C3=B6=C3=BC=C3=9F', $encoder->encodeString('äöüß'));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCharsetChangeNotImplemented()
+ {
+ $this->_encoder->charsetChanged('utf-8');
+ $this->_encoder->charsetChanged('charset');
+ $this->_encoder->encodeString('foo');
+ }
+
+ public function testGetName()
+ {
+ $this->assertSame('quoted-printable', $this->_encoder->getName());
+ }
+
+ private function _createEncoderFromContainer()
+ {
+ return Swift_DependencyContainer::getInstance()
+ ->lookup('mime.nativeqpcontentencoder')
+ ;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php
new file mode 100644
index 0000000..5eff4e2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php
@@ -0,0 +1,88 @@
+<?php
+
+class Swift_Mime_ContentEncoder_PlainContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
+ $this->_encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
+ }
+
+ public function testEncodingAndDecodingSamplesString()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+ $encodedText = $this->_encoder->encodeString($text);
+
+ $this->assertEquals(
+ $encodedText, $text,
+ '%s: Encoded string should be identical to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+
+ public function testEncodingAndDecodingSamplesByteStream()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write($text);
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+
+ $this->_encoder->encodeByteStream($os, $is);
+
+ $encoded = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $encoded .= $bytes;
+ }
+
+ $this->assertEquals(
+ $encoded, $text,
+ '%s: Encoded string should be identical to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php
new file mode 100644
index 0000000..a383b58
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php
@@ -0,0 +1,160 @@
+<?php
+
+class Swift_Mime_ContentEncoder_QpContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_samplesDir;
+ private $_factory;
+
+ protected function setUp()
+ {
+ $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
+ $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ }
+
+ protected function tearDown()
+ {
+ Swift_Preferences::getInstance()->setQPDotEscape(false);
+ }
+
+ public function testEncodingAndDecodingSamples()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $encoding = $encodingDir;
+ $charStream = new Swift_CharacterStream_NgCharacterStream(
+ $this->_factory, $encoding);
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write($text);
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+ $encoder->encodeByteStream($os, $is);
+
+ $encoded = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $encoded .= $bytes;
+ }
+
+ $this->assertEquals(
+ quoted_printable_decode($encoded), $text,
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+
+ public function testEncodingAndDecodingSamplesFromDiConfiguredInstance()
+ {
+ $sampleFp = opendir($this->_samplesDir);
+ while (false !== $encodingDir = readdir($sampleFp)) {
+ if (substr($encodingDir, 0, 1) == '.') {
+ continue;
+ }
+
+ $encoding = $encodingDir;
+ $encoder = $this->_createEncoderFromContainer();
+
+ $sampleDir = $this->_samplesDir.'/'.$encodingDir;
+
+ if (is_dir($sampleDir)) {
+ $fileFp = opendir($sampleDir);
+ while (false !== $sampleFile = readdir($fileFp)) {
+ if (substr($sampleFile, 0, 1) == '.') {
+ continue;
+ }
+
+ $text = file_get_contents($sampleDir.'/'.$sampleFile);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $os->write($text);
+
+ $is = new Swift_ByteStream_ArrayByteStream();
+ $encoder->encodeByteStream($os, $is);
+
+ $encoded = '';
+ while (false !== $bytes = $is->read(8192)) {
+ $encoded .= $bytes;
+ }
+
+ $this->assertEquals(
+ str_replace("\r\n", "\n", quoted_printable_decode($encoded)), str_replace("\r\n", "\n", $text),
+ '%s: Encoded string should decode back to original string for sample '.
+ $sampleDir.'/'.$sampleFile
+ );
+ }
+ closedir($fileFp);
+ }
+ }
+ closedir($sampleFp);
+ }
+
+ public function testEncodingLFTextWithDiConfiguredInstance()
+ {
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\nb\nc"));
+ }
+
+ public function testEncodingCRTextWithDiConfiguredInstance()
+ {
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\rb\rc"));
+ }
+
+ public function testEncodingLFCRTextWithDiConfiguredInstance()
+ {
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a\r\n\r\nb\r\n\r\nc", $encoder->encodeString("a\n\rb\n\rc"));
+ }
+
+ public function testEncodingCRLFTextWithDiConfiguredInstance()
+ {
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\r\nb\r\nc"));
+ }
+
+ public function testEncodingDotStuffingWithDiConfiguredInstance()
+ {
+ // Enable DotEscaping
+ Swift_Preferences::getInstance()->setQPDotEscape(true);
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a=2E\r\n=2E\r\n=2Eb\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc"));
+ // Return to default
+ Swift_Preferences::getInstance()->setQPDotEscape(false);
+ $encoder = $this->_createEncoderFromContainer();
+ $this->assertEquals("a.\r\n.\r\n.b\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc"));
+ }
+
+ public function testDotStuffingEncodingAndDecodingSamplesFromDiConfiguredInstance()
+ {
+ // Enable DotEscaping
+ Swift_Preferences::getInstance()->setQPDotEscape(true);
+ $this->testEncodingAndDecodingSamplesFromDiConfiguredInstance();
+ }
+
+ private function _createEncoderFromContainer()
+ {
+ return Swift_DependencyContainer::getInstance()
+ ->lookup('mime.qpcontentencoder')
+ ;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php
new file mode 100644
index 0000000..0f7aa72
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php
@@ -0,0 +1,136 @@
+<?php
+
+class Swift_Mime_EmbeddedFileAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_contentEncoder;
+ private $_cache;
+ private $_grammar;
+ private $_headers;
+
+ protected function setUp()
+ {
+ $this->_cache = new Swift_KeyCache_ArrayKeyCache(
+ new Swift_KeyCache_SimpleKeyCacheInputStream()
+ );
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $this->_grammar = new Swift_Mime_Grammar();
+ $this->_headers = new Swift_Mime_SimpleHeaderSet(
+ new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
+ );
+ }
+
+ public function testContentIdIsSetInHeader()
+ {
+ $file = $this->_createEmbeddedFile();
+ $file->setContentType('application/pdf');
+ $file->setId('foo@bar');
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <foo@bar>'."\r\n".
+ 'Content-Disposition: inline'."\r\n",
+ $file->toString()
+ );
+ }
+
+ public function testDispositionIsSetInHeader()
+ {
+ $file = $this->_createEmbeddedFile();
+ $id = $file->getId();
+ $file->setContentType('application/pdf');
+ $file->setDisposition('attachment');
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$id.'>'."\r\n".
+ 'Content-Disposition: attachment'."\r\n",
+ $file->toString()
+ );
+ }
+
+ public function testFilenameIsSetInHeader()
+ {
+ $file = $this->_createEmbeddedFile();
+ $id = $file->getId();
+ $file->setContentType('application/pdf');
+ $file->setFilename('foo.pdf');
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$id.'>'."\r\n".
+ 'Content-Disposition: inline; filename=foo.pdf'."\r\n",
+ $file->toString()
+ );
+ }
+
+ public function testSizeIsSetInHeader()
+ {
+ $file = $this->_createEmbeddedFile();
+ $id = $file->getId();
+ $file->setContentType('application/pdf');
+ $file->setSize(12340);
+ $this->assertEquals(
+ 'Content-Type: application/pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$id.'>'."\r\n".
+ 'Content-Disposition: inline; size=12340'."\r\n",
+ $file->toString()
+ );
+ }
+
+ public function testMultipleParametersInHeader()
+ {
+ $file = $this->_createEmbeddedFile();
+ $id = $file->getId();
+ $file->setContentType('application/pdf');
+ $file->setFilename('foo.pdf');
+ $file->setSize(12340);
+
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$id.'>'."\r\n".
+ 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n",
+ $file->toString()
+ );
+ }
+
+ public function testEndToEnd()
+ {
+ $file = $this->_createEmbeddedFile();
+ $id = $file->getId();
+ $file->setContentType('application/pdf');
+ $file->setFilename('foo.pdf');
+ $file->setSize(12340);
+ $file->setBody('abcd');
+ $this->assertEquals(
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$id.'>'."\r\n".
+ 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n".
+ "\r\n".
+ base64_encode('abcd'),
+ $file->toString()
+ );
+ }
+
+ protected function _createEmbeddedFile()
+ {
+ $entity = new Swift_Mime_EmbeddedFile(
+ $this->_headers,
+ $this->_contentEncoder,
+ $this->_cache,
+ $this->_grammar
+ );
+
+ return $entity;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php
new file mode 100644
index 0000000..e3fad6d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php
@@ -0,0 +1,32 @@
+<?php
+
+class Swift_Mime_HeaderEncoder_Base64HeaderEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder();
+ }
+
+ public function testEncodingJIS()
+ {
+ if (function_exists('mb_convert_encoding')) {
+ // base64_encode and split cannot handle long JIS text to fold
+ $subject = '長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い件名';
+
+ $encodedWrapperLength = strlen('=?iso-2022-jp?'.$this->_encoder->getName().'??=');
+
+ $old = mb_internal_encoding();
+ mb_internal_encoding('utf-8');
+ $newstring = mb_encode_mimeheader($subject, 'iso-2022-jp', 'B', "\r\n");
+ mb_internal_encoding($old);
+
+ $encoded = $this->_encoder->encodeString($subject, 0, 75 - $encodedWrapperLength, 'iso-2022-jp');
+ $this->assertEquals(
+ $encoded, $newstring,
+ 'Encoded string should decode back to original string for sample '
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php
new file mode 100644
index 0000000..a7f6fc5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php
@@ -0,0 +1,127 @@
+<?php
+
+class Swift_Mime_MimePartAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ private $_contentEncoder;
+ private $_cache;
+ private $_grammar;
+ private $_headers;
+
+ protected function setUp()
+ {
+ $this->_cache = new Swift_KeyCache_ArrayKeyCache(
+ new Swift_KeyCache_SimpleKeyCacheInputStream()
+ );
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $this->_contentEncoder = new Swift_Mime_ContentEncoder_QpContentEncoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'),
+ new Swift_StreamFilters_ByteArrayReplacementFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ )
+ );
+
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $this->_grammar = new Swift_Mime_Grammar();
+ $this->_headers = new Swift_Mime_SimpleHeaderSet(
+ new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
+ );
+ }
+
+ public function testCharsetIsSetInHeader()
+ {
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('utf-8');
+ $part->setBody('foobar');
+ $this->assertEquals(
+ 'Content-Type: text/plain; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foobar',
+ $part->toString()
+ );
+ }
+
+ public function testFormatIsSetInHeaders()
+ {
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setFormat('flowed');
+ $part->setBody('> foobar');
+ $this->assertEquals(
+ 'Content-Type: text/plain; format=flowed'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ '> foobar',
+ $part->toString()
+ );
+ }
+
+ public function testDelSpIsSetInHeaders()
+ {
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setDelSp(true);
+ $part->setBody('foobar');
+ $this->assertEquals(
+ 'Content-Type: text/plain; delsp=yes'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foobar',
+ $part->toString()
+ );
+ }
+
+ public function testAll3ParamsInHeaders()
+ {
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('utf-8');
+ $part->setFormat('fixed');
+ $part->setDelSp(true);
+ $part->setBody('foobar');
+ $this->assertEquals(
+ 'Content-Type: text/plain; charset=utf-8; format=fixed; delsp=yes'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foobar',
+ $part->toString()
+ );
+ }
+
+ public function testBodyIsCanonicalized()
+ {
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('utf-8');
+ $part->setBody("foobar\r\rtest\ning\r");
+ $this->assertEquals(
+ 'Content-Type: text/plain; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ "foobar\r\n".
+ "\r\n".
+ "test\r\n".
+ "ing\r\n",
+ $part->toString()
+ );
+ }
+
+ protected function _createMimePart()
+ {
+ $entity = new Swift_Mime_MimePart(
+ $this->_headers,
+ $this->_contentEncoder,
+ $this->_cache,
+ $this->_grammar
+ );
+
+ return $entity;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php
new file mode 100644
index 0000000..912768e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php
@@ -0,0 +1,1249 @@
+<?php
+
+class Swift_Mime_SimpleMessageAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ Swift_Preferences::getInstance()->setCharset(null); //TODO: Test with the charset defined
+ }
+
+ public function testBasicHeaders()
+ {
+ /* -- RFC 2822, 3.6.
+ */
+
+ $message = $this->_createMessage();
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString(),
+ '%s: Only required headers, and non-empty headers should be displayed'
+ );
+ }
+
+ public function testSubjectIsDisplayedIfSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testDateCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $id = $message->getId();
+ $message->setDate(1234);
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', 1234)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMessageIdCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setId('foo@bar');
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <foo@bar>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testContentTypeCanBeChanged()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setContentType('text/html');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/html'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testCharsetCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setContentType('text/html');
+ $message->setCharset('iso-8859-1');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testFormatCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFormat('flowed');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain; format=flowed'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testEncoderCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setContentType('text/html');
+ $message->setEncoder(
+ new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit')
+ );
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/html'."\r\n".
+ 'Content-Transfer-Encoding: 7bit'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testFromAddressCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom('chris.corbyn@swiftmailer.org');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: chris.corbyn@swiftmailer.org'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testFromAddressCanBeSetWithName()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris Corbyn'));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMultipleFromAddressesCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org',
+ ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>, mark@swiftmailer.org'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testReturnPathAddressCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testEmptyReturnPathHeaderCanBeUsed()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Return-Path: <>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testSenderCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setSender('chris.corbyn@swiftmailer.org');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Sender: chris.corbyn@swiftmailer.org'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testSenderCanBeSetWithName()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setSender(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Sender: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: '."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testReplyToCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array('chris@w3style.co.uk' => 'Myself'));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMultipleReplyAddressCanBeUsed()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testToAddressCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo('mark@swiftmailer.org');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMultipleToAddressesCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo(array(
+ 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testCcAddressCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo(array(
+ 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $message->setCc('john@some-site.com');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
+ 'Cc: john@some-site.com'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMultipleCcAddressesCanBeSet()
+ {
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo(array(
+ 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $message->setCc(array(
+ 'john@some-site.com' => 'John West',
+ 'fred@another-site.co.uk' => 'Big Fred',
+ ));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
+ 'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testBccAddressCanBeSet()
+ {
+ //Obviously Transports need to setBcc(array()) and send to each Bcc recipient
+ // separately in accordance with RFC 2822/2821
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo(array(
+ 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $message->setCc(array(
+ 'john@some-site.com' => 'John West',
+ 'fred@another-site.co.uk' => 'Big Fred',
+ ));
+ $message->setBcc('x@alphabet.tld');
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
+ 'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
+ 'Bcc: x@alphabet.tld'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testMultipleBccAddressesCanBeSet()
+ {
+ //Obviously Transports need to setBcc(array()) and send to each Bcc recipient
+ // separately in accordance with RFC 2822/2821
+ $message = $this->_createMessage();
+ $message->setSubject('just a test subject');
+ $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
+ $message->setReplyTo(array(
+ 'chris@w3style.co.uk' => 'Myself',
+ 'my.other@address.com' => 'Me',
+ ));
+ $message->setTo(array(
+ 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $message->setCc(array(
+ 'john@some-site.com' => 'John West',
+ 'fred@another-site.co.uk' => 'Big Fred',
+ ));
+ $message->setBcc(array('x@alphabet.tld', 'a@alphabet.tld' => 'A'));
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
+ 'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
+ 'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
+ 'Bcc: x@alphabet.tld, A <a@alphabet.tld>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testStringBodyIsAppended()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setBody(
+ 'just a test body'."\r\n".
+ 'with a new line'
+ );
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'just a test body'."\r\n".
+ 'with a new line',
+ $message->toString()
+ );
+ }
+
+ public function testStringBodyIsEncoded()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setBody(
+ 'Just s'.pack('C*', 0xC2, 0x01, 0x01).'me multi-'."\r\n".
+ 'line message!'
+ );
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'Just s=C2=01=01me multi-'."\r\n".
+ 'line message!',
+ $message->toString()
+ );
+ }
+
+ public function testChildrenCanBeAttached()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = $message->getDate();
+ $boundary = $message->getBoundary();
+
+ $part1 = $this->_createMimePart();
+ $part1->setContentType('text/plain');
+ $part1->setCharset('iso-8859-1');
+ $part1->setBody('foo');
+
+ $message->attach($part1);
+
+ $part2 = $this->_createMimePart();
+ $part2->setContentType('text/html');
+ $part2->setCharset('iso-8859-1');
+ $part2->setBody('test <b>foo</b>');
+
+ $message->attach($part2);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'test <b>foo</b>'.
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testAttachmentsBeingAttached()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('iso-8859-1');
+ $part->setBody('foo');
+
+ $message->attach($part);
+
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setBody('<pdf data>');
+
+ $message->attach($attachment);
+
+ $this->assertRegExp(
+ '~^'.
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<pdf data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString()
+ );
+ }
+
+ public function testAttachmentsAndEmbeddedFilesBeingAttached()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('iso-8859-1');
+ $part->setBody('foo');
+
+ $message->attach($part);
+
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setBody('<pdf data>');
+
+ $message->attach($attachment);
+
+ $file = $this->_createEmbeddedFile();
+ $file->setContentType('image/jpeg');
+ $file->setFilename('myimage.jpg');
+ $file->setBody('<image data>');
+
+ $message->attach($file);
+
+ $cid = $file->getId();
+
+ $this->assertRegExp(
+ '~^'.
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\2'."\r\n".
+ 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$cid.'>'."\r\n".
+ 'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<image data>'), '~').
+ "\r\n\r\n".
+ '--\\2--'."\r\n".
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<pdf data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString()
+ );
+ }
+
+ public function testComplexEmbeddingOfContent()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setBody('<pdf data>');
+
+ $message->attach($attachment);
+
+ $file = $this->_createEmbeddedFile();
+ $file->setContentType('image/jpeg');
+ $file->setFilename('myimage.jpg');
+ $file->setBody('<image data>');
+
+ $part = $this->_createMimePart();
+ $part->setContentType('text/html');
+ $part->setCharset('iso-8859-1');
+ $part->setBody('foo <img src="'.$message->embed($file).'" />');
+
+ $message->attach($part);
+
+ $cid = $file->getId();
+
+ $this->assertRegExp(
+ '~^'.
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo <img src=3D"cid:'.$cid.'" />'.//=3D is just = in QP
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$cid.'>'."\r\n".
+ 'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<image data>'), '~').
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<pdf data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString()
+ );
+ }
+
+ public function testAttachingAndDetachingContent()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $part = $this->_createMimePart();
+ $part->setContentType('text/plain');
+ $part->setCharset('iso-8859-1');
+ $part->setBody('foo');
+
+ $message->attach($part);
+
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setBody('<pdf data>');
+
+ $message->attach($attachment);
+
+ $file = $this->_createEmbeddedFile();
+ $file->setContentType('image/jpeg');
+ $file->setFilename('myimage.jpg');
+ $file->setBody('<image data>');
+
+ $message->attach($file);
+
+ $cid = $file->getId();
+
+ $message->detach($attachment);
+
+ $this->assertRegExp(
+ '~^'.
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$cid.'>'."\r\n".
+ 'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<image data>'), '~').
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString(),
+ '%s: Attachment should have been detached'
+ );
+ }
+
+ public function testBoundaryDoesNotAppearAfterAllPartsAreDetached()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = $message->getDate();
+ $boundary = $message->getBoundary();
+
+ $part1 = $this->_createMimePart();
+ $part1->setContentType('text/plain');
+ $part1->setCharset('iso-8859-1');
+ $part1->setBody('foo');
+
+ $message->attach($part1);
+
+ $part2 = $this->_createMimePart();
+ $part2->setContentType('text/html');
+ $part2->setCharset('iso-8859-1');
+ $part2->setBody('test <b>foo</b>');
+
+ $message->attach($part2);
+
+ $message->detach($part1);
+ $message->detach($part2);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n",
+ $message->toString(),
+ '%s: Message should be restored to orignal state after parts are detached'
+ );
+ }
+
+ public function testCharsetFormatOrDelSpAreNotShownWhenBoundaryIsSet()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setCharset('utf-8');
+ $message->setFormat('flowed');
+ $message->setDelSp(true);
+
+ $id = $message->getId();
+ $date = $message->getDate();
+ $boundary = $message->getBoundary();
+
+ $part1 = $this->_createMimePart();
+ $part1->setContentType('text/plain');
+ $part1->setCharset('iso-8859-1');
+ $part1->setBody('foo');
+
+ $message->attach($part1);
+
+ $part2 = $this->_createMimePart();
+ $part2->setContentType('text/html');
+ $part2->setCharset('iso-8859-1');
+ $part2->setBody('test <b>foo</b>');
+
+ $message->attach($part2);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'test <b>foo</b>'.
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testBodyCanBeSetWithAttachments()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setContentType('text/html');
+ $message->setCharset('iso-8859-1');
+ $message->setBody('foo');
+
+ $id = $message->getId();
+ $date = date('r', $message->getDate());
+ $boundary = $message->getBoundary();
+
+ $attachment = $this->_createAttachment();
+ $attachment->setContentType('application/pdf');
+ $attachment->setFilename('foo.pdf');
+ $attachment->setBody('<pdf data>');
+
+ $message->attach($attachment);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=iso-8859-1'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: application/pdf; name=foo.pdf'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
+ "\r\n".
+ base64_encode('<pdf data>').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testHtmlPartAlwaysAppearsLast()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+
+ $id = $message->getId();
+ $date = date('r', $message->getDate());
+ $boundary = $message->getBoundary();
+
+ $part1 = $this->_createMimePart();
+ $part1->setContentType('text/html');
+ $part1->setBody('foo');
+
+ $part2 = $this->_createMimePart();
+ $part2->setContentType('text/plain');
+ $part2->setBody('bar');
+
+ $message->attach($part1);
+ $message->attach($part2);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'bar'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testBodyBecomesPartIfOtherPartsAttached()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setContentType('text/html');
+ $message->setBody('foo');
+
+ $id = $message->getId();
+ $date = date('r', $message->getDate());
+ $boundary = $message->getBoundary();
+
+ $part2 = $this->_createMimePart();
+ $part2->setContentType('text/plain');
+ $part2->setBody('bar');
+
+ $message->attach($part2);
+
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'bar'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'foo'.
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n",
+ $message->toString()
+ );
+ }
+
+ public function testBodyIsCanonicalized()
+ {
+ $message = $this->_createMessage();
+ $message->setReturnPath('chris@w3style.co.uk');
+ $message->setSubject('just a test subject');
+ $message->setFrom(array(
+ 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
+ $message->setBody(
+ 'just a test body'."\n".
+ 'with a new line'
+ );
+ $id = $message->getId();
+ $date = $message->getDate();
+ $this->assertEquals(
+ 'Return-Path: <chris@w3style.co.uk>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.date('r', $date)."\r\n".
+ 'Subject: just a test subject'."\r\n".
+ 'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: text/plain'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'just a test body'."\r\n".
+ 'with a new line',
+ $message->toString()
+ );
+ }
+
+ protected function _createMessage()
+ {
+ return new Swift_Message();
+ }
+
+ protected function _createMimePart()
+ {
+ return new Swift_MimePart();
+ }
+
+ protected function _createAttachment()
+ {
+ return new Swift_Attachment();
+ }
+
+ protected function _createEmbeddedFile()
+ {
+ return new Swift_EmbeddedFile();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php
new file mode 100644
index 0000000..f42405d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php
@@ -0,0 +1,15 @@
+<?php
+
+require_once 'swift_required.php';
+require_once __DIR__.'/Mime/MimePartAcceptanceTest.php';
+
+class Swift_MimePartAcceptanceTest extends Swift_Mime_MimePartAcceptanceTest
+{
+ protected function _createMimePart()
+ {
+ Swift_DependencyContainer::getInstance()
+ ->register('properties.charset')->asValue(null);
+
+ return Swift_MimePart::newInstance();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php
new file mode 100644
index 0000000..21abc13
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php
@@ -0,0 +1,131 @@
+<?php
+
+abstract class Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest extends \PHPUnit_Framework_TestCase
+{
+ protected $_buffer;
+
+ abstract protected function _initializeBuffer();
+
+ protected function setUp()
+ {
+ if (true == getenv('TRAVIS')) {
+ $this->markTestSkipped(
+ 'Will fail on travis-ci if not skipped due to travis blocking '.
+ 'socket mailing tcp connections.'
+ );
+ }
+
+ $this->_buffer = new Swift_Transport_StreamBuffer(
+ $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock()
+ );
+ }
+
+ public function testReadLine()
+ {
+ $this->_initializeBuffer();
+
+ $line = $this->_buffer->readLine(0);
+ $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
+ $seq = $this->_buffer->write("QUIT\r\n");
+ $this->assertTrue((bool) $seq);
+ $line = $this->_buffer->readLine($seq);
+ $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
+ $this->_buffer->terminate();
+ }
+
+ public function testWrite()
+ {
+ $this->_initializeBuffer();
+
+ $line = $this->_buffer->readLine(0);
+ $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
+
+ $seq = $this->_buffer->write("HELO foo\r\n");
+ $this->assertTrue((bool) $seq);
+ $line = $this->_buffer->readLine($seq);
+ $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
+
+ $seq = $this->_buffer->write("QUIT\r\n");
+ $this->assertTrue((bool) $seq);
+ $line = $this->_buffer->readLine($seq);
+ $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
+ $this->_buffer->terminate();
+ }
+
+ public function testBindingOtherStreamsMirrorsWriteOperations()
+ {
+ $this->_initializeBuffer();
+
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is2->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+
+ $this->_buffer->bind($is1);
+ $this->_buffer->bind($is2);
+
+ $this->_buffer->write('x');
+ $this->_buffer->write('y');
+ }
+
+ public function testBindingOtherStreamsMirrorsFlushOperations()
+ {
+ $this->_initializeBuffer();
+
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->once())
+ ->method('flushBuffers');
+ $is2->expects($this->once())
+ ->method('flushBuffers');
+
+ $this->_buffer->bind($is1);
+ $this->_buffer->bind($is2);
+
+ $this->_buffer->flushBuffers();
+ }
+
+ public function testUnbindingStreamPreventsFurtherWrites()
+ {
+ $this->_initializeBuffer();
+
+ $is1 = $this->_createMockInputStream();
+ $is2 = $this->_createMockInputStream();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->once())
+ ->method('write')
+ ->with('x');
+
+ $this->_buffer->bind($is1);
+ $this->_buffer->bind($is2);
+
+ $this->_buffer->write('x');
+
+ $this->_buffer->unbind($is2);
+
+ $this->_buffer->write('y');
+ }
+
+ private function _createMockInputStream()
+ {
+ return $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php
new file mode 100644
index 0000000..4c3c7d3
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php
@@ -0,0 +1,33 @@
+<?php
+
+require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';
+
+class Swift_Transport_StreamBuffer_BasicSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
+{
+ protected function setUp()
+ {
+ if (!defined('SWIFT_SMTP_HOST')) {
+ $this->markTestSkipped(
+ 'Cannot run test without an SMTP host to connect to (define '.
+ 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)'
+ );
+ }
+ parent::setUp();
+ }
+
+ protected function _initializeBuffer()
+ {
+ $parts = explode(':', SWIFT_SMTP_HOST);
+ $host = $parts[0];
+ $port = isset($parts[1]) ? $parts[1] : 25;
+
+ $this->_buffer->initialize(array(
+ 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+ 'host' => $host,
+ 'port' => $port,
+ 'protocol' => 'tcp',
+ 'blocking' => 1,
+ 'timeout' => 15,
+ ));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php
new file mode 100644
index 0000000..a37439d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php
@@ -0,0 +1,26 @@
+<?php
+
+require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';
+
+class Swift_Transport_StreamBuffer_ProcessAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
+{
+ protected function setUp()
+ {
+ if (!defined('SWIFT_SENDMAIL_PATH')) {
+ $this->markTestSkipped(
+ 'Cannot run test without a path to sendmail (define '.
+ 'SWIFT_SENDMAIL_PATH in tests/acceptance.conf.php if you wish to run this test)'
+ );
+ }
+
+ parent::setUp();
+ }
+
+ protected function _initializeBuffer()
+ {
+ $this->_buffer->initialize(array(
+ 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS,
+ 'command' => SWIFT_SENDMAIL_PATH.' -bs',
+ ));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php
new file mode 100644
index 0000000..59362b0
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php
@@ -0,0 +1,67 @@
+<?php
+
+class Swift_Transport_StreamBuffer_SocketTimeoutTest extends \PHPUnit_Framework_TestCase
+{
+ protected $_buffer;
+
+ protected $_randomHighPort;
+
+ protected $_server;
+
+ protected function setUp()
+ {
+ if (!defined('SWIFT_SMTP_HOST')) {
+ $this->markTestSkipped(
+ 'Cannot run test without an SMTP host to connect to (define '.
+ 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)'
+ );
+ }
+
+ $serverStarted = false;
+ for ($i = 0; $i < 5; ++$i) {
+ $this->_randomHighPort = rand(50000, 65000);
+ $this->_server = stream_socket_server('tcp://127.0.0.1:'.$this->_randomHighPort);
+ if ($this->_server) {
+ $serverStarted = true;
+ }
+ }
+
+ $this->_buffer = new Swift_Transport_StreamBuffer(
+ $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock()
+ );
+ }
+
+ protected function _initializeBuffer()
+ {
+ $host = '127.0.0.1';
+ $port = $this->_randomHighPort;
+
+ $this->_buffer->initialize(array(
+ 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+ 'host' => $host,
+ 'port' => $port,
+ 'protocol' => 'tcp',
+ 'blocking' => 1,
+ 'timeout' => 1,
+ ));
+ }
+
+ public function testTimeoutException()
+ {
+ $this->_initializeBuffer();
+ $e = null;
+ try {
+ $line = $this->_buffer->readLine(0);
+ } catch (Exception $e) {
+ }
+ $this->assertInstanceOf('Swift_IoException', $e, 'IO Exception Not Thrown On Connection Timeout');
+ $this->assertRegExp('/Connection to .* Timed Out/', $e->getMessage());
+ }
+
+ protected function tearDown()
+ {
+ if ($this->_server) {
+ stream_socket_shutdown($this->_server, STREAM_SHUT_RDWR);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php
new file mode 100644
index 0000000..32e0fe8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php
@@ -0,0 +1,40 @@
+<?php
+
+require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';
+
+class Swift_Transport_StreamBuffer_SslSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
+{
+ protected function setUp()
+ {
+ $streams = stream_get_transports();
+ if (!in_array('ssl', $streams)) {
+ $this->markTestSkipped(
+ 'SSL is not configured for your system. It is not possible to run this test'
+ );
+ }
+ if (!defined('SWIFT_SSL_HOST')) {
+ $this->markTestSkipped(
+ 'Cannot run test without an SSL enabled SMTP host to connect to (define '.
+ 'SWIFT_SSL_HOST in tests/acceptance.conf.php if you wish to run this test)'
+ );
+ }
+
+ parent::setUp();
+ }
+
+ protected function _initializeBuffer()
+ {
+ $parts = explode(':', SWIFT_SSL_HOST);
+ $host = $parts[0];
+ $port = isset($parts[1]) ? $parts[1] : 25;
+
+ $this->_buffer->initialize(array(
+ 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+ 'host' => $host,
+ 'port' => $port,
+ 'protocol' => 'ssl',
+ 'blocking' => 1,
+ 'timeout' => 15,
+ ));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php
new file mode 100644
index 0000000..1053a87
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php
@@ -0,0 +1,39 @@
+<?php
+
+require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';
+
+class Swift_Transport_StreamBuffer_TlsSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
+{
+ protected function setUp()
+ {
+ $streams = stream_get_transports();
+ if (!in_array('tls', $streams)) {
+ $this->markTestSkipped(
+ 'TLS is not configured for your system. It is not possible to run this test'
+ );
+ }
+ if (!defined('SWIFT_TLS_HOST')) {
+ $this->markTestSkipped(
+ 'Cannot run test without a TLS enabled SMTP host to connect to (define '.
+ 'SWIFT_TLS_HOST in tests/acceptance.conf.php if you wish to run this test)'
+ );
+ }
+ parent::setUp();
+ }
+
+ protected function _initializeBuffer()
+ {
+ $parts = explode(':', SWIFT_TLS_HOST);
+ $host = $parts[0];
+ $port = isset($parts[1]) ? $parts[1] : 25;
+
+ $this->_buffer->initialize(array(
+ 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+ 'host' => $host,
+ 'port' => $port,
+ 'protocol' => 'tls',
+ 'blocking' => 1,
+ 'timeout' => 15,
+ ));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bootstrap.php b/vendor/swiftmailer/swiftmailer/tests/bootstrap.php
new file mode 100644
index 0000000..27091a2
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bootstrap.php
@@ -0,0 +1,21 @@
+<?php
+
+require_once dirname(__DIR__).'/vendor/autoload.php';
+
+// Disable garbage collector to prevent segfaults
+gc_disable();
+
+set_include_path(get_include_path().PATH_SEPARATOR.dirname(__DIR__).'/lib');
+
+Mockery::getConfiguration()->allowMockingNonExistentMethods(true);
+
+if (is_file(__DIR__.'/acceptance.conf.php')) {
+ require_once __DIR__.'/acceptance.conf.php';
+}
+if (is_file(__DIR__.'/smoke.conf.php')) {
+ require_once __DIR__.'/smoke.conf.php';
+}
+require_once __DIR__.'/StreamCollector.php';
+require_once __DIR__.'/IdenticalBinaryConstraint.php';
+require_once __DIR__.'/SwiftMailerTestCase.php';
+require_once __DIR__.'/SwiftMailerSmokeTestCase.php';
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php
new file mode 100644
index 0000000..ba29ba8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php
@@ -0,0 +1,42 @@
+<?php
+
+class Swift_Bug111Test extends \PHPUnit_Framework_TestCase
+{
+ public function testUnstructuredHeaderSlashesShouldNotBeEscaped()
+ {
+ $complicated_header = array(
+ 'to' => array(
+ 'email1@example.com',
+ 'email2@example.com',
+ 'email3@example.com',
+ 'email4@example.com',
+ 'email5@example.com',
+ ),
+ 'sub' => array(
+ '-name-' => array(
+ 'email1',
+ '"email2"',
+ 'email3\\',
+ 'email4',
+ 'email5',
+ ),
+ '-url-' => array(
+ 'http://google.com',
+ 'http://yahoo.com',
+ 'http://hotmail.com',
+ 'http://aol.com',
+ 'http://facebook.com',
+ ),
+ ),
+ );
+ $json = json_encode($complicated_header);
+
+ $message = new Swift_Message();
+ $headers = $message->getHeaders();
+ $headers->addTextHeader('X-SMTPAPI', $json);
+ $header = $headers->get('X-SMTPAPI');
+
+ $this->assertEquals('Swift_Mime_Headers_UnstructuredHeader', get_class($header));
+ $this->assertEquals($json, $header->getFieldBody());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php
new file mode 100644
index 0000000..40b5a77
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php
@@ -0,0 +1,20 @@
+<?php
+
+class Swift_Bug118Test extends \PHPUnit_Framework_TestCase
+{
+ private $_message;
+
+ protected function setUp()
+ {
+ $this->_message = new Swift_Message();
+ }
+
+ public function testCallingGenerateIdChangesTheMessageId()
+ {
+ $currentId = $this->_message->getId();
+ $this->_message->generateId();
+ $newId = $this->_message->getId();
+
+ $this->assertNotEquals($currentId, $newId);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php
new file mode 100644
index 0000000..7563f4d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php
@@ -0,0 +1,38 @@
+<?php
+
+class Swift_Bug206Test extends \PHPUnit_Framework_TestCase
+{
+ private $_factory;
+
+ protected function setUp()
+ {
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
+ new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
+ );
+ $grammar = new Swift_Mime_Grammar();
+ $this->_factory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar);
+ }
+
+ public function testMailboxHeaderEncoding()
+ {
+ $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Name, Name', ' "Family Name, Name" <email@example.org>');
+ $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé, Name', ' Family =?utf-8?Q?Nam=C3=A9=2C?= Name');
+ $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé , Name', ' Family =?utf-8?Q?Nam=C3=A9_=2C?= Name');
+ $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé ;Name', ' Family =?utf-8?Q?Nam=C3=A9_=3BName?= ');
+ }
+
+ private function _testHeaderIsFullyEncoded($email, $name, $expected)
+ {
+ $mailboxHeader = $this->_factory->createMailboxHeader('To', array(
+ $email => $name,
+ ));
+
+ $headerBody = substr($mailboxHeader->toString(), 3, strlen($expected));
+
+ $this->assertEquals($expected, $headerBody);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php
new file mode 100644
index 0000000..f5f057a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php
@@ -0,0 +1,21 @@
+<?php
+
+class Swift_Bug274Test extends \PHPUnit_Framework_TestCase
+{
+ public function testEmptyFileNameAsAttachment()
+ {
+ $message = new Swift_Message();
+ $this->setExpectedException('Swift_IoException', 'The path cannot be empty');
+ $message->attach(Swift_Attachment::fromPath(''));
+ }
+
+ public function testNonEmptyFileNameAsAttachment()
+ {
+ $message = new Swift_Message();
+ try {
+ $message->attach(Swift_Attachment::fromPath(__FILE__));
+ } catch (Exception $e) {
+ $this->fail('Path should not be empty');
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php
new file mode 100644
index 0000000..768bf3d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php
@@ -0,0 +1,75 @@
+<?php
+
+class Swift_Bug34Test extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ Swift_Preferences::getInstance()->setCharset('utf-8');
+ }
+
+ public function testEmbeddedFilesWithMultipartDataCreateMultipartRelatedContentAsAnAlternative()
+ {
+ $message = Swift_Message::newInstance();
+ $message->setCharset('utf-8');
+ $message->setSubject('test subject');
+ $message->addPart('plain part', 'text/plain');
+
+ $image = Swift_Image::newInstance('<image data>', 'image.gif', 'image/gif');
+ $cid = $message->embed($image);
+
+ $message->setBody('<img src="'.$cid.'" />', 'text/html');
+
+ $message->setTo(array('user@domain.tld' => 'User'));
+
+ $message->setFrom(array('other@domain.tld' => 'Other'));
+ $message->setSender(array('other@domain.tld' => 'Other'));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+ $cidVal = $image->getId();
+
+ $this->assertRegExp(
+ '~^'.
+ 'Sender: Other <other@domain.tld>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: test subject'."\r\n".
+ 'From: Other <other@domain.tld>'."\r\n".
+ 'To: User <user@domain.tld>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/plain; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'plain part'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/html; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ '<img.*?/>'.
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: image/gif; name=image.gif'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.$cidVal.'>'."\r\n".
+ 'Content-Disposition: inline; filename=image.gif'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<image data>'), '~').
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString()
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php
new file mode 100644
index 0000000..98999f0
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php
@@ -0,0 +1,73 @@
+<?php
+
+class Swift_Bug35Test extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ Swift_Preferences::getInstance()->setCharset('utf-8');
+ }
+
+ public function testHTMLPartAppearsLastEvenWhenAttachmentsAdded()
+ {
+ $message = Swift_Message::newInstance();
+ $message->setCharset('utf-8');
+ $message->setSubject('test subject');
+ $message->addPart('plain part', 'text/plain');
+
+ $attachment = Swift_Attachment::newInstance('<data>', 'image.gif', 'image/gif');
+ $message->attach($attachment);
+
+ $message->setBody('HTML part', 'text/html');
+
+ $message->setTo(array('user@domain.tld' => 'User'));
+
+ $message->setFrom(array('other@domain.tld' => 'Other'));
+ $message->setSender(array('other@domain.tld' => 'Other'));
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $this->assertRegExp(
+ '~^'.
+ 'Sender: Other <other@domain.tld>'."\r\n".
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: test subject'."\r\n".
+ 'From: Other <other@domain.tld>'."\r\n".
+ 'To: User <user@domain.tld>'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: multipart/alternative;'."\r\n".
+ ' boundary="(.*?)"'."\r\n".
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/plain; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'plain part'.
+ "\r\n\r\n".
+ '--\\1'."\r\n".
+ 'Content-Type: text/html; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'HTML part'.
+ "\r\n\r\n".
+ '--\\1--'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: image/gif; name=image.gif'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename=image.gif'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $message->toString()
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php
new file mode 100644
index 0000000..9deae4f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php
@@ -0,0 +1,192 @@
+<?php
+
+class Swift_Bug38Test extends \PHPUnit_Framework_TestCase
+{
+ private $_attFile;
+ private $_attFileName;
+ private $_attFileType;
+
+ protected function setUp()
+ {
+ $this->_attFileName = 'data.txt';
+ $this->_attFileType = 'text/plain';
+ $this->_attFile = __DIR__.'/../../_samples/files/data.txt';
+ Swift_Preferences::getInstance()->setCharset('utf-8');
+ }
+
+ public function testWritingMessageToByteStreamProducesCorrectStructure()
+ {
+ $message = new Swift_Message();
+ $message->setSubject('test subject');
+ $message->setTo('user@domain.tld');
+ $message->setCc('other@domain.tld');
+ $message->setFrom('user@domain.tld');
+
+ $image = new Swift_Image('<data>', 'image.gif', 'image/gif');
+
+ $cid = $message->embed($image);
+ $message->setBody('HTML part', 'text/html');
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+ $imgId = $image->getId();
+
+ $stream = new Swift_ByteStream_ArrayByteStream();
+
+ $message->toByteStream($stream);
+
+ $this->assertPatternInStream(
+ '~^'.
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: test subject'."\r\n".
+ 'From: user@domain.tld'."\r\n".
+ 'To: user@domain.tld'."\r\n".
+ 'Cc: other@domain.tld'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'HTML part'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: image/gif; name=image.gif'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n".
+ 'Content-Disposition: inline; filename=image.gif'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D',
+ $stream
+ );
+ }
+
+ public function testWritingMessageToByteStreamTwiceProducesCorrectStructure()
+ {
+ $message = new Swift_Message();
+ $message->setSubject('test subject');
+ $message->setTo('user@domain.tld');
+ $message->setCc('other@domain.tld');
+ $message->setFrom('user@domain.tld');
+
+ $image = new Swift_Image('<data>', 'image.gif', 'image/gif');
+
+ $cid = $message->embed($image);
+ $message->setBody('HTML part', 'text/html');
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+ $imgId = $image->getId();
+
+ $pattern = '~^'.
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: test subject'."\r\n".
+ 'From: user@domain.tld'."\r\n".
+ 'To: user@domain.tld'."\r\n".
+ 'Cc: other@domain.tld'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/related;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'HTML part'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: image/gif; name=image.gif'."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n".
+ 'Content-Disposition: inline; filename=image.gif'."\r\n".
+ "\r\n".
+ preg_quote(base64_encode('<data>'), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D'
+ ;
+
+ $streamA = new Swift_ByteStream_ArrayByteStream();
+ $streamB = new Swift_ByteStream_ArrayByteStream();
+
+ $message->toByteStream($streamA);
+ $message->toByteStream($streamB);
+
+ $this->assertPatternInStream($pattern, $streamA);
+ $this->assertPatternInStream($pattern, $streamB);
+ }
+
+ public function testWritingMessageToByteStreamTwiceUsingAFileAttachment()
+ {
+ $message = new Swift_Message();
+ $message->setSubject('test subject');
+ $message->setTo('user@domain.tld');
+ $message->setCc('other@domain.tld');
+ $message->setFrom('user@domain.tld');
+
+ $attachment = Swift_Attachment::fromPath($this->_attFile);
+
+ $message->attach($attachment);
+
+ $message->setBody('HTML part', 'text/html');
+
+ $id = $message->getId();
+ $date = preg_quote(date('r', $message->getDate()), '~');
+ $boundary = $message->getBoundary();
+
+ $streamA = new Swift_ByteStream_ArrayByteStream();
+ $streamB = new Swift_ByteStream_ArrayByteStream();
+
+ $pattern = '~^'.
+ 'Message-ID: <'.$id.'>'."\r\n".
+ 'Date: '.$date."\r\n".
+ 'Subject: test subject'."\r\n".
+ 'From: user@domain.tld'."\r\n".
+ 'To: user@domain.tld'."\r\n".
+ 'Cc: other@domain.tld'."\r\n".
+ 'MIME-Version: 1.0'."\r\n".
+ 'Content-Type: multipart/mixed;'."\r\n".
+ ' boundary="'.$boundary.'"'."\r\n".
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: text/html; charset=utf-8'."\r\n".
+ 'Content-Transfer-Encoding: quoted-printable'."\r\n".
+ "\r\n".
+ 'HTML part'.
+ "\r\n\r\n".
+ '--'.$boundary."\r\n".
+ 'Content-Type: '.$this->_attFileType.'; name='.$this->_attFileName."\r\n".
+ 'Content-Transfer-Encoding: base64'."\r\n".
+ 'Content-Disposition: attachment; filename='.$this->_attFileName."\r\n".
+ "\r\n".
+ preg_quote(base64_encode(file_get_contents($this->_attFile)), '~').
+ "\r\n\r\n".
+ '--'.$boundary.'--'."\r\n".
+ '$~D'
+ ;
+
+ $message->toByteStream($streamA);
+ $message->toByteStream($streamB);
+
+ $this->assertPatternInStream($pattern, $streamA);
+ $this->assertPatternInStream($pattern, $streamB);
+ }
+
+ public function assertPatternInStream($pattern, $stream, $message = '%s')
+ {
+ $string = '';
+ while (false !== $bytes = $stream->read(8192)) {
+ $string .= $bytes;
+ }
+ $this->assertRegExp($pattern, $string, $message);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php
new file mode 100644
index 0000000..b83984f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php
@@ -0,0 +1,38 @@
+<?php
+
+use Mockery as m;
+
+class Swift_Bug518Test extends \PHPUnit_Framework_TestCase
+{
+ public function testIfEmailChangesAfterQueued()
+ {
+ $failedRecipients = 'value';
+ $message = new Swift_Message();
+ $message->setTo('foo@bar.com');
+
+ $that = $this;
+ $messageValidation = function ($m) use ($that) {
+ //the getTo should return the same value as we put in
+ $that->assertEquals('foo@bar.com', key($m->getTo()), 'The message has changed after it was put to the memory queue');
+
+ return true;
+ };
+
+ $transport = m::mock('Swift_Transport');
+ $transport->shouldReceive('isStarted')->andReturn(true);
+ $transport->shouldReceive('send')
+ ->with(m::on($messageValidation), $failedRecipients)
+ ->andReturn(1);
+
+ $memorySpool = new Swift_MemorySpool();
+ $memorySpool->queueMessage($message);
+
+ /*
+ * The message is queued in memory.
+ * Lets change the message
+ */
+ $message->setTo('other@value.com');
+
+ $memorySpool->flushQueue($transport, $failedRecipients);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php
new file mode 100644
index 0000000..48074f0
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php
@@ -0,0 +1,110 @@
+<?php
+
+class Swift_Bug51Test extends \SwiftMailerTestCase
+{
+ private $_attachmentFile;
+ private $_outputFile;
+
+ protected function setUp()
+ {
+ $this->_attachmentFile = sys_get_temp_dir().'/attach.rand.bin';
+ file_put_contents($this->_attachmentFile, '');
+
+ $this->_outputFile = sys_get_temp_dir().'/attach.out.bin';
+ file_put_contents($this->_outputFile, '');
+ }
+
+ protected function tearDown()
+ {
+ unlink($this->_attachmentFile);
+ unlink($this->_outputFile);
+ }
+
+ public function testAttachmentsDoNotGetTruncatedUsingToByteStream()
+ {
+ //Run 100 times with 10KB attachments
+ for ($i = 0; $i < 10; ++$i) {
+ $message = $this->_createMessageWithRandomAttachment(
+ 10000, $this->_attachmentFile
+ );
+
+ file_put_contents($this->_outputFile, '');
+ $message->toByteStream(
+ new Swift_ByteStream_FileByteStream($this->_outputFile, true)
+ );
+
+ $emailSource = file_get_contents($this->_outputFile);
+
+ $this->assertAttachmentFromSourceMatches(
+ file_get_contents($this->_attachmentFile),
+ $emailSource
+ );
+ }
+ }
+
+ public function testAttachmentsDoNotGetTruncatedUsingToString()
+ {
+ //Run 100 times with 10KB attachments
+ for ($i = 0; $i < 10; ++$i) {
+ $message = $this->_createMessageWithRandomAttachment(
+ 10000, $this->_attachmentFile
+ );
+
+ $emailSource = $message->toString();
+
+ $this->assertAttachmentFromSourceMatches(
+ file_get_contents($this->_attachmentFile),
+ $emailSource
+ );
+ }
+ }
+
+ public function assertAttachmentFromSourceMatches($attachmentData, $source)
+ {
+ $encHeader = 'Content-Transfer-Encoding: base64';
+ $base64declaration = strpos($source, $encHeader);
+
+ $attachmentDataStart = strpos($source, "\r\n\r\n", $base64declaration);
+ $attachmentDataEnd = strpos($source, "\r\n--", $attachmentDataStart);
+
+ if (false === $attachmentDataEnd) {
+ $attachmentBase64 = trim(substr($source, $attachmentDataStart));
+ } else {
+ $attachmentBase64 = trim(substr(
+ $source, $attachmentDataStart,
+ $attachmentDataEnd - $attachmentDataStart
+ ));
+ }
+
+ $this->assertIdenticalBinary($attachmentData, base64_decode($attachmentBase64));
+ }
+
+ private function _fillFileWithRandomBytes($byteCount, $file)
+ {
+ // I was going to use dd with if=/dev/random but this way seems more
+ // cross platform even if a hella expensive!!
+
+ file_put_contents($file, '');
+ $fp = fopen($file, 'wb');
+ for ($i = 0; $i < $byteCount; ++$i) {
+ $byteVal = rand(0, 255);
+ fwrite($fp, pack('i', $byteVal));
+ }
+ fclose($fp);
+ }
+
+ private function _createMessageWithRandomAttachment($size, $attachmentPath)
+ {
+ $this->_fillFileWithRandomBytes($size, $attachmentPath);
+
+ $message = Swift_Message::newInstance()
+ ->setSubject('test')
+ ->setBody('test')
+ ->setFrom('a@b.c')
+ ->setTo('d@e.f')
+ ->attach(Swift_Attachment::fromPath($attachmentPath))
+ ;
+
+ return $message;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php
new file mode 100644
index 0000000..263cae5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php
@@ -0,0 +1,38 @@
+<?php
+
+use Mockery as m;
+
+class Swift_Bug534Test extends \PHPUnit_Framework_TestCase
+{
+ public function testEmbeddedImagesAreEmbedded()
+ {
+ $message = Swift_Message::newInstance()
+ ->setFrom('from@example.com')
+ ->setTo('to@example.com')
+ ->setSubject('test')
+ ;
+ $cid = $message->embed(Swift_Image::fromPath(__DIR__.'/../../_samples/files/swiftmailer.png'));
+ $message->setBody('<img src="'.$cid.'" />', 'text/html');
+
+ $that = $this;
+ $messageValidation = function (Swift_Mime_Message $message) use ($that) {
+ preg_match('/cid:(.*)"/', $message->toString(), $matches);
+ $cid = $matches[1];
+ preg_match('/Content-ID: <(.*)>/', $message->toString(), $matches);
+ $contentId = $matches[1];
+ $that->assertEquals($cid, $contentId, 'cid in body and mime part Content-ID differ');
+
+ return true;
+ };
+
+ $failedRecipients = array();
+
+ $transport = m::mock('Swift_Transport');
+ $transport->shouldReceive('isStarted')->andReturn(true);
+ $transport->shouldReceive('send')->with(m::on($messageValidation), $failedRecipients)->andReturn(1);
+
+ $memorySpool = new Swift_MemorySpool();
+ $memorySpool->queueMessage($message);
+ $memorySpool->flushQueue($transport, $failedRecipients);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php
new file mode 100644
index 0000000..3393fb8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php
@@ -0,0 +1,36 @@
+<?php
+
+class Swift_Bug650Test extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider encodingDataProvider
+ *
+ * @param string $name
+ * @param string $expectedEncodedName
+ */
+ public function testMailboxHeaderEncoding($name, $expectedEncodedName)
+ {
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');
+ $encoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream);
+ $header = new Swift_Mime_Headers_MailboxHeader('To', $encoder, new Swift_Mime_Grammar());
+ $header->setCharset('utf-8');
+
+ $header->setNameAddresses(array(
+ 'test@example.com' => $name,
+ ));
+
+ $this->assertSame('To: '.$expectedEncodedName." <test@example.com>\r\n", $header->toString());
+ }
+
+ public function encodingDataProvider()
+ {
+ return array(
+ array('this is " a test ö', 'this is =?utf-8?Q?=22?= a test =?utf-8?Q?=C3=B6?='),
+ array(': this is a test ö', '=?utf-8?Q?=3A?= this is a test =?utf-8?Q?=C3=B6?='),
+ array('( test ö', '=?utf-8?Q?=28?= test =?utf-8?Q?=C3=B6?='),
+ array('[ test ö', '=?utf-8?Q?=5B?= test =?utf-8?Q?=C3=B6?='),
+ array('@ test ö)', '=?utf-8?Q?=40?= test =?utf-8?Q?=C3=B6=29?='),
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php
new file mode 100644
index 0000000..d58242f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php
@@ -0,0 +1,20 @@
+<?php
+
+class Swift_Bug71Test extends \PHPUnit_Framework_TestCase
+{
+ private $_message;
+
+ protected function setUp()
+ {
+ $this->_message = new Swift_Message('test');
+ }
+
+ public function testCallingToStringAfterSettingNewBodyReflectsChanges()
+ {
+ $this->_message->setBody('BODY1');
+ $this->assertRegExp('/BODY1/', $this->_message->toString());
+
+ $this->_message->setBody('BODY2');
+ $this->assertRegExp('/BODY2/', $this->_message->toString());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php
new file mode 100644
index 0000000..899083c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php
@@ -0,0 +1,71 @@
+<?php
+
+class Swift_Bug76Test extends \PHPUnit_Framework_TestCase
+{
+ private $_inputFile;
+ private $_outputFile;
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_inputFile = sys_get_temp_dir().'/in.bin';
+ file_put_contents($this->_inputFile, '');
+
+ $this->_outputFile = sys_get_temp_dir().'/out.bin';
+ file_put_contents($this->_outputFile, '');
+
+ $this->_encoder = $this->_createEncoder();
+ }
+
+ protected function tearDown()
+ {
+ unlink($this->_inputFile);
+ unlink($this->_outputFile);
+ }
+
+ public function testBase64EncodedLineLengthNeverExceeds76CharactersEvenIfArgsDo()
+ {
+ $this->_fillFileWithRandomBytes(1000, $this->_inputFile);
+
+ $os = $this->_createStream($this->_inputFile);
+ $is = $this->_createStream($this->_outputFile);
+
+ $this->_encoder->encodeByteStream($os, $is, 0, 80); //Exceeds 76
+
+ $this->assertMaxLineLength(76, $this->_outputFile,
+ '%s: Line length should not exceed 76 characters'
+ );
+ }
+
+ public function assertMaxLineLength($length, $filePath, $message = '%s')
+ {
+ $lines = file($filePath);
+ foreach ($lines as $line) {
+ $this->assertTrue((strlen(trim($line)) <= 76), $message);
+ }
+ }
+
+ private function _fillFileWithRandomBytes($byteCount, $file)
+ {
+ // I was going to use dd with if=/dev/random but this way seems more
+ // cross platform even if a hella expensive!!
+
+ file_put_contents($file, '');
+ $fp = fopen($file, 'wb');
+ for ($i = 0; $i < $byteCount; ++$i) {
+ $byteVal = rand(0, 255);
+ fwrite($fp, pack('i', $byteVal));
+ }
+ fclose($fp);
+ }
+
+ private function _createEncoder()
+ {
+ return new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+ }
+
+ private function _createStream($file)
+ {
+ return new Swift_ByteStream_FileByteStream($file, true);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php
new file mode 100644
index 0000000..35733ec
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php
@@ -0,0 +1,19 @@
+<?php
+
+
+class Swift_FileByteStreamConsecutiveReadCalls extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @test
+ * @expectedException \Swift_IoException
+ */
+ public function shouldThrowExceptionOnConsecutiveRead()
+ {
+ $fbs = new \Swift_ByteStream_FileByteStream('does not exist');
+ try {
+ $fbs->read(100);
+ } catch (\Swift_IoException $exc) {
+ $fbs->read(100);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php b/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php
new file mode 100644
index 0000000..159c2ae
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php
@@ -0,0 +1,67 @@
+<?php
+
+class MimeEntityFixture implements Swift_Mime_MimeEntity
+{
+ private $level;
+ private $string;
+ private $contentType;
+
+ public function __construct($level = null, $string = '', $contentType = null)
+ {
+ $this->level = $level;
+ $this->string = $string;
+ $this->contentType = $contentType;
+ }
+
+ public function getNestingLevel()
+ {
+ return $this->level;
+ }
+
+ public function toString()
+ {
+ return $this->string;
+ }
+
+ public function getContentType()
+ {
+ return $this->contentType;
+ }
+
+ // These methods are here to account for the implemented interfaces
+ public function getId()
+ {
+ }
+
+ public function getHeaders()
+ {
+ }
+
+ public function getBody()
+ {
+ }
+
+ public function setBody($body, $contentType = null)
+ {
+ }
+
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ }
+
+ public function charsetChanged($charset)
+ {
+ }
+
+ public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
+ {
+ }
+
+ public function getChildren()
+ {
+ }
+
+ public function setChildren(array $children)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default b/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default
new file mode 100644
index 0000000..0de2763
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ Swift Mailer V4 smoke test configuration.
+
+ YOU ONLY NEED TO EDIT THIS FILE IF YOU WISH TO RUN THE SMOKE TESTS.
+
+ The smoke tests are run by default when "All Tests" are run with the
+ testing suite, however, without configuration options here only the unit tests
+ will be run and the smoke tests will be skipped.
+ */
+
+/*
+ Defines: The an address which Swift can send to (it will also send "from" this address).
+ Recommended: (your own email address?)
+ */
+define('SWIFT_SMOKE_EMAIL_ADDRESS', 'test@swiftmailer.org');
+
+/*
+ Defines: The specific transport you want to mail with.
+ Recommended: Any of 'smtp', 'sendmail' or 'mail'
+ */
+define('SWIFT_SMOKE_TRANSPORT_TYPE', 'smtp');
+
+// SMTP-specific settings
+
+/*
+ Defines: An SMTP server to connect to
+ Recommended: smtp.your-isp.com (varies wildly!)
+ */
+define('SWIFT_SMOKE_SMTP_HOST', 'localhost');
+
+/*
+ Defines: The SMTP port to connect to
+ Recommended: 25
+ */
+define('SWIFT_SMOKE_SMTP_PORT', '4456');
+
+/*
+ Defines: A username to authenticate with SMTP (if needed).
+ Recommended: (none)
+ */
+define('SWIFT_SMOKE_SMTP_USER', '');
+
+/*
+ Defines: A password to authenticate with SMTP (if needed).
+ Recommended: (none)
+ */
+define('SWIFT_SMOKE_SMTP_PASS', '');
+
+/*
+ Defines: The encryption needed on your SMTP server.
+ Recommended: (none), or 'tls' or 'ssl'
+ */
+define('SWIFT_SMOKE_SMTP_ENCRYPTION', '');
+
+// Sendmail specific settings
+
+/*
+ Defines: The command to use when sending via sendmail
+ Recommended: /usr/sbin/sendmail -bs (or "-oi -t")
+ */
+define('SWIFT_SMOKE_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/AttachmentSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/AttachmentSmokeTest.php
new file mode 100644
index 0000000..5a4ccde
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/AttachmentSmokeTest.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @group smoke
+ */
+class Swift_Smoke_AttachmentSmokeTest extends SwiftMailerSmokeTestCase
+{
+ private $_attFile;
+
+ protected function setUp()
+ {
+ parent::setup(); // For skip
+ $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
+ }
+
+ public function testAttachmentSending()
+ {
+ $mailer = $this->_getMailer();
+ $message = Swift_Message::newInstance()
+ ->setSubject('[Swift Mailer] AttachmentSmokeTest')
+ ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
+ ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
+ ->setBody('This message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL.
+ 'When unzipped, the archive should produce a text file which reads:'.PHP_EOL.
+ '"This is part of a Swift Mailer v4 smoke test."'
+ )
+ ->attach(Swift_Attachment::fromPath($this->_attFile))
+ ;
+ $this->assertEquals(1, $mailer->send($message),
+ '%s: The smoke test should send a single message'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php
new file mode 100644
index 0000000..c7501d4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @group smoke
+ */
+class Swift_Smoke_BasicSmokeTest extends SwiftMailerSmokeTestCase
+{
+ public function testBasicSending()
+ {
+ $mailer = $this->_getMailer();
+ $message = Swift_Message::newInstance()
+ ->setSubject('[Swift Mailer] BasicSmokeTest')
+ ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
+ ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
+ ->setBody('One, two, three, four, five...'.PHP_EOL.
+ 'six, seven, eight...'
+ )
+ ;
+ $this->assertEquals(1, $mailer->send($message),
+ '%s: The smoke test should send a single message'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php
new file mode 100644
index 0000000..3b13cc5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @group smoke
+ */
+class Swift_Smoke_HtmlWithAttachmentSmokeTest extends SwiftMailerSmokeTestCase
+{
+ private $_attFile;
+
+ protected function setUp()
+ {
+ $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
+ }
+
+ public function testAttachmentSending()
+ {
+ $mailer = $this->_getMailer();
+ $message = Swift_Message::newInstance('[Swift Mailer] HtmlWithAttachmentSmokeTest')
+ ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
+ ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
+ ->attach(Swift_Attachment::fromPath($this->_attFile))
+ ->setBody('<p>This HTML-formatted message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL.
+ 'When unzipped, the archive should produce a text file which reads:</p>'.PHP_EOL.
+ '<p><q>This is part of a Swift Mailer v4 smoke test.</q></p>', 'text/html'
+ )
+ ;
+ $this->assertEquals(1, $mailer->send($message),
+ '%s: The smoke test should send a single message'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php
new file mode 100644
index 0000000..b9ebef5
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @group smoke
+ */
+class Swift_Smoke_InternationalSmokeTest extends SwiftMailerSmokeTestCase
+{
+ private $_attFile;
+
+ protected function setUp()
+ {
+ parent::setup(); // For skip
+ $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
+ }
+
+ public function testAttachmentSending()
+ {
+ $mailer = $this->_getMailer();
+ $message = Swift_Message::newInstance()
+ ->setCharset('utf-8')
+ ->setSubject('[Swift Mailer] InternationalSmokeTest (διεθνής)')
+ ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Χριστοφορου (Swift Mailer)'))
+ ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
+ ->setBody('This message should contain an attached ZIP file (named "κείμενο, εδάφιο, θέμα.zip").'.PHP_EOL.
+ 'When unzipped, the archive should produce a text file which reads:'.PHP_EOL.
+ '"This is part of a Swift Mailer v4 smoke test."'.PHP_EOL.
+ PHP_EOL.
+ 'Following is some arbitrary Greek text:'.PHP_EOL.
+ 'Δεν βρέθηκαν λέξεις.'
+ )
+ ->attach(Swift_Attachment::fromPath($this->_attFile)
+ ->setContentType('application/zip')
+ ->setFilename('κείμενο, εδάφιο, θέμα.zip')
+ )
+ ;
+ $this->assertEquals(1, $mailer->send($message),
+ '%s: The smoke test should send a single message'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php
new file mode 100644
index 0000000..60ebb66
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php
@@ -0,0 +1,201 @@
+<?php
+
+class Swift_ByteStream_ArrayByteStreamTest extends \PHPUnit_Framework_TestCase
+{
+ public function testReadingSingleBytesFromBaseInput()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+ $output = array();
+ while (false !== $bytes = $bs->read(1)) {
+ $output[] = $bytes;
+ }
+ $this->assertEquals($input, $output,
+ '%s: Bytes read from stream should be the same as bytes in constructor'
+ );
+ }
+
+ public function testReadingMultipleBytesFromBaseInput()
+ {
+ $input = array('a', 'b', 'c', 'd');
+ $bs = $this->_createArrayStream($input);
+ $output = array();
+ while (false !== $bytes = $bs->read(2)) {
+ $output[] = $bytes;
+ }
+ $this->assertEquals(array('ab', 'cd'), $output,
+ '%s: Bytes read from stream should be in pairs'
+ );
+ }
+
+ public function testReadingOddOffsetOnLastByte()
+ {
+ $input = array('a', 'b', 'c', 'd', 'e');
+ $bs = $this->_createArrayStream($input);
+ $output = array();
+ while (false !== $bytes = $bs->read(2)) {
+ $output[] = $bytes;
+ }
+ $this->assertEquals(array('ab', 'cd', 'e'), $output,
+ '%s: Bytes read from stream should be in pairs except final read'
+ );
+ }
+
+ public function testSettingPointerPartway()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+ $bs->setReadPointer(1);
+ $this->assertEquals('b', $bs->read(1),
+ '%s: Byte should be second byte since pointer as at offset 1'
+ );
+ }
+
+ public function testResettingPointerAfterExhaustion()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+ while (false !== $bs->read(1));
+
+ $bs->setReadPointer(0);
+ $this->assertEquals('a', $bs->read(1),
+ '%s: Byte should be first byte since pointer as at offset 0'
+ );
+ }
+
+ public function testPointerNeverSetsBelowZero()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+
+ $bs->setReadPointer(-1);
+ $this->assertEquals('a', $bs->read(1),
+ '%s: Byte should be first byte since pointer should be at offset 0'
+ );
+ }
+
+ public function testPointerNeverSetsAboveStackSize()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+
+ $bs->setReadPointer(3);
+ $this->assertFalse($bs->read(1),
+ '%s: Stream should be at end and thus return false'
+ );
+ }
+
+ public function testBytesCanBeWrittenToStream()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+
+ $bs->write('de');
+
+ $output = array();
+ while (false !== $bytes = $bs->read(1)) {
+ $output[] = $bytes;
+ }
+ $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), $output,
+ '%s: Bytes read from stream should be from initial stack + written'
+ );
+ }
+
+ public function testContentsCanBeFlushed()
+ {
+ $input = array('a', 'b', 'c');
+ $bs = $this->_createArrayStream($input);
+
+ $bs->flushBuffers();
+
+ $this->assertFalse($bs->read(1),
+ '%s: Contents have been flushed so read() should return false'
+ );
+ }
+
+ public function testConstructorCanTakeStringArgument()
+ {
+ $bs = $this->_createArrayStream('abc');
+ $output = array();
+ while (false !== $bytes = $bs->read(1)) {
+ $output[] = $bytes;
+ }
+ $this->assertEquals(array('a', 'b', 'c'), $output,
+ '%s: Bytes read from stream should be the same as bytes in constructor'
+ );
+ }
+
+ public function testBindingOtherStreamsMirrorsWriteOperations()
+ {
+ $bs = $this->_createArrayStream('');
+ $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is2->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+
+ $bs->bind($is1);
+ $bs->bind($is2);
+
+ $bs->write('x');
+ $bs->write('y');
+ }
+
+ public function testBindingOtherStreamsMirrorsFlushOperations()
+ {
+ $bs = $this->_createArrayStream('');
+ $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+
+ $is1->expects($this->once())
+ ->method('flushBuffers');
+ $is2->expects($this->once())
+ ->method('flushBuffers');
+
+ $bs->bind($is1);
+ $bs->bind($is2);
+
+ $bs->flushBuffers();
+ }
+
+ public function testUnbindingStreamPreventsFurtherWrites()
+ {
+ $bs = $this->_createArrayStream('');
+ $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
+
+ $is1->expects($this->at(0))
+ ->method('write')
+ ->with('x');
+ $is1->expects($this->at(1))
+ ->method('write')
+ ->with('y');
+ $is2->expects($this->once())
+ ->method('write')
+ ->with('x');
+
+ $bs->bind($is1);
+ $bs->bind($is2);
+
+ $bs->write('x');
+
+ $bs->unbind($is2);
+
+ $bs->write('y');
+ }
+
+ private function _createArrayStream($input)
+ {
+ return new Swift_ByteStream_ArrayByteStream($input);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php
new file mode 100644
index 0000000..3f7a46c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php
@@ -0,0 +1,43 @@
+<?php
+
+class Swift_CharacterReader_GenericFixedWidthReaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInitialByteSizeMatchesWidth()
+ {
+ $reader = new Swift_CharacterReader_GenericFixedWidthReader(1);
+ $this->assertSame(1, $reader->getInitialByteSize());
+
+ $reader = new Swift_CharacterReader_GenericFixedWidthReader(4);
+ $this->assertSame(4, $reader->getInitialByteSize());
+ }
+
+ public function testValidationValueIsBasedOnOctetCount()
+ {
+ $reader = new Swift_CharacterReader_GenericFixedWidthReader(4);
+
+ $this->assertSame(
+ 1, $reader->validateByteSequence(array(0x01, 0x02, 0x03), 3)
+ ); //3 octets
+
+ $this->assertSame(
+ 2, $reader->validateByteSequence(array(0x01, 0x0A), 2)
+ ); //2 octets
+
+ $this->assertSame(
+ 3, $reader->validateByteSequence(array(0xFE), 1)
+ ); //1 octet
+
+ $this->assertSame(
+ 0, $reader->validateByteSequence(array(0xFE, 0x03, 0x67, 0x9A), 4)
+ ); //All 4 octets
+ }
+
+ public function testValidationFailsIfTooManyOctets()
+ {
+ $reader = new Swift_CharacterReader_GenericFixedWidthReader(6);
+
+ $this->assertSame(-1, $reader->validateByteSequence(
+ array(0xFE, 0x03, 0x67, 0x9A, 0x10, 0x09, 0x85), 7
+ )); //7 octets
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php
new file mode 100644
index 0000000..0d56736
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php
@@ -0,0 +1,52 @@
+<?php
+
+class Swift_CharacterReader_UsAsciiReaderTest extends \PHPUnit_Framework_TestCase
+{
+ /*
+
+ for ($c = '', $size = 1; false !== $bytes = $os->read($size); ) {
+ $c .= $bytes;
+ $size = $v->validateCharacter($c);
+ if (-1 == $size) {
+ throw new Exception( ... invalid char .. );
+ } elseif (0 == $size) {
+ return $c; //next character in $os
+ }
+ }
+
+ */
+
+ private $_reader;
+
+ protected function setUp()
+ {
+ $this->_reader = new Swift_CharacterReader_UsAsciiReader();
+ }
+
+ public function testAllValidAsciiCharactersReturnZero()
+ {
+ for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) {
+ $this->assertSame(
+ 0, $this->_reader->validateByteSequence(array($ordinal), 1)
+ );
+ }
+ }
+
+ public function testMultipleBytesAreInvalid()
+ {
+ for ($ordinal = 0x00; $ordinal <= 0x7F; $ordinal += 2) {
+ $this->assertSame(
+ -1, $this->_reader->validateByteSequence(array($ordinal, $ordinal + 1), 2)
+ );
+ }
+ }
+
+ public function testBytesAboveAsciiRangeAreInvalid()
+ {
+ for ($ordinal = 0x80; $ordinal <= 0xFF; ++$ordinal) {
+ $this->assertSame(
+ -1, $this->_reader->validateByteSequence(array($ordinal), 1)
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php
new file mode 100644
index 0000000..ec17eeb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php
@@ -0,0 +1,65 @@
+<?php
+
+class Swift_CharacterReader_Utf8ReaderTest extends \PHPUnit_Framework_TestCase
+{
+ private $_reader;
+
+ protected function setUp()
+ {
+ $this->_reader = new Swift_CharacterReader_Utf8Reader();
+ }
+
+ public function testLeading7BitOctetCausesReturnZero()
+ {
+ for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) {
+ $this->assertSame(
+ 0, $this->_reader->validateByteSequence(array($ordinal), 1)
+ );
+ }
+ }
+
+ public function testLeadingByteOf2OctetCharCausesReturn1()
+ {
+ for ($octet = 0xC0; $octet <= 0xDF; ++$octet) {
+ $this->assertSame(
+ 1, $this->_reader->validateByteSequence(array($octet), 1)
+ );
+ }
+ }
+
+ public function testLeadingByteOf3OctetCharCausesReturn2()
+ {
+ for ($octet = 0xE0; $octet <= 0xEF; ++$octet) {
+ $this->assertSame(
+ 2, $this->_reader->validateByteSequence(array($octet), 1)
+ );
+ }
+ }
+
+ public function testLeadingByteOf4OctetCharCausesReturn3()
+ {
+ for ($octet = 0xF0; $octet <= 0xF7; ++$octet) {
+ $this->assertSame(
+ 3, $this->_reader->validateByteSequence(array($octet), 1)
+ );
+ }
+ }
+
+ public function testLeadingByteOf5OctetCharCausesReturn4()
+ {
+ for ($octet = 0xF8; $octet <= 0xFB; ++$octet) {
+ $this->assertSame(
+ 4, $this->_reader->validateByteSequence(array($octet), 1)
+ );
+ }
+ }
+
+ public function testLeadingByteOf6OctetCharCausesReturn5()
+ {
+ for ($octet = 0xFC; $octet <= 0xFD; ++$octet) {
+ $this->assertSame(
+ 5, $this->_reader->validateByteSequence(array($octet), 1)
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php
new file mode 100644
index 0000000..977051e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php
@@ -0,0 +1,358 @@
+<?php
+
+class Swift_CharacterStream_ArrayCharacterStreamTest extends \SwiftMailerTestCase
+{
+ public function testValidatorAlgorithmOnImportString()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importString(pack('C*',
+ 0xD0, 0x94,
+ 0xD0, 0xB6,
+ 0xD0, 0xBE,
+ 0xD1, 0x8D,
+ 0xD0, 0xBB,
+ 0xD0, 0xB0
+ )
+ );
+ }
+
+ public function testCharactersWrittenUseValidator()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $stream->write(pack('C*',
+ 0xD0, 0xBB,
+ 0xD1, 0x8E,
+ 0xD0, 0xB1,
+ 0xD1, 0x8B,
+ 0xD1, 0x85
+ )
+ );
+ }
+
+ public function testReadCharactersAreInTact()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ //String
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ //Stream
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $stream->write(pack('C*',
+ 0xD0, 0xBB,
+ 0xD1, 0x8E,
+ 0xD0, 0xB1,
+ 0xD1, 0x8B,
+ 0xD1, 0x85
+ )
+ );
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
+ $this->assertIdenticalBinary(
+ pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2)
+ );
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1));
+ $this->assertIdenticalBinary(
+ pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3)
+ );
+ $this->assertIdenticalBinary(pack('C*', 0xD1, 0x85), $stream->read(1));
+
+ $this->assertFalse($stream->read(1));
+ }
+
+ public function testCharactersCanBeReadAsByteArrays()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ //String
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ //Stream
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $stream->write(pack('C*',
+ 0xD0, 0xBB,
+ 0xD1, 0x8E,
+ 0xD0, 0xB1,
+ 0xD1, 0x8B,
+ 0xD1, 0x85
+ )
+ );
+
+ $this->assertEquals(array(0xD0, 0x94), $stream->readBytes(1));
+ $this->assertEquals(array(0xD0, 0xB6, 0xD0, 0xBE), $stream->readBytes(2));
+ $this->assertEquals(array(0xD0, 0xBB), $stream->readBytes(1));
+ $this->assertEquals(
+ array(0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->readBytes(3)
+ );
+ $this->assertEquals(array(0xD1, 0x85), $stream->readBytes(1));
+
+ $this->assertFalse($stream->readBytes(1));
+ }
+
+ public function testRequestingLargeCharCountPastEndOfStream()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE),
+ $stream->read(100)
+ );
+
+ $this->assertFalse($stream->read(1));
+ }
+
+ public function testRequestingByteArrayCountPastEndOfStream()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $this->assertEquals(array(0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE),
+ $stream->readBytes(100)
+ );
+
+ $this->assertFalse($stream->readBytes(1));
+ }
+
+ public function testPointerOffsetCanBeSet()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
+
+ $stream->setPointer(0);
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
+
+ $stream->setPointer(2);
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1));
+ }
+
+ public function testContentsCanBeFlushed()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+
+ $stream->flushContents();
+
+ $this->assertFalse($stream->read(1));
+ }
+
+ public function testByteStreamCanBeImportingUsesValidator()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+ $os = $this->_getByteStream();
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $os->shouldReceive('setReadPointer')
+ ->between(0, 1)
+ ->with(0);
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE));
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importByteStream($os);
+ }
+
+ public function testImportingStreamProducesCorrectCharArray()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+ $os = $this->_getByteStream();
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');
+
+ $os->shouldReceive('setReadPointer')
+ ->between(0, 1)
+ ->with(0);
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
+ $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE));
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
+
+ $stream->importByteStream($os);
+
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB6), $stream->read(1));
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1));
+
+ $this->assertFalse($stream->read(1));
+ }
+
+ public function testAlgorithmWithFixedWidthCharsets()
+ {
+ $reader = $this->_getReader();
+ $factory = $this->_getFactory($reader);
+
+ $reader->shouldReceive('getInitialByteSize')
+ ->zeroOrMoreTimes()
+ ->andReturn(2);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1, 0x8D), 2);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xBB), 2);
+ $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xB0), 2);
+
+ $stream = new Swift_CharacterStream_ArrayCharacterStream(
+ $factory, 'utf-8'
+ );
+ $stream->importString(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0));
+
+ $this->assertIdenticalBinary(pack('C*', 0xD1, 0x8D), $stream->read(1));
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1));
+ $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB0), $stream->read(1));
+
+ $this->assertFalse($stream->read(1));
+ }
+
+ private function _getReader()
+ {
+ return $this->getMockery('Swift_CharacterReader');
+ }
+
+ private function _getFactory($reader)
+ {
+ $factory = $this->getMockery('Swift_CharacterReaderFactory');
+ $factory->shouldReceive('getReaderFor')
+ ->zeroOrMoreTimes()
+ ->with('utf-8')
+ ->andReturn($reader);
+
+ return $factory;
+ }
+
+ private function _getByteStream()
+ {
+ return $this->getMockery('Swift_OutputByteStream');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php
new file mode 100644
index 0000000..ccd14f6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php
@@ -0,0 +1,176 @@
+<?php
+
+class One
+{
+ public $arg1;
+ public $arg2;
+
+ public function __construct($arg1 = null, $arg2 = null)
+ {
+ $this->arg1 = $arg1;
+ $this->arg2 = $arg2;
+ }
+}
+
+class Swift_DependencyContainerTest extends \PHPUnit_Framework_TestCase
+{
+ private $_container;
+
+ protected function setUp()
+ {
+ $this->_container = new Swift_DependencyContainer();
+ }
+
+ public function testRegisterAndLookupValue()
+ {
+ $this->_container->register('foo')->asValue('bar');
+ $this->assertEquals('bar', $this->_container->lookup('foo'));
+ }
+
+ public function testHasReturnsTrueForRegisteredValue()
+ {
+ $this->_container->register('foo')->asValue('bar');
+ $this->assertTrue($this->_container->has('foo'));
+ }
+
+ public function testHasReturnsFalseForUnregisteredValue()
+ {
+ $this->assertFalse($this->_container->has('foo'));
+ }
+
+ public function testRegisterAndLookupNewInstance()
+ {
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $this->assertInstanceOf('One', $this->_container->lookup('one'));
+ }
+
+ public function testHasReturnsTrueForRegisteredInstance()
+ {
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $this->assertTrue($this->_container->has('one'));
+ }
+
+ public function testNewInstanceIsAlwaysNew()
+ {
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $a = $this->_container->lookup('one');
+ $b = $this->_container->lookup('one');
+ $this->assertEquals($a, $b);
+ }
+
+ public function testRegisterAndLookupSharedInstance()
+ {
+ $this->_container->register('one')->asSharedInstanceOf('One');
+ $this->assertInstanceOf('One', $this->_container->lookup('one'));
+ }
+
+ public function testHasReturnsTrueForSharedInstance()
+ {
+ $this->_container->register('one')->asSharedInstanceOf('One');
+ $this->assertTrue($this->_container->has('one'));
+ }
+
+ public function testMultipleSharedInstancesAreSameInstance()
+ {
+ $this->_container->register('one')->asSharedInstanceOf('One');
+ $a = $this->_container->lookup('one');
+ $b = $this->_container->lookup('one');
+ $this->assertEquals($a, $b);
+ }
+
+ public function testNewInstanceWithDependencies()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('one')->asNewInstanceOf('One')
+ ->withDependencies(array('foo'));
+ $obj = $this->_container->lookup('one');
+ $this->assertSame('FOO', $obj->arg1);
+ }
+
+ public function testNewInstanceWithMultipleDependencies()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('bar')->asValue(42);
+ $this->_container->register('one')->asNewInstanceOf('One')
+ ->withDependencies(array('foo', 'bar'));
+ $obj = $this->_container->lookup('one');
+ $this->assertSame('FOO', $obj->arg1);
+ $this->assertSame(42, $obj->arg2);
+ }
+
+ public function testNewInstanceWithInjectedObjects()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $this->_container->register('two')->asNewInstanceOf('One')
+ ->withDependencies(array('one', 'foo'));
+ $obj = $this->_container->lookup('two');
+ $this->assertEquals($this->_container->lookup('one'), $obj->arg1);
+ $this->assertSame('FOO', $obj->arg2);
+ }
+
+ public function testNewInstanceWithAddConstructorValue()
+ {
+ $this->_container->register('one')->asNewInstanceOf('One')
+ ->addConstructorValue('x')
+ ->addConstructorValue(99);
+ $obj = $this->_container->lookup('one');
+ $this->assertSame('x', $obj->arg1);
+ $this->assertSame(99, $obj->arg2);
+ }
+
+ public function testNewInstanceWithAddConstructorLookup()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('bar')->asValue(42);
+ $this->_container->register('one')->asNewInstanceOf('One')
+ ->addConstructorLookup('foo')
+ ->addConstructorLookup('bar');
+
+ $obj = $this->_container->lookup('one');
+ $this->assertSame('FOO', $obj->arg1);
+ $this->assertSame(42, $obj->arg2);
+ }
+
+ public function testResolvedDependenciesCanBeLookedUp()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $this->_container->register('two')->asNewInstanceOf('One')
+ ->withDependencies(array('one', 'foo'));
+ $deps = $this->_container->createDependenciesFor('two');
+ $this->assertEquals(
+ array($this->_container->lookup('one'), 'FOO'), $deps
+ );
+ }
+
+ public function testArrayOfDependenciesCanBeSpecified()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('one')->asNewInstanceOf('One');
+ $this->_container->register('two')->asNewInstanceOf('One')
+ ->withDependencies(array(array('one', 'foo'), 'foo'));
+
+ $obj = $this->_container->lookup('two');
+ $this->assertEquals(array($this->_container->lookup('one'), 'FOO'), $obj->arg1);
+ $this->assertSame('FOO', $obj->arg2);
+ }
+
+ public function testAliasCanBeSet()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('bar')->asAliasOf('foo');
+
+ $this->assertSame('FOO', $this->_container->lookup('bar'));
+ }
+
+ public function testAliasOfAliasCanBeSet()
+ {
+ $this->_container->register('foo')->asValue('FOO');
+ $this->_container->register('bar')->asAliasOf('foo');
+ $this->_container->register('zip')->asAliasOf('bar');
+ $this->_container->register('button')->asAliasOf('zip');
+
+ $this->assertSame('FOO', $this->_container->lookup('button'));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php
new file mode 100644
index 0000000..b89eb9f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php
@@ -0,0 +1,173 @@
+<?php
+
+class Swift_Encoder_Base64EncoderTest extends \PHPUnit_Framework_TestCase
+{
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_encoder = new Swift_Encoder_Base64Encoder();
+ }
+
+ /*
+ There's really no point in testing the entire base64 encoding to the
+ level QP encoding has been tested. base64_encode() has been in PHP for
+ years.
+ */
+
+ public function testInputOutputRatioIs3to4Bytes()
+ {
+ /*
+ RFC 2045, 6.8
+
+ The encoding process represents 24-bit groups of input bits as output
+ strings of 4 encoded characters. Proceeding from left to right, a
+ 24-bit input group is formed by concatenating 3 8bit input groups.
+ These 24 bits are then treated as 4 concatenated 6-bit groups, each
+ of which is translated into a single digit in the base64 alphabet.
+ */
+
+ $this->assertEquals(
+ 'MTIz', $this->_encoder->encodeString('123'),
+ '%s: 3 bytes of input should yield 4 bytes of output'
+ );
+ $this->assertEquals(
+ 'MTIzNDU2', $this->_encoder->encodeString('123456'),
+ '%s: 6 bytes in input should yield 8 bytes of output'
+ );
+ $this->assertEquals(
+ 'MTIzNDU2Nzg5', $this->_encoder->encodeString('123456789'),
+ '%s: 9 bytes in input should yield 12 bytes of output'
+ );
+ }
+
+ public function testPadLength()
+ {
+ /*
+ RFC 2045, 6.8
+
+ Special processing is performed if fewer than 24 bits are available
+ at the end of the data being encoded. A full encoding quantum is
+ always completed at the end of a body. When fewer than 24 input bits
+ are available in an input group, zero bits are added (on the right)
+ to form an integral number of 6-bit groups. Padding at the end of
+ the data is performed using the "=" character. Since all base64
+ input is an integral number of octets, only the following cases can
+ arise: (1) the final quantum of encoding input is an integral
+ multiple of 24 bits; here, the final unit of encoded output will be
+ an integral multiple of 4 characters with no "=" padding, (2) the
+ final quantum of encoding input is exactly 8 bits; here, the final
+ unit of encoded output will be two characters followed by two "="
+ padding characters, or (3) the final quantum of encoding input is
+ exactly 16 bits; here, the final unit of encoded output will be three
+ characters followed by one "=" padding character.
+ */
+
+ for ($i = 0; $i < 30; ++$i) {
+ $input = pack('C', rand(0, 255));
+ $this->assertRegExp(
+ '~^[a-zA-Z0-9/\+]{2}==$~', $this->_encoder->encodeString($input),
+ '%s: A single byte should have 2 bytes of padding'
+ );
+ }
+
+ for ($i = 0; $i < 30; ++$i) {
+ $input = pack('C*', rand(0, 255), rand(0, 255));
+ $this->assertRegExp(
+ '~^[a-zA-Z0-9/\+]{3}=$~', $this->_encoder->encodeString($input),
+ '%s: Two bytes should have 1 byte of padding'
+ );
+ }
+
+ for ($i = 0; $i < 30; ++$i) {
+ $input = pack('C*', rand(0, 255), rand(0, 255), rand(0, 255));
+ $this->assertRegExp(
+ '~^[a-zA-Z0-9/\+]{4}$~', $this->_encoder->encodeString($input),
+ '%s: Three bytes should have no padding'
+ );
+ }
+ }
+
+ public function testMaximumLineLengthIs76Characters()
+ {
+ /*
+ The encoded output stream must be represented in lines of no more
+ than 76 characters each. All line breaks or other characters not
+ found in Table 1 must be ignored by decoding software.
+ */
+
+ $input =
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ $output =
+ 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+ 'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1'."\r\n".//76 *
+ 'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3'.//38
+ 'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla'."\r\n".//76 *
+ 'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl'.//38
+ 'NUVVZXWFla'; //48
+
+ $this->assertEquals(
+ $output, $this->_encoder->encodeString($input),
+ '%s: Lines should be no more than 76 characters'
+ );
+ }
+
+ public function testMaximumLineLengthCanBeSpecified()
+ {
+ $input =
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ $output =
+ 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+ 'NERUZHSElKS0'."\r\n".//50 *
+ 'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk'.//38
+ 'ZWZnaGlqa2xt'."\r\n".//50 *
+ 'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1'.//38
+ 'BRUlNUVVZXWF'."\r\n".//50 *
+ 'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR'.//38
+ 'UlNUVVZXWFla'; //50 *
+
+ $this->assertEquals(
+ $output, $this->_encoder->encodeString($input, 0, 50),
+ '%s: Lines should be no more than 100 characters'
+ );
+ }
+
+ public function testFirstLineLengthCanBeDifferent()
+ {
+ $input =
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ '1234567890'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ $output =
+ 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+ 'NERUZHSElKS0xNTk9QU'."\r\n".//57 *
+ 'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl'.//38
+ 'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT'."\r\n".//76 *
+ 'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R'.//38
+ 'FRkdISUpLTE1OT1BRUlNUVVZXWFla'; //67
+
+ $this->assertEquals(
+ $output, $this->_encoder->encodeString($input, 19),
+ '%s: First line offset is 19 so first line should be 57 chars long'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php
new file mode 100644
index 0000000..6740f22
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php
@@ -0,0 +1,400 @@
+<?php
+
+class Swift_Encoder_QpEncoderTest extends \SwiftMailerTestCase
+{
+ /* -- RFC 2045, 6.7 --
+ (1) (General 8bit representation) Any octet, except a CR or
+ LF that is part of a CRLF line break of the canonical
+ (standard) form of the data being encoded, may be
+ represented by an "=" followed by a two digit
+ hexadecimal representation of the octet's value. The
+ digits of the hexadecimal alphabet, for this purpose,
+ are "0123456789ABCDEF". Uppercase letters must be
+ used; lowercase letters are not allowed. Thus, for
+ example, the decimal value 12 (US-ASCII form feed) can
+ be represented by "=0C", and the decimal value 61 (US-
+ ASCII EQUAL SIGN) can be represented by "=3D". This
+ rule must be followed except when the following rules
+ allow an alternative encoding.
+ */
+
+ public function testPermittedCharactersAreNotEncoded()
+ {
+ /* -- RFC 2045, 6.7 --
+ (2) (Literal representation) Octets with decimal values of
+ 33 through 60 inclusive, and 62 through 126, inclusive,
+ MAY be represented as the US-ASCII characters which
+ correspond to those octets (EXCLAMATION POINT through
+ LESS THAN, and GREATER THAN through TILDE,
+ respectively).
+ */
+
+ foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) {
+ $char = chr($ordinal);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($char);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+
+ $this->assertIdenticalBinary($char, $encoder->encodeString($char));
+ }
+ }
+
+ public function testWhiteSpaceAtLineEndingIsEncoded()
+ {
+ /* -- RFC 2045, 6.7 --
+ (3) (White Space) Octets with values of 9 and 32 MAY be
+ represented as US-ASCII TAB (HT) and SPACE characters,
+ respectively, but MUST NOT be so represented at the end
+ of an encoded line. Any TAB (HT) or SPACE characters
+ on an encoded line MUST thus be followed on that line
+ by a printable character. In particular, an "=" at the
+ end of an encoded line, indicating a soft line break
+ (see rule #5) may follow one or more TAB (HT) or SPACE
+ characters. It follows that an octet with decimal
+ value 9 or 32 appearing at the end of an encoded line
+ must be represented according to Rule #1. This rule is
+ necessary because some MTAs (Message Transport Agents,
+ programs which transport messages from one user to
+ another, or perform a portion of such transfers) are
+ known to pad lines of text with SPACEs, and others are
+ known to remove "white space" characters from the end
+ of a line. Therefore, when decoding a Quoted-Printable
+ body, any trailing white space on a line must be
+ deleted, as it will necessarily have been added by
+ intermediate transport agents.
+ */
+
+ $HT = chr(0x09); //9
+ $SPACE = chr(0x20); //32
+
+ //HT
+ $string = 'a'.$HT.$HT."\r\n".'b';
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals(
+ 'a'.$HT.'=09'."\r\n".'b',
+ $encoder->encodeString($string)
+ );
+
+ //SPACE
+ $string = 'a'.$SPACE.$SPACE."\r\n".'b';
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals(
+ 'a'.$SPACE.'=20'."\r\n".'b',
+ $encoder->encodeString($string)
+ );
+ }
+
+ public function testCRLFIsLeftAlone()
+ {
+ /*
+ (4) (Line Breaks) A line break in a text body, represented
+ as a CRLF sequence in the text canonical form, must be
+ represented by a (RFC 822) line break, which is also a
+ CRLF sequence, in the Quoted-Printable encoding. Since
+ the canonical representation of media types other than
+ text do not generally include the representation of
+ line breaks as CRLF sequences, no hard line breaks
+ (i.e. line breaks that are intended to be meaningful
+ and to be displayed to the user) can occur in the
+ quoted-printable encoding of such types. Sequences
+ like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
+ appear in non-text data represented in quoted-
+ printable, of course.
+
+ Note that many implementations may elect to encode the
+ local representation of various content types directly
+ rather than converting to canonical form first,
+ encoding, and then converting back to local
+ representation. In particular, this may apply to plain
+ text material on systems that use newline conventions
+ other than a CRLF terminator sequence. Such an
+ implementation optimization is permissible, but only
+ when the combined canonicalization-encoding step is
+ equivalent to performing the three steps separately.
+ */
+
+ $string = 'a'."\r\n".'b'."\r\n".'c'."\r\n";
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('c')));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')->once()->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals($string, $encoder->encodeString($string));
+ }
+
+ public function testLinesLongerThan76CharactersAreSoftBroken()
+ {
+ /*
+ (5) (Soft Line Breaks) The Quoted-Printable encoding
+ REQUIRES that encoded lines be no more than 76
+ characters long. If longer lines are to be encoded
+ with the Quoted-Printable encoding, "soft" line breaks
+ must be used. An equal sign as the last character on a
+ encoded line indicates such a non-significant ("soft")
+ line break in the encoded text.
+ */
+
+ $input = str_repeat('a', 140);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($input);
+
+ $output = '';
+ for ($i = 0; $i < 140; ++$i) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+
+ if (75 == $i) {
+ $output .= "=\r\n";
+ }
+ $output .= 'a';
+ }
+
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals($output, $encoder->encodeString($input));
+ }
+
+ public function testMaxLineLengthCanBeSpecified()
+ {
+ $input = str_repeat('a', 100);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($input);
+
+ $output = '';
+ for ($i = 0; $i < 100; ++$i) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+
+ if (53 == $i) {
+ $output .= "=\r\n";
+ }
+ $output .= 'a';
+ }
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals($output, $encoder->encodeString($input, 0, 54));
+ }
+
+ public function testBytesBelowPermittedRangeAreEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ foreach (range(0, 32) as $ordinal) {
+ $char = chr($ordinal);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($char);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+
+ $this->assertEquals(
+ sprintf('=%02X', $ordinal), $encoder->encodeString($char)
+ );
+ }
+ }
+
+ public function testDecimalByte61IsEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ $char = '=';
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($char);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(61));
+ $charStream->shouldReceive('readBytes')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+
+ $this->assertEquals('=3D', $encoder->encodeString('='));
+ }
+
+ public function testBytesAbovePermittedRangeAreEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ foreach (range(127, 255) as $ordinal) {
+ $char = chr($ordinal);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($char);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+
+ $this->assertEquals(
+ sprintf('=%02X', $ordinal), $encoder->encodeString($char)
+ );
+ }
+ }
+
+ public function testFirstLineLengthCanBeDifferent()
+ {
+ $input = str_repeat('a', 140);
+
+ $charStream = $this->_createCharStream();
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($input);
+
+ $output = '';
+ for ($i = 0; $i < 140; ++$i) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+
+ if (53 == $i || 53 + 75 == $i) {
+ $output .= "=\r\n";
+ }
+ $output .= 'a';
+ }
+
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_QpEncoder($charStream);
+ $this->assertEquals(
+ $output, $encoder->encodeString($input, 22),
+ '%s: First line should start at offset 22 so can only have max length 54'
+ );
+ }
+
+ public function testTextIsPreWrapped()
+ {
+ $encoder = $this->createEncoder();
+
+ $input = str_repeat('a', 70)."\r\n".
+ str_repeat('a', 70)."\r\n".
+ str_repeat('a', 70);
+
+ $this->assertEquals(
+ $input, $encoder->encodeString($input)
+ );
+ }
+
+ private function _createCharStream()
+ {
+ return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
+ }
+
+ private function createEncoder()
+ {
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');
+
+ return new Swift_Encoder_QpEncoder($charStream);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php
new file mode 100644
index 0000000..28eae6f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php
@@ -0,0 +1,141 @@
+<?php
+
+class Swift_Encoder_Rfc2231EncoderTest extends \SwiftMailerTestCase
+{
+ private $_rfc2045Token = '/^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+$/D';
+
+ /* --
+ This algorithm is described in RFC 2231, but is barely touched upon except
+ for mentioning bytes can be represented as their octet values (e.g. %20 for
+ the SPACE character).
+
+ The tests here focus on how to use that representation to always generate text
+ which matches RFC 2045's definition of "token".
+ */
+
+ public function testEncodingAsciiCharactersProducesValidToken()
+ {
+ $charStream = $this->getMockery('Swift_CharacterStream');
+
+ $string = '';
+ foreach (range(0x00, 0x7F) as $octet) {
+ $char = pack('C', $octet);
+ $string .= $char;
+ $charStream->shouldReceive('read')
+ ->once()
+ ->andReturn($char);
+ }
+
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+ $charStream->shouldReceive('read')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+
+ $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
+ $encoded = $encoder->encodeString($string);
+
+ foreach (explode("\r\n", $encoded) as $line) {
+ $this->assertRegExp($this->_rfc2045Token, $line,
+ '%s: Encoder should always return a valid RFC 2045 token.');
+ }
+ }
+
+ public function testEncodingNonAsciiCharactersProducesValidToken()
+ {
+ $charStream = $this->getMockery('Swift_CharacterStream');
+
+ $string = '';
+ foreach (range(0x80, 0xFF) as $octet) {
+ $char = pack('C', $octet);
+ $string .= $char;
+ $charStream->shouldReceive('read')
+ ->once()
+ ->andReturn($char);
+ }
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+ $charStream->shouldReceive('read')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+ $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
+
+ $encoded = $encoder->encodeString($string);
+
+ foreach (explode("\r\n", $encoded) as $line) {
+ $this->assertRegExp($this->_rfc2045Token, $line,
+ '%s: Encoder should always return a valid RFC 2045 token.');
+ }
+ }
+
+ public function testMaximumLineLengthCanBeSet()
+ {
+ $charStream = $this->getMockery('Swift_CharacterStream');
+
+ $string = '';
+ for ($x = 0; $x < 200; ++$x) {
+ $char = 'a';
+ $string .= $char;
+ $charStream->shouldReceive('read')
+ ->once()
+ ->andReturn($char);
+ }
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+ $charStream->shouldReceive('read')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+ $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
+
+ $encoded = $encoder->encodeString($string, 0, 75);
+
+ $this->assertEquals(
+ str_repeat('a', 75)."\r\n".
+ str_repeat('a', 75)."\r\n".
+ str_repeat('a', 50),
+ $encoded,
+ '%s: Lines should be wrapped at each 75 characters'
+ );
+ }
+
+ public function testFirstLineCanHaveShorterLength()
+ {
+ $charStream = $this->getMockery('Swift_CharacterStream');
+
+ $string = '';
+ for ($x = 0; $x < 200; ++$x) {
+ $char = 'a';
+ $string .= $char;
+ $charStream->shouldReceive('read')
+ ->once()
+ ->andReturn($char);
+ }
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importString')
+ ->once()
+ ->with($string);
+ $charStream->shouldReceive('read')
+ ->atLeast()->times(1)
+ ->andReturn(false);
+ $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
+ $encoded = $encoder->encodeString($string, 25, 75);
+
+ $this->assertEquals(
+ str_repeat('a', 50)."\r\n".
+ str_repeat('a', 75)."\r\n".
+ str_repeat('a', 75),
+ $encoded,
+ '%s: First line should be 25 bytes shorter than the others.'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php
new file mode 100644
index 0000000..a78bc3a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php
@@ -0,0 +1,34 @@
+<?php
+
+class Swift_Events_CommandEventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCommandCanBeFetchedByGetter()
+ {
+ $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n");
+ $this->assertEquals("FOO\r\n", $evt->getCommand());
+ }
+
+ public function testSuccessCodesCanBeFetchedViaGetter()
+ {
+ $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n", array(250));
+ $this->assertEquals(array(250), $evt->getSuccessCodes());
+ }
+
+ public function testSourceIsBuffer()
+ {
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport, "FOO\r\n");
+ $ref = $evt->getSource();
+ $this->assertEquals($transport, $ref);
+ }
+
+ private function _createEvent(Swift_Transport $source, $command, $successCodes = array())
+ {
+ return new Swift_Events_CommandEvent($source, $command, $successCodes);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php
new file mode 100644
index 0000000..0cfe3ca
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php
@@ -0,0 +1,32 @@
+<?php
+
+class Swift_Events_EventObjectTest extends \PHPUnit_Framework_TestCase
+{
+ public function testEventSourceCanBeReturnedViaGetter()
+ {
+ $source = new stdClass();
+ $evt = $this->_createEvent($source);
+ $ref = $evt->getSource();
+ $this->assertEquals($source, $ref);
+ }
+
+ public function testEventDoesNotHaveCancelledBubbleWhenNew()
+ {
+ $source = new stdClass();
+ $evt = $this->_createEvent($source);
+ $this->assertFalse($evt->bubbleCancelled());
+ }
+
+ public function testBubbleCanBeCancelledInEvent()
+ {
+ $source = new stdClass();
+ $evt = $this->_createEvent($source);
+ $evt->cancelBubble();
+ $this->assertTrue($evt->bubbleCancelled());
+ }
+
+ private function _createEvent($source)
+ {
+ return new Swift_Events_EventObject($source);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php
new file mode 100644
index 0000000..6f611ac
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php
@@ -0,0 +1,38 @@
+<?php
+
+class Swift_Events_ResponseEventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testResponseCanBeFetchViaGetter()
+ {
+ $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", true);
+ $this->assertEquals("250 Ok\r\n", $evt->getResponse(),
+ '%s: Response should be available via getResponse()'
+ );
+ }
+
+ public function testResultCanBeFetchedViaGetter()
+ {
+ $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", false);
+ $this->assertFalse($evt->isValid(),
+ '%s: Result should be checkable via isValid()'
+ );
+ }
+
+ public function testSourceIsBuffer()
+ {
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport, "250 Ok\r\n", true);
+ $ref = $evt->getSource();
+ $this->assertEquals($transport, $ref);
+ }
+
+ private function _createEvent(Swift_Transport $source, $response, $result)
+ {
+ return new Swift_Events_ResponseEvent($source, $response, $result);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php
new file mode 100644
index 0000000..c4a6a7e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php
@@ -0,0 +1,97 @@
+<?php
+
+class Swift_Events_SendEventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testMessageCanBeFetchedViaGetter()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+
+ $ref = $evt->getMessage();
+ $this->assertEquals($message, $ref,
+ '%s: Message should be returned from getMessage()'
+ );
+ }
+
+ public function testTransportCanBeFetchViaGetter()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+
+ $ref = $evt->getTransport();
+ $this->assertEquals($transport, $ref,
+ '%s: Transport should be returned from getTransport()'
+ );
+ }
+
+ public function testTransportCanBeFetchViaGetSource()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+
+ $ref = $evt->getSource();
+ $this->assertEquals($transport, $ref,
+ '%s: Transport should be returned from getSource()'
+ );
+ }
+
+ public function testResultCanBeSetAndGet()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+
+ $evt->setResult(
+ Swift_Events_SendEvent::RESULT_SUCCESS | Swift_Events_SendEvent::RESULT_TENTATIVE
+ );
+
+ $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_SUCCESS));
+ $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_TENTATIVE));
+ }
+
+ public function testFailedRecipientsCanBeSetAndGet()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+
+ $evt->setFailedRecipients(array('foo@bar', 'zip@button'));
+
+ $this->assertEquals(array('foo@bar', 'zip@button'), $evt->getFailedRecipients(),
+ '%s: FailedRecipients should be returned from getter'
+ );
+ }
+
+ public function testFailedRecipientsGetsPickedUpCorrectly()
+ {
+ $message = $this->_createMessage();
+ $transport = $this->_createTransport();
+
+ $evt = $this->_createEvent($transport, $message);
+ $this->assertEquals(array(), $evt->getFailedRecipients());
+ }
+
+ private function _createEvent(Swift_Transport $source,
+ Swift_Mime_Message $message)
+ {
+ return new Swift_Events_SendEvent($source, $message);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+
+ private function _createMessage()
+ {
+ return $this->getMockBuilder('Swift_Mime_Message')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php
new file mode 100644
index 0000000..3f063ff
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php
@@ -0,0 +1,142 @@
+<?php
+
+class Swift_Events_SimpleEventDispatcherTest extends \PHPUnit_Framework_TestCase
+{
+ private $_dispatcher;
+
+ protected function setUp()
+ {
+ $this->_dispatcher = new Swift_Events_SimpleEventDispatcher();
+ }
+
+ public function testSendEventCanBeCreated()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+ $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+ $evt = $this->_dispatcher->createSendEvent($transport, $message);
+ $this->assertInstanceOf('Swift_Events_SendEvent', $evt);
+ $this->assertSame($message, $evt->getMessage());
+ $this->assertSame($transport, $evt->getTransport());
+ }
+
+ public function testCommandEventCanBeCreated()
+ {
+ $buf = $this->getMockBuilder('Swift_Transport')->getMock();
+ $evt = $this->_dispatcher->createCommandEvent($buf, "FOO\r\n", array(250));
+ $this->assertInstanceOf('Swift_Events_CommandEvent', $evt);
+ $this->assertSame($buf, $evt->getSource());
+ $this->assertEquals("FOO\r\n", $evt->getCommand());
+ $this->assertEquals(array(250), $evt->getSuccessCodes());
+ }
+
+ public function testResponseEventCanBeCreated()
+ {
+ $buf = $this->getMockBuilder('Swift_Transport')->getMock();
+ $evt = $this->_dispatcher->createResponseEvent($buf, "250 Ok\r\n", true);
+ $this->assertInstanceOf('Swift_Events_ResponseEvent', $evt);
+ $this->assertSame($buf, $evt->getSource());
+ $this->assertEquals("250 Ok\r\n", $evt->getResponse());
+ $this->assertTrue($evt->isValid());
+ }
+
+ public function testTransportChangeEventCanBeCreated()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+ $evt = $this->_dispatcher->createTransportChangeEvent($transport);
+ $this->assertInstanceOf('Swift_Events_TransportChangeEvent', $evt);
+ $this->assertSame($transport, $evt->getSource());
+ }
+
+ public function testTransportExceptionEventCanBeCreated()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+ $ex = new Swift_TransportException('');
+ $evt = $this->_dispatcher->createTransportExceptionEvent($transport, $ex);
+ $this->assertInstanceOf('Swift_Events_TransportExceptionEvent', $evt);
+ $this->assertSame($transport, $evt->getSource());
+ $this->assertSame($ex, $evt->getException());
+ }
+
+ public function testListenersAreNotifiedOfDispatchedEvent()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+
+ $evt = $this->_dispatcher->createTransportChangeEvent($transport);
+
+ $listenerA = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock();
+ $listenerB = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock();
+
+ $this->_dispatcher->bindEventListener($listenerA);
+ $this->_dispatcher->bindEventListener($listenerB);
+
+ $listenerA->expects($this->once())
+ ->method('transportStarted')
+ ->with($evt);
+ $listenerB->expects($this->once())
+ ->method('transportStarted')
+ ->with($evt);
+
+ $this->_dispatcher->dispatchEvent($evt, 'transportStarted');
+ }
+
+ public function testListenersAreOnlyCalledIfImplementingCorrectInterface()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+ $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+
+ $evt = $this->_dispatcher->createSendEvent($transport, $message);
+
+ $targetListener = $this->getMockBuilder('Swift_Events_SendListener')->getMock();
+ $otherListener = $this->getMockBuilder('DummyListener')->getMock();
+
+ $this->_dispatcher->bindEventListener($targetListener);
+ $this->_dispatcher->bindEventListener($otherListener);
+
+ $targetListener->expects($this->once())
+ ->method('sendPerformed')
+ ->with($evt);
+ $otherListener->expects($this->never())
+ ->method('sendPerformed');
+
+ $this->_dispatcher->dispatchEvent($evt, 'sendPerformed');
+ }
+
+ public function testListenersCanCancelBubblingOfEvent()
+ {
+ $transport = $this->getMockBuilder('Swift_Transport')->getMock();
+ $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+
+ $evt = $this->_dispatcher->createSendEvent($transport, $message);
+
+ $listenerA = $this->getMockBuilder('Swift_Events_SendListener')->getMock();
+ $listenerB = $this->getMockBuilder('Swift_Events_SendListener')->getMock();
+
+ $this->_dispatcher->bindEventListener($listenerA);
+ $this->_dispatcher->bindEventListener($listenerB);
+
+ $listenerA->expects($this->once())
+ ->method('sendPerformed')
+ ->with($evt)
+ ->will($this->returnCallback(function ($object) {
+ $object->cancelBubble(true);
+ }));
+ $listenerB->expects($this->never())
+ ->method('sendPerformed');
+
+ $this->_dispatcher->dispatchEvent($evt, 'sendPerformed');
+
+ $this->assertTrue($evt->bubbleCancelled());
+ }
+
+ private function _createDispatcher(array $map)
+ {
+ return new Swift_Events_SimpleEventDispatcher($map);
+ }
+}
+
+class DummyListener implements Swift_Events_EventListener
+{
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php
new file mode 100644
index 0000000..a260ccb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php
@@ -0,0 +1,30 @@
+<?php
+
+class Swift_Events_TransportChangeEventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGetTransportReturnsTransport()
+ {
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport);
+ $ref = $evt->getTransport();
+ $this->assertEquals($transport, $ref);
+ }
+
+ public function testSourceIsTransport()
+ {
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport);
+ $ref = $evt->getSource();
+ $this->assertEquals($transport, $ref);
+ }
+
+ private function _createEvent(Swift_Transport $source)
+ {
+ return new Swift_Events_TransportChangeEvent($source);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php
new file mode 100644
index 0000000..731dfad
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class Swift_Events_TransportExceptionEventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testExceptionCanBeFetchViaGetter()
+ {
+ $ex = $this->_createException();
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport, $ex);
+ $ref = $evt->getException();
+ $this->assertEquals($ex, $ref,
+ '%s: Exception should be available via getException()'
+ );
+ }
+
+ public function testSourceIsTransport()
+ {
+ $ex = $this->_createException();
+ $transport = $this->_createTransport();
+ $evt = $this->_createEvent($transport, $ex);
+ $ref = $evt->getSource();
+ $this->assertEquals($transport, $ref,
+ '%s: Transport should be available via getSource()'
+ );
+ }
+
+ private function _createEvent(Swift_Transport $transport, Swift_TransportException $ex)
+ {
+ return new Swift_Events_TransportExceptionEvent($transport, $ex);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+
+ private function _createException()
+ {
+ return new Swift_TransportException('');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php
new file mode 100644
index 0000000..f2ed5dd
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php
@@ -0,0 +1,240 @@
+<?php
+
+class Swift_KeyCache_ArrayKeyCacheTest extends \PHPUnit_Framework_TestCase
+{
+ private $_key1 = 'key1';
+ private $_key2 = 'key2';
+
+ public function testStringDataCanBeSetAndFetched()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeOverwritten()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $cache->setString(
+ $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
+ );
+
+ $this->assertEquals('whatever', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testStringDataCanBeAppended()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $cache->setString(
+ $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
+ );
+
+ $this->assertEquals('testing', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testHasKeyReturnValue()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+
+ $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyIsWellPartitioned()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $cache->setString(
+ $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+
+ $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $cache->getString($this->_key2, 'foo'));
+ }
+
+ public function testItemKeyIsWellPartitioned()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $cache->setString(
+ $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
+ );
+
+ $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
+ $this->assertEquals('ing', $cache->getString($this->_key1, 'bar'));
+ }
+
+ public function testByteStreamCanBeImported()
+ {
+ $os = $this->_createOutputStream();
+ $os->expects($this->at(0))
+ ->method('read')
+ ->will($this->returnValue('abc'));
+ $os->expects($this->at(1))
+ ->method('read')
+ ->will($this->returnValue('def'));
+ $os->expects($this->at(2))
+ ->method('read')
+ ->will($this->returnValue(false));
+
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+ $cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertEquals('abcdef', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamCanBeAppended()
+ {
+ $os1 = $this->_createOutputStream();
+ $os1->expects($this->at(0))
+ ->method('read')
+ ->will($this->returnValue('abc'));
+ $os1->expects($this->at(1))
+ ->method('read')
+ ->will($this->returnValue('def'));
+ $os1->expects($this->at(2))
+ ->method('read')
+ ->will($this->returnValue(false));
+
+ $os2 = $this->_createOutputStream();
+ $os2->expects($this->at(0))
+ ->method('read')
+ ->will($this->returnValue('xyz'));
+ $os2->expects($this->at(1))
+ ->method('read')
+ ->will($this->returnValue('uvw'));
+ $os2->expects($this->at(2))
+ ->method('read')
+ ->will($this->returnValue(false));
+
+ $is = $this->_createKeyCacheInputStream(true);
+
+ $cache = $this->_createCache($is);
+
+ $cache->importFromByteStream(
+ $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
+ );
+ $cache->importFromByteStream(
+ $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
+ );
+
+ $this->assertEquals('abcdefxyzuvw', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testByteStreamAndStringCanBeAppended()
+ {
+ $os = $this->_createOutputStream();
+ $os->expects($this->at(0))
+ ->method('read')
+ ->will($this->returnValue('abc'));
+ $os->expects($this->at(1))
+ ->method('read')
+ ->will($this->returnValue('def'));
+ $os->expects($this->at(2))
+ ->method('read')
+ ->will($this->returnValue(false));
+
+ $is = $this->_createKeyCacheInputStream(true);
+
+ $cache = $this->_createCache($is);
+
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
+ );
+ $cache->importFromByteStream(
+ $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
+ );
+ $this->assertEquals('testabcdef', $cache->getString($this->_key1, 'foo'));
+ }
+
+ public function testDataCanBeExportedToByteStream()
+ {
+ //See acceptance test for more detail
+ $is = $this->_createInputStream();
+ $is->expects($this->atLeastOnce())
+ ->method('write');
+
+ $kcis = $this->_createKeyCacheInputStream(true);
+
+ $cache = $this->_createCache($kcis);
+
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+
+ $cache->exportToByteStream($this->_key1, 'foo', $is);
+ }
+
+ public function testKeyCanBeCleared()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
+ $cache->clearKey($this->_key1, 'foo');
+ $this->assertFalse($cache->hasKey($this->_key1, 'foo'));
+ }
+
+ public function testNsKeyCanBeCleared()
+ {
+ $is = $this->_createKeyCacheInputStream();
+ $cache = $this->_createCache($is);
+
+ $cache->setString(
+ $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
+ );
+ $cache->setString(
+ $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
+ );
+ $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
+ $this->assertTrue($cache->hasKey($this->_key1, 'bar'));
+ $cache->clearAll($this->_key1);
+ $this->assertFalse($cache->hasKey($this->_key1, 'foo'));
+ $this->assertFalse($cache->hasKey($this->_key1, 'bar'));
+ }
+
+ private function _createCache($is)
+ {
+ return new Swift_KeyCache_ArrayKeyCache($is);
+ }
+
+ private function _createKeyCacheInputStream()
+ {
+ return $this->getMockBuilder('Swift_KeyCache_KeyCacheInputStream')->getMock();
+ }
+
+ private function _createOutputStream()
+ {
+ return $this->getMockBuilder('Swift_OutputByteStream')->getMock();
+ }
+
+ private function _createInputStream()
+ {
+ return $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php
new file mode 100644
index 0000000..38fbc0d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php
@@ -0,0 +1,73 @@
+<?php
+
+class Swift_KeyCache_SimpleKeyCacheInputStreamTest extends \PHPUnit_Framework_TestCase
+{
+ private $_nsKey = 'ns1';
+
+ public function testStreamWritesToCacheInAppendMode()
+ {
+ $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
+ $cache->expects($this->at(0))
+ ->method('setString')
+ ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND);
+ $cache->expects($this->at(1))
+ ->method('setString')
+ ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND);
+ $cache->expects($this->at(2))
+ ->method('setString')
+ ->with($this->_nsKey, 'foo', 'c', Swift_KeyCache::MODE_APPEND);
+
+ $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
+ $stream->setKeyCache($cache);
+ $stream->setNsKey($this->_nsKey);
+ $stream->setItemKey('foo');
+
+ $stream->write('a');
+ $stream->write('b');
+ $stream->write('c');
+ }
+
+ public function testFlushContentClearsKey()
+ {
+ $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
+ $cache->expects($this->once())
+ ->method('clearKey')
+ ->with($this->_nsKey, 'foo');
+
+ $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
+ $stream->setKeyCache($cache);
+ $stream->setNsKey($this->_nsKey);
+ $stream->setItemKey('foo');
+
+ $stream->flushBuffers();
+ }
+
+ public function testClonedStreamStillReferencesSameCache()
+ {
+ $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
+ $cache->expects($this->at(0))
+ ->method('setString')
+ ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND);
+ $cache->expects($this->at(1))
+ ->method('setString')
+ ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND);
+ $cache->expects($this->at(2))
+ ->method('setString')
+ ->with('test', 'bar', 'x', Swift_KeyCache::MODE_APPEND);
+
+ $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
+ $stream->setKeyCache($cache);
+ $stream->setNsKey($this->_nsKey);
+ $stream->setItemKey('foo');
+
+ $stream->write('a');
+ $stream->write('b');
+
+ $newStream = clone $stream;
+ $newStream->setKeyCache($cache);
+ $newStream->setNsKey('test');
+ $newStream->setItemKey('bar');
+
+ $newStream->write('x');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php
new file mode 100644
index 0000000..ff0bce4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php
@@ -0,0 +1,42 @@
+<?php
+
+class Swift_Mailer_ArrayRecipientIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testHasNextReturnsFalseForEmptyArray()
+ {
+ $it = new Swift_Mailer_ArrayRecipientIterator(array());
+ $this->assertFalse($it->hasNext());
+ }
+
+ public function testHasNextReturnsTrueIfItemsLeft()
+ {
+ $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
+ $this->assertTrue($it->hasNext());
+ }
+
+ public function testReadingToEndOfListCausesHasNextToReturnFalse()
+ {
+ $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
+ $this->assertTrue($it->hasNext());
+ $it->nextRecipient();
+ $this->assertFalse($it->hasNext());
+ }
+
+ public function testReturnedValueHasPreservedKeyValuePair()
+ {
+ $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
+ $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient());
+ }
+
+ public function testIteratorMovesNextAfterEachIteration()
+ {
+ $it = new Swift_Mailer_ArrayRecipientIterator(array(
+ 'foo@bar' => 'Foo',
+ 'zip@button' => 'Zip thing',
+ 'test@test' => null,
+ ));
+ $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient());
+ $this->assertEquals(array('zip@button' => 'Zip thing'), $it->nextRecipient());
+ $this->assertEquals(array('test@test' => null), $it->nextRecipient());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php
new file mode 100644
index 0000000..74951a7
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php
@@ -0,0 +1,145 @@
+<?php
+
+class Swift_MailerTest extends \SwiftMailerTestCase
+{
+ public function testTransportIsStartedWhenSending()
+ {
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+
+ $started = false;
+ $transport->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$started) {
+ return $started;
+ });
+ $transport->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$started) {
+ $started = true;
+
+ return;
+ });
+
+ $mailer = $this->_createMailer($transport);
+ $mailer->send($message);
+ }
+
+ public function testTransportIsOnlyStartedOnce()
+ {
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+
+ $started = false;
+ $transport->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$started) {
+ return $started;
+ });
+ $transport->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$started) {
+ $started = true;
+
+ return;
+ });
+
+ $mailer = $this->_createMailer($transport);
+ for ($i = 0; $i < 10; ++$i) {
+ $mailer->send($message);
+ }
+ }
+
+ public function testMessageIsPassedToTransport()
+ {
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+ $transport->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any());
+
+ $mailer = $this->_createMailer($transport);
+ $mailer->send($message);
+ }
+
+ public function testSendReturnsCountFromTransport()
+ {
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+ $transport->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturn(57);
+
+ $mailer = $this->_createMailer($transport);
+ $this->assertEquals(57, $mailer->send($message));
+ }
+
+ public function testFailedRecipientReferenceIsPassedToTransport()
+ {
+ $failures = array();
+
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+ $transport->shouldReceive('send')
+ ->once()
+ ->with($message, $failures)
+ ->andReturn(57);
+
+ $mailer = $this->_createMailer($transport);
+ $mailer->send($message, $failures);
+ }
+
+ public function testSendRecordsRfcComplianceExceptionAsEntireSendFailure()
+ {
+ $failures = array();
+
+ $rfcException = new Swift_RfcComplianceException('test');
+ $transport = $this->_createTransport();
+ $message = $this->_createMessage();
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo&invalid' => 'Foo', 'bar@valid.tld' => 'Bar'));
+ $transport->shouldReceive('send')
+ ->once()
+ ->with($message, $failures)
+ ->andThrow($rfcException);
+
+ $mailer = $this->_createMailer($transport);
+ $this->assertEquals(0, $mailer->send($message, $failures), '%s: Should return 0');
+ $this->assertEquals(array('foo&invalid', 'bar@valid.tld'), $failures, '%s: Failures should contain all addresses since the entire message failed to compile');
+ }
+
+ public function testRegisterPluginDelegatesToTransport()
+ {
+ $plugin = $this->_createPlugin();
+ $transport = $this->_createTransport();
+ $mailer = $this->_createMailer($transport);
+
+ $transport->shouldReceive('registerPlugin')
+ ->once()
+ ->with($plugin);
+
+ $mailer->registerPlugin($plugin);
+ }
+
+ private function _createPlugin()
+ {
+ return $this->getMockery('Swift_Events_EventListener')->shouldIgnoreMissing();
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockery('Swift_Transport')->shouldIgnoreMissing();
+ }
+
+ private function _createMessage()
+ {
+ return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ }
+
+ private function _createMailer(Swift_Transport $transport)
+ {
+ return new Swift_Mailer($transport);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php
new file mode 100644
index 0000000..35a568c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php
@@ -0,0 +1,129 @@
+<?php
+
+class Swift_MessageTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCloning()
+ {
+ $message1 = new Swift_Message('subj', 'body', 'ctype');
+ $message2 = new Swift_Message('subj', 'body', 'ctype');
+ $message1_clone = clone $message1;
+
+ $this->_recursiveObjectCloningCheck($message1, $message2, $message1_clone);
+ }
+
+ public function testCloningWithSigners()
+ {
+ $message1 = new Swift_Message('subj', 'body', 'ctype');
+ $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example');
+ $message1->attachSigner($signer);
+ $message2 = new Swift_Message('subj', 'body', 'ctype');
+ $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example');
+ $message2->attachSigner($signer);
+ $message1_clone = clone $message1;
+
+ $this->_recursiveObjectCloningCheck($message1, $message2, $message1_clone);
+ }
+
+ public function testBodySwap()
+ {
+ $message1 = new Swift_Message('Test');
+ $html = Swift_MimePart::newInstance('<html></html>', 'text/html');
+ $html->getHeaders()->addTextHeader('X-Test-Remove', 'Test-Value');
+ $html->getHeaders()->addTextHeader('X-Test-Alter', 'Test-Value');
+ $message1->attach($html);
+ $source = $message1->toString();
+ $message2 = clone $message1;
+ $message2->setSubject('Message2');
+ foreach ($message2->getChildren() as $child) {
+ $child->setBody('Test');
+ $child->getHeaders()->removeAll('X-Test-Remove');
+ $child->getHeaders()->get('X-Test-Alter')->setValue('Altered');
+ }
+ $final = $message1->toString();
+ if ($source != $final) {
+ $this->fail("Difference although object cloned \n [".$source."]\n[".$final."]\n");
+ }
+ $final = $message2->toString();
+ if ($final == $source) {
+ $this->fail('Two body matches although they should differ'."\n [".$source."]\n[".$final."]\n");
+ }
+ $id_1 = $message1->getId();
+ $id_2 = $message2->getId();
+ $this->assertEquals($id_1, $id_2, 'Message Ids differ');
+ $id_2 = $message2->generateId();
+ $this->assertNotEquals($id_1, $id_2, 'Message Ids are the same');
+ }
+
+ protected function _recursiveObjectCloningCheck($obj1, $obj2, $obj1_clone)
+ {
+ $obj1_properties = (array) $obj1;
+ $obj2_properties = (array) $obj2;
+ $obj1_clone_properties = (array) $obj1_clone;
+
+ foreach ($obj1_properties as $property => $value) {
+ if (is_object($value)) {
+ $obj1_value = $obj1_properties[$property];
+ $obj2_value = $obj2_properties[$property];
+ $obj1_clone_value = $obj1_clone_properties[$property];
+
+ if ($obj1_value !== $obj2_value) {
+ // two separetely instanciated objects property not referencing same object
+ $this->assertFalse(
+ // but object's clone does - not everything copied
+ $obj1_value === $obj1_clone_value,
+ "Property `$property` cloning error: source and cloned objects property is referencing same object"
+ );
+ } else {
+ // two separetely instanciated objects have same reference
+ $this->assertFalse(
+ // but object's clone doesn't - overdone making copies
+ $obj1_value !== $obj1_clone_value,
+ "Property `$property` not properly cloned: it should reference same object as cloning source (overdone copping)"
+ );
+ }
+ // recurse
+ $this->_recursiveObjectCloningCheck($obj1_value, $obj2_value, $obj1_clone_value);
+ } elseif (is_array($value)) {
+ $obj1_value = $obj1_properties[$property];
+ $obj2_value = $obj2_properties[$property];
+ $obj1_clone_value = $obj1_clone_properties[$property];
+
+ return $this->_recursiveArrayCloningCheck($obj1_value, $obj2_value, $obj1_clone_value);
+ }
+ }
+ }
+
+ protected function _recursiveArrayCloningCheck($array1, $array2, $array1_clone)
+ {
+ foreach ($array1 as $key => $value) {
+ if (is_object($value)) {
+ $arr1_value = $array1[$key];
+ $arr2_value = $array2[$key];
+ $arr1_clone_value = $array1_clone[$key];
+ if ($arr1_value !== $arr2_value) {
+ // two separetely instanciated objects property not referencing same object
+ $this->assertFalse(
+ // but object's clone does - not everything copied
+ $arr1_value === $arr1_clone_value,
+ "Key `$key` cloning error: source and cloned objects property is referencing same object"
+ );
+ } else {
+ // two separetely instanciated objects have same reference
+ $this->assertFalse(
+ // but object's clone doesn't - overdone making copies
+ $arr1_value !== $arr1_clone_value,
+ "Key `$key` not properly cloned: it should reference same object as cloning source (overdone copping)"
+ );
+ }
+ // recurse
+ $this->_recursiveObjectCloningCheck($arr1_value, $arr2_value, $arr1_clone_value);
+ } elseif (is_array($value)) {
+ $arr1_value = $array1[$key];
+ $arr2_value = $array2[$key];
+ $arr1_clone_value = $array1_clone[$key];
+
+ return $this->_recursiveArrayCloningCheck($arr1_value, $arr2_value, $arr1_clone_value);
+ }
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php
new file mode 100644
index 0000000..3efe6ec
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php
@@ -0,0 +1,1092 @@
+<?php
+
+require_once dirname(dirname(dirname(__DIR__))).'/fixtures/MimeEntityFixture.php';
+
+abstract class Swift_Mime_AbstractMimeEntityTest extends \SwiftMailerTestCase
+{
+ public function testGetHeadersReturnsHeaderSet()
+ {
+ $headers = $this->_createHeaderSet();
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $this->assertSame($headers, $entity->getHeaders());
+ }
+
+ public function testContentTypeIsReturnedFromHeader()
+ {
+ $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test');
+ $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $this->assertEquals('image/jpeg-test', $entity->getContentType());
+ }
+
+ public function testContentTypeIsSetInHeader()
+ {
+ $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));
+
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('image/jpeg');
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes()
+ ->with(\Mockery::not('image/jpeg'));
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setContentType('image/jpeg');
+ }
+
+ public function testContentTypeHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addParameterizedHeader')
+ ->once()
+ ->with('Content-Type', 'image/jpeg');
+ $headers->shouldReceive('addParameterizedHeader')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setContentType('image/jpeg');
+ }
+
+ public function testContentTypeCanBeSetViaSetBody()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addParameterizedHeader')
+ ->once()
+ ->with('Content-Type', 'text/html');
+ $headers->shouldReceive('addParameterizedHeader')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBody('<b>foo</b>', 'text/html');
+ }
+
+ public function testGetEncoderFromConstructor()
+ {
+ $encoder = $this->_createEncoder('base64');
+ $entity = $this->_createEntity($this->_createHeaderSet(), $encoder,
+ $this->_createCache()
+ );
+ $this->assertSame($encoder, $entity->getEncoder());
+ }
+
+ public function testSetAndGetEncoder()
+ {
+ $encoder = $this->_createEncoder('base64');
+ $headers = $this->_createHeaderSet();
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setEncoder($encoder);
+ $this->assertSame($encoder, $entity->getEncoder());
+ }
+
+ public function testSettingEncoderUpdatesTransferEncoding()
+ {
+ $encoder = $this->_createEncoder('base64');
+ $encoding = $this->_createHeader(
+ 'Content-Transfer-Encoding', '8bit', array(), false
+ );
+ $headers = $this->_createHeaderSet(array(
+ 'Content-Transfer-Encoding' => $encoding,
+ ));
+ $encoding->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('base64');
+ $encoding->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setEncoder($encoder);
+ }
+
+ public function testSettingEncoderAddsEncodingHeaderIfNonePresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addTextHeader')
+ ->once()
+ ->with('Content-Transfer-Encoding', 'something');
+ $headers->shouldReceive('addTextHeader')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setEncoder($this->_createEncoder('something'));
+ }
+
+ public function testIdIsReturnedFromHeader()
+ {
+ /* -- RFC 2045, 7.
+ In constructing a high-level user agent, it may be desirable to allow
+ one body to make reference to another. Accordingly, bodies may be
+ labelled using the "Content-ID" header field, which is syntactically
+ identical to the "Message-ID" header field
+ */
+
+ $cid = $this->_createHeader('Content-ID', 'zip@button');
+ $headers = $this->_createHeaderSet(array('Content-ID' => $cid));
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $this->assertEquals('zip@button', $entity->getId());
+ }
+
+ public function testIdIsSetInHeader()
+ {
+ $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false);
+ $headers = $this->_createHeaderSet(array('Content-ID' => $cid));
+
+ $cid->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('foo@bar');
+ $cid->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setId('foo@bar');
+ }
+
+ public function testIdIsAutoGenerated()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertRegExp('/^.*?@.*?$/D', $entity->getId());
+ }
+
+ public function testGenerateIdCreatesNewId()
+ {
+ $headers = $this->_createHeaderSet();
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $id1 = $entity->generateId();
+ $id2 = $entity->generateId();
+ $this->assertNotEquals($id1, $id2);
+ }
+
+ public function testGenerateIdSetsNewId()
+ {
+ $headers = $this->_createHeaderSet();
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $id = $entity->generateId();
+ $this->assertEquals($id, $entity->getId());
+ }
+
+ public function testDescriptionIsReadFromHeader()
+ {
+ /* -- RFC 2045, 8.
+ The ability to associate some descriptive information with a given
+ body is often desirable. For example, it may be useful to mark an
+ "image" body as "a picture of the Space Shuttle Endeavor." Such text
+ may be placed in the Content-Description header field. This header
+ field is always optional.
+ */
+
+ $desc = $this->_createHeader('Content-Description', 'something');
+ $headers = $this->_createHeaderSet(array('Content-Description' => $desc));
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $this->assertEquals('something', $entity->getDescription());
+ }
+
+ public function testDescriptionIsSetInHeader()
+ {
+ $desc = $this->_createHeader('Content-Description', '', array(), false);
+ $desc->shouldReceive('setFieldBodyModel')->once()->with('whatever');
+
+ $headers = $this->_createHeaderSet(array('Content-Description' => $desc));
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setDescription('whatever');
+ }
+
+ public function testDescriptionHeaderIsAddedIfNotPresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addTextHeader')
+ ->once()
+ ->with('Content-Description', 'whatever');
+ $headers->shouldReceive('addTextHeader')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setDescription('whatever');
+ }
+
+ public function testSetAndGetMaxLineLength()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setMaxLineLength(60);
+ $this->assertEquals(60, $entity->getMaxLineLength());
+ }
+
+ public function testEncoderIsUsedForStringGeneration()
+ {
+ $encoder = $this->_createEncoder('base64', false);
+ $encoder->expects($this->once())
+ ->method('encodeString')
+ ->with('blah');
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $encoder, $this->_createCache()
+ );
+ $entity->setBody('blah');
+ $entity->toString();
+ }
+
+ public function testMaxLineLengthIsProvidedWhenEncoding()
+ {
+ $encoder = $this->_createEncoder('base64', false);
+ $encoder->expects($this->once())
+ ->method('encodeString')
+ ->with('blah', 0, 65);
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $encoder, $this->_createCache()
+ );
+ $entity->setBody('blah');
+ $entity->setMaxLineLength(65);
+ $entity->toString();
+ }
+
+ public function testHeadersAppearInString()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->once()
+ ->andReturn(
+ "Content-Type: text/plain; charset=utf-8\r\n".
+ "X-MyHeader: foobar\r\n"
+ );
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $this->assertEquals(
+ "Content-Type: text/plain; charset=utf-8\r\n".
+ "X-MyHeader: foobar\r\n",
+ $entity->toString()
+ );
+ }
+
+ public function testSetAndGetBody()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setBody("blah\r\nblah!");
+ $this->assertEquals("blah\r\nblah!", $entity->getBody());
+ }
+
+ public function testBodyIsAppended()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->once()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBody("blah\r\nblah!");
+ $this->assertEquals(
+ "Content-Type: text/plain; charset=utf-8\r\n".
+ "\r\n".
+ "blah\r\nblah!",
+ $entity->toString()
+ );
+ }
+
+ public function testGetBodyReturnsStringFromByteStream()
+ {
+ $os = $this->_createOutputStream('byte stream string');
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setBody($os);
+ $this->assertEquals('byte stream string', $entity->getBody());
+ }
+
+ public function testByteStreamBodyIsAppended()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $os = $this->_createOutputStream('streamed');
+ $headers->shouldReceive('toString')
+ ->once()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBody($os);
+ $this->assertEquals(
+ "Content-Type: text/plain; charset=utf-8\r\n".
+ "\r\n".
+ 'streamed',
+ $entity->toString()
+ );
+ }
+
+ public function testBoundaryCanBeRetrieved()
+ {
+ /* -- RFC 2046, 5.1.1.
+ boundary := 0*69<bchars> bcharsnospace
+
+ bchars := bcharsnospace / " "
+
+ bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
+ "+" / "_" / "," / "-" / "." /
+ "/" / ":" / "=" / "?"
+ */
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertRegExp(
+ '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D',
+ $entity->getBoundary()
+ );
+ }
+
+ public function testBoundaryNeverChanges()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $firstBoundary = $entity->getBoundary();
+ for ($i = 0; $i < 10; ++$i) {
+ $this->assertEquals($firstBoundary, $entity->getBoundary());
+ }
+ }
+
+ public function testBoundaryCanBeSet()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setBoundary('foobar');
+ $this->assertEquals('foobar', $entity->getBoundary());
+ }
+
+ public function testAddingChildrenGeneratesBoundaryInHeaders()
+ {
+ $child = $this->_createChild();
+ $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $cType->shouldReceive('setParameter')
+ ->once()
+ ->with('boundary', \Mockery::any());
+ $cType->shouldReceive('setParameter')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($this->_createHeaderSet(array(
+ 'Content-Type' => $cType,
+ )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ }
+
+ public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed()
+ {
+ for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED;
+ $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2) {
+ $child = $this->_createChild($level);
+ $cType = $this->_createHeader(
+ 'Content-Type', 'text/plain', array(), false
+ );
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('multipart/mixed');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ }
+ }
+
+ public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative()
+ {
+ for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE;
+ $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2) {
+ $child = $this->_createChild($level);
+ $cType = $this->_createHeader(
+ 'Content-Type', 'text/plain', array(), false
+ );
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('multipart/alternative');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ }
+ }
+
+ public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated()
+ {
+ for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED;
+ $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2) {
+ $child = $this->_createChild($level);
+ $cType = $this->_createHeader(
+ 'Content-Type', 'text/plain', array(), false
+ );
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('multipart/related');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $entity = $this->_createEntity($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ }
+ }
+
+ public function testHighestLevelChildDeterminesContentType()
+ {
+ $combinations = array(
+ array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
+ Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ Swift_Mime_MimeEntity::LEVEL_RELATED,
+ ),
+ 'type' => 'multipart/mixed',
+ ),
+ array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
+ Swift_Mime_MimeEntity::LEVEL_RELATED,
+ ),
+ 'type' => 'multipart/mixed',
+ ),
+ array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
+ Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ ),
+ 'type' => 'multipart/mixed',
+ ),
+ array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ Swift_Mime_MimeEntity::LEVEL_RELATED,
+ ),
+ 'type' => 'multipart/alternative',
+ ),
+ );
+
+ foreach ($combinations as $combination) {
+ $children = array();
+ foreach ($combination['levels'] as $level) {
+ $children[] = $this->_createChild($level);
+ }
+
+ $cType = $this->_createHeader(
+ 'Content-Type', 'text/plain', array(), false
+ );
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with($combination['type']);
+
+ $headerSet = $this->_createHeaderSet(array('Content-Type' => $cType));
+ $headerSet->shouldReceive('newInstance')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use ($headerSet) {
+ return $headerSet;
+ });
+ $entity = $this->_createEntity($headerSet,
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren($children);
+ }
+ }
+
+ public function testChildrenAppearNestedInString()
+ {
+ /* -- RFC 2046, 5.1.1.
+ (excerpt too verbose to paste here)
+ */
+
+ $headers = $this->_createHeaderSet(array(), false);
+
+ $child1 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'foobar', 'text/plain'
+ );
+
+ $child2 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/html\r\n".
+ "\r\n".
+ '<b>foobar</b>', 'text/html'
+ );
+
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBoundary('xxx');
+ $entity->setChildren(array($child1, $child2));
+
+ $this->assertEquals(
+ "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+ "\r\n".
+ "\r\n--xxx\r\n".
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ "foobar\r\n".
+ "\r\n--xxx\r\n".
+ "Content-Type: text/html\r\n".
+ "\r\n".
+ "<b>foobar</b>\r\n".
+ "\r\n--xxx--\r\n",
+ $entity->toString()
+ );
+ }
+
+ public function testMixingLevelsIsHierarchical()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $newHeaders = $this->_createHeaderSet(array(), false);
+
+ $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'foobar'
+ );
+
+ $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED,
+ "Content-Type: application/octet-stream\r\n".
+ "\r\n".
+ 'data'
+ );
+
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: multipart/mixed; boundary=\"xxx\"\r\n");
+ $headers->shouldReceive('newInstance')
+ ->zeroOrMoreTimes()
+ ->andReturn($newHeaders);
+ $newHeaders->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: multipart/alternative; boundary=\"yyy\"\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBoundary('xxx');
+ $entity->setChildren(array($part, $attachment));
+
+ $this->assertRegExp(
+ '~^'.
+ "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n".
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n".
+ "\r\n\r\n--(.*?)\r\n".
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'foobar'.
+ "\r\n\r\n--\\1--\r\n".
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: application/octet-stream\r\n".
+ "\r\n".
+ 'data'.
+ "\r\n\r\n--xxx--\r\n".
+ '$~',
+ $entity->toString()
+ );
+ }
+
+ public function testSettingEncoderNotifiesChildren()
+ {
+ $child = $this->_createChild(0, '', false);
+ $encoder = $this->_createEncoder('base64');
+
+ $child->shouldReceive('encoderChanged')
+ ->once()
+ ->with($encoder);
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ $entity->setEncoder($encoder);
+ }
+
+ public function testReceiptOfEncoderChangeNotifiesChildren()
+ {
+ $child = $this->_createChild(0, '', false);
+ $encoder = $this->_createEncoder('base64');
+
+ $child->shouldReceive('encoderChanged')
+ ->once()
+ ->with($encoder);
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ $entity->encoderChanged($encoder);
+ }
+
+ public function testReceiptOfCharsetChangeNotifiesChildren()
+ {
+ $child = $this->_createChild(0, '', false);
+ $child->shouldReceive('charsetChanged')
+ ->once()
+ ->with('windows-874');
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $entity->setChildren(array($child));
+ $entity->charsetChanged('windows-874');
+ }
+
+ public function testEntityIsWrittenToByteStream()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $is = $this->_createInputStream(false);
+ $is->expects($this->atLeastOnce())
+ ->method('write');
+
+ $entity->toByteStream($is);
+ }
+
+ public function testEntityHeadersAreComittedToByteStream()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $is = $this->_createInputStream(false);
+ $is->expects($this->atLeastOnce())
+ ->method('write');
+ $is->expects($this->atLeastOnce())
+ ->method('commit');
+
+ $entity->toByteStream($is);
+ }
+
+ public function testOrderingTextBeforeHtml()
+ {
+ $htmlChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/html\r\n".
+ "\r\n".
+ 'HTML PART',
+ 'text/html'
+ );
+ $textChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'TEXT PART',
+ 'text/plain'
+ );
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBoundary('xxx');
+ $entity->setChildren(array($htmlChild, $textChild));
+
+ $this->assertEquals(
+ "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'TEXT PART'.
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: text/html\r\n".
+ "\r\n".
+ 'HTML PART'.
+ "\r\n\r\n--xxx--\r\n",
+ $entity->toString()
+ );
+ }
+
+ public function testOrderingEqualContentTypesMaintainsOriginalOrdering()
+ {
+ $firstChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'PART 1',
+ 'text/plain'
+ );
+ $secondChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'PART 2',
+ 'text/plain'
+ );
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $entity->setBoundary('xxx');
+ $entity->setChildren(array($firstChild, $secondChild));
+
+ $this->assertEquals(
+ "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'PART 1'.
+ "\r\n\r\n--xxx\r\n".
+ "Content-Type: text/plain\r\n".
+ "\r\n".
+ 'PART 2'.
+ "\r\n\r\n--xxx--\r\n",
+ $entity->toString()
+ );
+ }
+
+ public function testUnsettingChildrenRestoresContentType()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE);
+
+ $cType->shouldReceive('setFieldBodyModel')
+ ->twice()
+ ->with('image/jpeg');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('multipart/alternative');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes()
+ ->with(\Mockery::not('multipart/alternative', 'image/jpeg'));
+
+ $entity = $this->_createEntity($this->_createHeaderSet(array(
+ 'Content-Type' => $cType,
+ )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $entity->setContentType('image/jpeg');
+ $entity->setChildren(array($child));
+ $entity->setChildren(array());
+ }
+
+ public function testBodyIsReadFromCacheWhenUsingToStringIfPresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+ $cache->shouldReceive('hasKey')
+ ->once()
+ ->with(\Mockery::any(), 'body')
+ ->andReturn(true);
+ $cache->shouldReceive('getString')
+ ->once()
+ ->with(\Mockery::any(), 'body')
+ ->andReturn("\r\ncache\r\ncache!");
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $this->assertEquals(
+ "Content-Type: text/plain; charset=utf-8\r\n".
+ "\r\n".
+ "cache\r\ncache!",
+ $entity->toString()
+ );
+ }
+
+ public function testBodyIsAddedToCacheWhenUsingToString()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+ $cache->shouldReceive('hasKey')
+ ->once()
+ ->with(\Mockery::any(), 'body')
+ ->andReturn(false);
+ $cache->shouldReceive('setString')
+ ->once()
+ ->with(\Mockery::any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE);
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $entity->toString();
+ }
+
+ public function testBodyIsClearedFromCacheIfNewBodySet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $entity->toString();
+
+ // We set the expectation at this point because we only care what happens when calling setBody()
+ $cache->shouldReceive('clearKey')
+ ->once()
+ ->with(\Mockery::any(), 'body');
+
+ $entity->setBody("new\r\nnew!");
+ }
+
+ public function testBodyIsNotClearedFromCacheIfSameBodySet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $entity->toString();
+
+ // We set the expectation at this point because we only care what happens when calling setBody()
+ $cache->shouldReceive('clearKey')
+ ->never();
+
+ $entity->setBody("blah\r\nblah!");
+ }
+
+ public function testBodyIsClearedFromCacheIfNewEncoderSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+ $otherEncoder = $this->_createEncoder();
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $entity->toString();
+
+ // We set the expectation at this point because we only care what happens when calling setEncoder()
+ $cache->shouldReceive('clearKey')
+ ->once()
+ ->with(\Mockery::any(), 'body');
+
+ $entity->setEncoder($otherEncoder);
+ }
+
+ public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent()
+ {
+ $is = $this->_createInputStream();
+ $cache = $this->_createCache(false);
+ $cache->shouldReceive('hasKey')
+ ->once()
+ ->with(\Mockery::any(), 'body')
+ ->andReturn(true);
+ $cache->shouldReceive('exportToByteStream')
+ ->once()
+ ->with(\Mockery::any(), 'body', $is);
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $cache
+ );
+ $entity->setBody('foo');
+
+ $entity->toByteStream($is);
+ }
+
+ public function testBodyIsAddedToCacheWhenUsingToByteStream()
+ {
+ $is = $this->_createInputStream();
+ $cache = $this->_createCache(false);
+ $cache->shouldReceive('hasKey')
+ ->once()
+ ->with(\Mockery::any(), 'body')
+ ->andReturn(false);
+ $cache->shouldReceive('getInputByteStream')
+ ->once()
+ ->with(\Mockery::any(), 'body');
+
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $cache
+ );
+ $entity->setBody('foo');
+
+ $entity->toByteStream($is);
+ }
+
+ public function testFluidInterface()
+ {
+ $entity = $this->_createEntity($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $this->assertSame($entity,
+ $entity
+ ->setContentType('text/plain')
+ ->setEncoder($this->_createEncoder())
+ ->setId('foo@bar')
+ ->setDescription('my description')
+ ->setMaxLineLength(998)
+ ->setBody('xx')
+ ->setBoundary('xyz')
+ ->setChildren(array())
+ );
+ }
+
+ abstract protected function _createEntity($headers, $encoder, $cache);
+
+ protected function _createChild($level = null, $string = '', $stub = true)
+ {
+ $child = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing();
+ if (isset($level)) {
+ $child->shouldReceive('getNestingLevel')
+ ->zeroOrMoreTimes()
+ ->andReturn($level);
+ }
+ $child->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn($string);
+
+ return $child;
+ }
+
+ protected function _createEncoder($name = 'quoted-printable', $stub = true)
+ {
+ $encoder = $this->getMockBuilder('Swift_Mime_ContentEncoder')->getMock();
+ $encoder->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue($name));
+ $encoder->expects($this->any())
+ ->method('encodeString')
+ ->will($this->returnCallback(function () {
+ $args = func_get_args();
+
+ return array_shift($args);
+ }));
+
+ return $encoder;
+ }
+
+ protected function _createCache($stub = true)
+ {
+ return $this->getMockery('Swift_KeyCache')->shouldIgnoreMissing();
+ }
+
+ protected function _createHeaderSet($headers = array(), $stub = true)
+ {
+ $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();
+ $set->shouldReceive('get')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function ($key) use ($headers) {
+ return $headers[$key];
+ });
+ $set->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function ($key) use ($headers) {
+ return array_key_exists($key, $headers);
+ });
+
+ return $set;
+ }
+
+ protected function _createHeader($name, $model = null, $params = array(), $stub = true)
+ {
+ $header = $this->getMockery('Swift_Mime_ParameterizedHeader')->shouldIgnoreMissing();
+ $header->shouldReceive('getFieldName')
+ ->zeroOrMoreTimes()
+ ->andReturn($name);
+ $header->shouldReceive('getFieldBodyModel')
+ ->zeroOrMoreTimes()
+ ->andReturn($model);
+ $header->shouldReceive('getParameter')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function ($key) use ($params) {
+ return $params[$key];
+ });
+
+ return $header;
+ }
+
+ protected function _createOutputStream($data = null, $stub = true)
+ {
+ $os = $this->getMockery('Swift_OutputByteStream');
+ if (isset($data)) {
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use ($data) {
+ static $first = true;
+ if (!$first) {
+ return false;
+ }
+
+ $first = false;
+
+ return $data;
+ });
+ $os->shouldReceive('setReadPointer')
+ ->zeroOrMoreTimes();
+ }
+
+ return $os;
+ }
+
+ protected function _createInputStream($stub = true)
+ {
+ return $this->getMockBuilder('Swift_InputByteStream')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php
new file mode 100644
index 0000000..2c1e581
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php
@@ -0,0 +1,318 @@
+<?php
+
+class Swift_Mime_AttachmentTest extends Swift_Mime_AbstractMimeEntityTest
+{
+ public function testNestingLevelIsAttachment()
+ {
+ $attachment = $this->_createAttachment($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(
+ Swift_Mime_MimeEntity::LEVEL_MIXED, $attachment->getNestingLevel()
+ );
+ }
+
+ public function testDispositionIsReturnedFromHeader()
+ {
+ /* -- RFC 2183, 2.1, 2.2.
+ */
+
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment');
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('attachment', $attachment->getDisposition());
+ }
+
+ public function testDispositionIsSetInHeader()
+ {
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array(), false
+ );
+ $disposition->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('inline');
+ $disposition->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setDisposition('inline');
+ }
+
+ public function testDispositionIsAddedIfNonePresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addParameterizedHeader')
+ ->once()
+ ->with('Content-Disposition', 'inline');
+ $headers->shouldReceive('addParameterizedHeader')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $attachment->setDisposition('inline');
+ }
+
+ public function testDispositionIsAutoDefaultedToAttachment()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addParameterizedHeader')
+ ->once()
+ ->with('Content-Disposition', 'attachment');
+ $headers->shouldReceive('addParameterizedHeader')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ }
+
+ public function testDefaultContentTypeInitializedToOctetStream()
+ {
+ $cType = $this->_createHeader('Content-Type', '',
+ array(), false
+ );
+ $cType->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('application/octet-stream');
+ $cType->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ }
+
+ public function testFilenameIsReturnedFromHeader()
+ {
+ /* -- RFC 2183, 2.3.
+ */
+
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('filename' => 'foo.txt')
+ );
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('foo.txt', $attachment->getFilename());
+ }
+
+ public function testFilenameIsSetInHeader()
+ {
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('filename' => 'foo.txt'), false
+ );
+ $disposition->shouldReceive('setParameter')
+ ->once()
+ ->with('filename', 'bar.txt');
+ $disposition->shouldReceive('setParameter')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setFilename('bar.txt');
+ }
+
+ public function testSettingFilenameSetsNameInContentType()
+ {
+ /*
+ This is a legacy requirement which isn't covered by up-to-date RFCs.
+ */
+
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array(), false
+ );
+ $cType->shouldReceive('setParameter')
+ ->once()
+ ->with('name', 'bar.txt');
+ $cType->shouldReceive('setParameter')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setFilename('bar.txt');
+ }
+
+ public function testSizeIsReturnedFromHeader()
+ {
+ /* -- RFC 2183, 2.7.
+ */
+
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('size' => 1234)
+ );
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(1234, $attachment->getSize());
+ }
+
+ public function testSizeIsSetInHeader()
+ {
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array(), false
+ );
+ $disposition->shouldReceive('setParameter')
+ ->once()
+ ->with('size', 12345);
+ $disposition->shouldReceive('setParameter')
+ ->zeroOrMoreTimes();
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setSize(12345);
+ }
+
+ public function testFilnameCanBeReadFromFileStream()
+ {
+ $file = $this->_createFileStream('/bar/file.ext', '');
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('filename' => 'foo.txt'), false
+ );
+ $disposition->shouldReceive('setParameter')
+ ->once()
+ ->with('filename', 'file.ext');
+
+ $attachment = $this->_createAttachment($this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setFile($file);
+ }
+
+ public function testContentTypeCanBeSetViaSetFile()
+ {
+ $file = $this->_createFileStream('/bar/file.ext', '');
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('filename' => 'foo.txt'), false
+ );
+ $disposition->shouldReceive('setParameter')
+ ->once()
+ ->with('filename', 'file.ext');
+
+ $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('text/html');
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $headers = $this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition,
+ 'Content-Type' => $ctype,
+ ));
+
+ $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $attachment->setFile($file, 'text/html');
+ }
+
+ public function XtestContentTypeCanBeLookedUpFromCommonListIfNotProvided()
+ {
+ $file = $this->_createFileStream('/bar/file.zip', '');
+ $disposition = $this->_createHeader('Content-Disposition', 'attachment',
+ array('filename' => 'foo.zip'), false
+ );
+ $disposition->shouldReceive('setParameter')
+ ->once()
+ ->with('filename', 'file.zip');
+
+ $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('application/zip');
+ $ctype->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $headers = $this->_createHeaderSet(array(
+ 'Content-Disposition' => $disposition,
+ 'Content-Type' => $ctype,
+ ));
+
+ $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
+ $this->_createCache(), array('zip' => 'application/zip', 'txt' => 'text/plain')
+ );
+ $attachment->setFile($file);
+ }
+
+ public function testDataCanBeReadFromFile()
+ {
+ $file = $this->_createFileStream('/foo/file.ext', '<some data>');
+ $attachment = $this->_createAttachment($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $attachment->setFile($file);
+ $this->assertEquals('<some data>', $attachment->getBody());
+ }
+
+ public function testFluidInterface()
+ {
+ $attachment = $this->_createAttachment($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertSame($attachment,
+ $attachment
+ ->setContentType('application/pdf')
+ ->setEncoder($this->_createEncoder())
+ ->setId('foo@bar')
+ ->setDescription('my pdf')
+ ->setMaxLineLength(998)
+ ->setBody('xx')
+ ->setBoundary('xyz')
+ ->setChildren(array())
+ ->setDisposition('inline')
+ ->setFilename('afile.txt')
+ ->setSize(123)
+ ->setFile($this->_createFileStream('foo.txt', ''))
+ );
+ }
+
+ protected function _createEntity($headers, $encoder, $cache)
+ {
+ return $this->_createAttachment($headers, $encoder, $cache);
+ }
+
+ protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array())
+ {
+ return new Swift_Mime_Attachment($headers, $encoder, $cache, new Swift_Mime_Grammar(), $mimeTypes);
+ }
+
+ protected function _createFileStream($path, $data, $stub = true)
+ {
+ $file = $this->getMockery('Swift_FileStream');
+ $file->shouldReceive('getPath')
+ ->zeroOrMoreTimes()
+ ->andReturn($path);
+ $file->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use ($data) {
+ static $first = true;
+ if (!$first) {
+ return false;
+ }
+
+ $first = false;
+
+ return $data;
+ });
+ $file->shouldReceive('setReadPointer')
+ ->zeroOrMoreTimes();
+
+ return $file;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php
new file mode 100644
index 0000000..1571fce
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php
@@ -0,0 +1,323 @@
+<?php
+
+class Swift_Mime_ContentEncoder_Base64ContentEncoderTest extends \SwiftMailerTestCase
+{
+ private $_encoder;
+
+ protected function setUp()
+ {
+ $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+ }
+
+ public function testNameIsBase64()
+ {
+ $this->assertEquals('base64', $this->_encoder->getName());
+ }
+
+ /*
+ There's really no point in testing the entire base64 encoding to the
+ level QP encoding has been tested. base64_encode() has been in PHP for
+ years.
+ */
+
+ public function testInputOutputRatioIs3to4Bytes()
+ {
+ /*
+ RFC 2045, 6.8
+
+ The encoding process represents 24-bit groups of input bits as output
+ strings of 4 encoded characters. Proceeding from left to right, a
+ 24-bit input group is formed by concatenating 3 8bit input groups.
+ These 24 bits are then treated as 4 concatenated 6-bit groups, each
+ of which is translated into a single digit in the base64 alphabet.
+ */
+
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('123');
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is);
+ $this->assertEquals('MTIz', $collection->content);
+ }
+
+ public function testPadLength()
+ {
+ /*
+ RFC 2045, 6.8
+
+ Special processing is performed if fewer than 24 bits are available
+ at the end of the data being encoded. A full encoding quantum is
+ always completed at the end of a body. When fewer than 24 input bits
+ are available in an input group, zero bits are added (on the right)
+ to form an integral number of 6-bit groups. Padding at the end of
+ the data is performed using the "=" character. Since all base64
+ input is an integral number of octets, only the following cases can
+ arise: (1) the final quantum of encoding input is an integral
+ multiple of 24 bits; here, the final unit of encoded output will be
+ an integral multiple of 4 characters with no "=" padding, (2) the
+ final quantum of encoding input is exactly 8 bits; here, the final
+ unit of encoded output will be two characters followed by two "="
+ padding characters, or (3) the final quantum of encoding input is
+ exactly 16 bits; here, the final unit of encoded output will be three
+ characters followed by one "=" padding character.
+ */
+
+ for ($i = 0; $i < 30; ++$i) {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn(pack('C', rand(0, 255)));
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is);
+ $this->assertRegExp('~^[a-zA-Z0-9/\+]{2}==$~', $collection->content,
+ '%s: A single byte should have 2 bytes of padding'
+ );
+ }
+
+ for ($i = 0; $i < 30; ++$i) {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn(pack('C*', rand(0, 255), rand(0, 255)));
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is);
+ $this->assertRegExp('~^[a-zA-Z0-9/\+]{3}=$~', $collection->content,
+ '%s: Two bytes should have 1 byte of padding'
+ );
+ }
+
+ for ($i = 0; $i < 30; ++$i) {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn(pack('C*', rand(0, 255), rand(0, 255), rand(0, 255)));
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is);
+ $this->assertRegExp('~^[a-zA-Z0-9/\+]{4}$~', $collection->content,
+ '%s: Three bytes should have no padding'
+ );
+ }
+ }
+
+ public function testMaximumLineLengthIs76Characters()
+ {
+ /*
+ The encoded output stream must be represented in lines of no more
+ than 76 characters each. All line breaks or other characters not
+ found in Table 1 must be ignored by decoding software.
+ */
+
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //12
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('mnopqrstuvwx'); //24
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('yzabc1234567'); //36
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('890ABCDEFGHI'); //48
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('JKLMNOPQRSTU'); //60
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('VWXYZ1234567'); //72
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //84
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is);
+ $this->assertEquals(
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n".
+ 'U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
+ $collection->content
+ );
+ }
+
+ public function testMaximumLineLengthCanBeDifferent()
+ {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //12
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('mnopqrstuvwx'); //24
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('yzabc1234567'); //36
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('890ABCDEFGHI'); //48
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('JKLMNOPQRSTU'); //60
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('VWXYZ1234567'); //72
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //84
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is, 0, 50);
+ $this->assertEquals(
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3OD\r\n".
+ "kwQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJj\r\n".
+ 'ZGVmZ2hpamts',
+ $collection->content
+ );
+ }
+
+ public function testMaximumLineLengthIsNeverMoreThan76Chars()
+ {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //12
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('mnopqrstuvwx'); //24
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('yzabc1234567'); //36
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('890ABCDEFGHI'); //48
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('JKLMNOPQRSTU'); //60
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('VWXYZ1234567'); //72
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //84
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is, 0, 100);
+ $this->assertEquals(
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n".
+ 'U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
+ $collection->content
+ );
+ }
+
+ public function testFirstLineLengthCanBeDifferent()
+ {
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //12
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('mnopqrstuvwx'); //24
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('yzabc1234567'); //36
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('890ABCDEFGHI'); //48
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('JKLMNOPQRSTU'); //60
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('VWXYZ1234567'); //72
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('abcdefghijkl'); //84
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_encoder->encodeByteStream($os, $is, 19);
+ $this->assertEquals(
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDR\r\n".
+ 'EVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
+ $collection->content
+ );
+ }
+
+ private function _createOutputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
+ }
+
+ private function _createInputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php
new file mode 100644
index 0000000..ca44e11
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php
@@ -0,0 +1,171 @@
+<?php
+
+class Swift_Mime_ContentEncoder_PlainContentEncoderTest extends \SwiftMailerTestCase
+{
+ public function testNameCanBeSpecifiedInConstructor()
+ {
+ $encoder = $this->_getEncoder('7bit');
+ $this->assertEquals('7bit', $encoder->getName());
+
+ $encoder = $this->_getEncoder('8bit');
+ $this->assertEquals('8bit', $encoder->getName());
+ }
+
+ public function testNoOctetsAreModifiedInString()
+ {
+ $encoder = $this->_getEncoder('7bit');
+ foreach (range(0x00, 0xFF) as $octet) {
+ $byte = pack('C', $octet);
+ $this->assertIdenticalBinary($byte, $encoder->encodeString($byte));
+ }
+ }
+
+ public function testNoOctetsAreModifiedInByteStream()
+ {
+ $encoder = $this->_getEncoder('7bit');
+ foreach (range(0x00, 0xFF) as $octet) {
+ $byte = pack('C', $octet);
+
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn($byte);
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder->encodeByteStream($os, $is);
+ $this->assertIdenticalBinary($byte, $collection->content);
+ }
+ }
+
+ public function testLineLengthCanBeSpecified()
+ {
+ $encoder = $this->_getEncoder('7bit');
+
+ $chars = array();
+ for ($i = 0; $i < 50; ++$i) {
+ $chars[] = 'a';
+ }
+ $input = implode(' ', $chars); //99 chars long
+
+ $this->assertEquals(
+ 'a a a a a a a a a a a a a a a a a a a a a a a a a '."\r\n".//50 *
+ 'a a a a a a a a a a a a a a a a a a a a a a a a a', //99
+ $encoder->encodeString($input, 0, 50),
+ '%s: Lines should be wrapped at 50 chars'
+ );
+ }
+
+ public function testLineLengthCanBeSpecifiedInByteStream()
+ {
+ $encoder = $this->_getEncoder('7bit');
+
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+
+ for ($i = 0; $i < 50; ++$i) {
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('a ');
+ }
+
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder->encodeByteStream($os, $is, 0, 50);
+ $this->assertEquals(
+ str_repeat('a ', 25)."\r\n".str_repeat('a ', 25),
+ $collection->content
+ );
+ }
+
+ public function testencodeStringGeneratesCorrectCrlf()
+ {
+ $encoder = $this->_getEncoder('7bit', true);
+ $this->assertEquals("a\r\nb", $encoder->encodeString("a\rb"),
+ '%s: Line endings should be standardized'
+ );
+ $this->assertEquals("a\r\nb", $encoder->encodeString("a\nb"),
+ '%s: Line endings should be standardized'
+ );
+ $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\rb"),
+ '%s: Line endings should be standardized'
+ );
+ $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\r\rb"),
+ '%s: Line endings should be standardized'
+ );
+ $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\nb"),
+ '%s: Line endings should be standardized'
+ );
+ }
+
+ public function crlfProvider()
+ {
+ return array(
+ array("\r", "a\r\nb"),
+ array("\n", "a\r\nb"),
+ array("\n\r", "a\r\n\r\nb"),
+ array("\n\n", "a\r\n\r\nb"),
+ array("\r\r", "a\r\n\r\nb"),
+ );
+ }
+
+ /**
+ * @dataProvider crlfProvider
+ */
+ public function testCanonicEncodeByteStreamGeneratesCorrectCrlf($test, $expected)
+ {
+ $encoder = $this->_getEncoder('7bit', true);
+
+ $os = $this->_createOutputByteStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('a');
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn($test);
+ $os->shouldReceive('read')
+ ->once()
+ ->andReturn('b');
+ $os->shouldReceive('read')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals($expected, $collection->content);
+ }
+
+ private function _getEncoder($name, $canonical = false)
+ {
+ return new Swift_Mime_ContentEncoder_PlainContentEncoder($name, $canonical);
+ }
+
+ private function _createOutputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
+ }
+
+ private function _createInputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php
new file mode 100644
index 0000000..7762bbe
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php
@@ -0,0 +1,516 @@
+<?php
+
+class Swift_Mime_ContentEncoder_QpContentEncoderTest extends \SwiftMailerTestCase
+{
+ public function testNameIsQuotedPrintable()
+ {
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder(
+ $this->_createCharacterStream(true)
+ );
+ $this->assertEquals('quoted-printable', $encoder->getName());
+ }
+
+ /* -- RFC 2045, 6.7 --
+ (1) (General 8bit representation) Any octet, except a CR or
+ LF that is part of a CRLF line break of the canonical
+ (standard) form of the data being encoded, may be
+ represented by an "=" followed by a two digit
+ hexadecimal representation of the octet's value. The
+ digits of the hexadecimal alphabet, for this purpose,
+ are "0123456789ABCDEF". Uppercase letters must be
+ used; lowercase letters are not allowed. Thus, for
+ example, the decimal value 12 (US-ASCII form feed) can
+ be represented by "=0C", and the decimal value 61 (US-
+ ASCII EQUAL SIGN) can be represented by "=3D". This
+ rule must be followed except when the following rules
+ allow an alternative encoding.
+ */
+
+ public function testPermittedCharactersAreNotEncoded()
+ {
+ /* -- RFC 2045, 6.7 --
+ (2) (Literal representation) Octets with decimal values of
+ 33 through 60 inclusive, and 62 through 126, inclusive,
+ MAY be represented as the US-ASCII characters which
+ correspond to those octets (EXCLAMATION POINT through
+ LESS THAN, and GREATER THAN through TILDE,
+ respectively).
+ */
+
+ foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) {
+ $char = chr($ordinal);
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertIdenticalBinary($char, $collection->content);
+ }
+ }
+
+ public function testLinearWhiteSpaceAtLineEndingIsEncoded()
+ {
+ /* -- RFC 2045, 6.7 --
+ (3) (White Space) Octets with values of 9 and 32 MAY be
+ represented as US-ASCII TAB (HT) and SPACE characters,
+ respectively, but MUST NOT be so represented at the end
+ of an encoded line. Any TAB (HT) or SPACE characters
+ on an encoded line MUST thus be followed on that line
+ by a printable character. In particular, an "=" at the
+ end of an encoded line, indicating a soft line break
+ (see rule #5) may follow one or more TAB (HT) or SPACE
+ characters. It follows that an octet with decimal
+ value 9 or 32 appearing at the end of an encoded line
+ must be represented according to Rule #1. This rule is
+ necessary because some MTAs (Message Transport Agents,
+ programs which transport messages from one user to
+ another, or perform a portion of such transfers) are
+ known to pad lines of text with SPACEs, and others are
+ known to remove "white space" characters from the end
+ of a line. Therefore, when decoding a Quoted-Printable
+ body, any trailing white space on a line must be
+ deleted, as it will necessarily have been added by
+ intermediate transport agents.
+ */
+
+ $HT = chr(0x09); //9
+ $SPACE = chr(0x20); //32
+
+ //HT
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x09));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x09));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+
+ $this->assertEquals("a\t=09\r\nb", $collection->content);
+
+ //SPACE
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x20));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x20));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+
+ $this->assertEquals("a =20\r\nb", $collection->content);
+ }
+
+ public function testCRLFIsLeftAlone()
+ {
+ /*
+ (4) (Line Breaks) A line break in a text body, represented
+ as a CRLF sequence in the text canonical form, must be
+ represented by a (RFC 822) line break, which is also a
+ CRLF sequence, in the Quoted-Printable encoding. Since
+ the canonical representation of media types other than
+ text do not generally include the representation of
+ line breaks as CRLF sequences, no hard line breaks
+ (i.e. line breaks that are intended to be meaningful
+ and to be displayed to the user) can occur in the
+ quoted-printable encoding of such types. Sequences
+ like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
+ appear in non-text data represented in quoted-
+ printable, of course.
+
+ Note that many implementations may elect to encode the
+ local representation of various content types directly
+ rather than converting to canonical form first,
+ encoding, and then converting back to local
+ representation. In particular, this may apply to plain
+ text material on systems that use newline conventions
+ other than a CRLF terminator sequence. Such an
+ implementation optimization is permissible, but only
+ when the combined canonicalization-encoding step is
+ equivalent to performing the three steps separately.
+ */
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('c')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0D));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x0A));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals("a\r\nb\r\nc\r\n", $collection->content);
+ }
+
+ public function testLinesLongerThan76CharactersAreSoftBroken()
+ {
+ /*
+ (5) (Soft Line Breaks) The Quoted-Printable encoding
+ REQUIRES that encoded lines be no more than 76
+ characters long. If longer lines are to be encoded
+ with the Quoted-Printable encoding, "soft" line breaks
+ must be used. An equal sign as the last character on a
+ encoded line indicates such a non-significant ("soft")
+ line break in the encoded text.
+ */
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+
+ for ($seq = 0; $seq <= 140; ++$seq) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ }
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals(str_repeat('a', 75)."=\r\n".str_repeat('a', 66), $collection->content);
+ }
+
+ public function testMaxLineLengthCanBeSpecified()
+ {
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+
+ for ($seq = 0; $seq <= 100; ++$seq) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ }
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is, 0, 54);
+ $this->assertEquals(str_repeat('a', 53)."=\r\n".str_repeat('a', 48), $collection->content);
+ }
+
+ public function testBytesBelowPermittedRangeAreEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ foreach (range(0, 32) as $ordinal) {
+ $char = chr($ordinal);
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content);
+ }
+ }
+
+ public function testDecimalByte61IsEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ $char = chr(61);
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(61));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals(sprintf('=%02X', 61), $collection->content);
+ }
+
+ public function testBytesAbovePermittedRangeAreEncoded()
+ {
+ /*
+ According to Rule (1 & 2)
+ */
+
+ foreach (range(127, 255) as $ordinal) {
+ $char = chr($ordinal);
+
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($ordinal));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is);
+ $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content);
+ }
+ }
+
+ public function testFirstLineLengthCanBeDifferent()
+ {
+ $os = $this->_createOutputByteStream(true);
+ $charStream = $this->_createCharacterStream();
+ $is = $this->_createInputByteStream();
+ $collection = new Swift_StreamCollector();
+
+ $is->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing($collection);
+ $charStream->shouldReceive('flushContents')
+ ->once();
+ $charStream->shouldReceive('importByteStream')
+ ->once()
+ ->with($os);
+
+ for ($seq = 0; $seq <= 140; ++$seq) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ }
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ $encoder->encodeByteStream($os, $is, 22);
+ $this->assertEquals(
+ str_repeat('a', 53)."=\r\n".str_repeat('a', 75)."=\r\n".str_repeat('a', 13),
+ $collection->content
+ );
+ }
+
+ public function testObserverInterfaceCanChangeCharset()
+ {
+ $stream = $this->_createCharacterStream();
+ $stream->shouldReceive('setCharacterSet')
+ ->once()
+ ->with('windows-1252');
+
+ $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($stream);
+ $encoder->charsetChanged('windows-1252');
+ }
+
+ public function testTextIsPreWrapped()
+ {
+ $encoder = $this->createEncoder();
+
+ $input = str_repeat('a', 70)."\r\n".
+ str_repeat('a', 70)."\r\n".
+ str_repeat('a', 70);
+
+ $os = new Swift_ByteStream_ArrayByteStream();
+ $is = new Swift_ByteStream_ArrayByteStream();
+ $is->write($input);
+
+ $encoder->encodeByteStream($is, $os);
+
+ $this->assertEquals(
+ $input, $os->read(PHP_INT_MAX)
+ );
+ }
+
+ private function _createCharacterStream($stub = false)
+ {
+ return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
+ }
+
+ private function createEncoder()
+ {
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');
+
+ return new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
+ }
+
+ private function _createOutputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
+ }
+
+ private function _createInputByteStream($stub = false)
+ {
+ return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php
new file mode 100644
index 0000000..3a1fc51
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class Swift_Mime_EmbeddedFileTest extends Swift_Mime_AttachmentTest
+{
+ public function testNestingLevelIsAttachment()
+ {
+ //Overridden
+ }
+
+ public function testNestingLevelIsEmbedded()
+ {
+ $file = $this->_createEmbeddedFile($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(
+ Swift_Mime_MimeEntity::LEVEL_RELATED, $file->getNestingLevel()
+ );
+ }
+
+ public function testIdIsAutoGenerated()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addIdHeader')
+ ->once()
+ ->with('Content-ID', '/^.*?@.*?$/D');
+
+ $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ }
+
+ public function testDefaultDispositionIsInline()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addParameterizedHeader')
+ ->once()
+ ->with('Content-Disposition', 'inline');
+ $headers->shouldReceive('addParameterizedHeader')
+ ->zeroOrMoreTimes();
+
+ $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ }
+
+ protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array())
+ {
+ return $this->_createEmbeddedFile($headers, $encoder, $cache, $mimeTypes);
+ }
+
+ private function _createEmbeddedFile($headers, $encoder, $cache)
+ {
+ return new Swift_Mime_EmbeddedFile($headers, $encoder, $cache, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php
new file mode 100644
index 0000000..3580155
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php
@@ -0,0 +1,13 @@
+<?php
+
+class Swift_Mime_HeaderEncoder_Base64HeaderEncoderTest extends \PHPUnit_Framework_TestCase
+{
+ //Most tests are already covered in Base64EncoderTest since this subclass only
+ // adds a getName() method
+
+ public function testNameIsB()
+ {
+ $encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder();
+ $this->assertEquals('B', $encoder->getName());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php
new file mode 100644
index 0000000..b5a10fe
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php
@@ -0,0 +1,221 @@
+<?php
+
+class Swift_Mime_HeaderEncoder_QpHeaderEncoderTest extends \SwiftMailerTestCase
+{
+ //Most tests are already covered in QpEncoderTest since this subclass only
+ // adds a getName() method
+
+ public function testNameIsQ()
+ {
+ $encoder = $this->_createEncoder(
+ $this->_createCharacterStream(true)
+ );
+ $this->assertEquals('Q', $encoder->getName());
+ }
+
+ public function testSpaceAndTabNeverAppear()
+ {
+ /* -- RFC 2047, 4.
+ Only a subset of the printable ASCII characters may be used in
+ 'encoded-text'. Space and tab characters are not allowed, so that
+ the beginning and end of an 'encoded-word' are obvious.
+ */
+
+ $charStream = $this->_createCharacterStream();
+ $charStream->shouldReceive('readBytes')
+ ->atLeast()->times(6)
+ ->andReturn(array(ord('a')), array(0x20), array(0x09), array(0x20), array(ord('b')), false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $this->assertNotRegExp('~[ \t]~', $encoder->encodeString("a \t b"),
+ '%s: encoded-words in headers cannot contain LWSP as per RFC 2047.'
+ );
+ }
+
+ public function testSpaceIsRepresentedByUnderscore()
+ {
+ /* -- RFC 2047, 4.2.
+ (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be
+ represented as "_" (underscore, ASCII 95.). (This character may
+ not pass through some internetwork mail gateways, but its use
+ will greatly enhance readability of "Q" encoded data with mail
+ readers that do not support this encoding.) Note that the "_"
+ always represents hexadecimal 20, even if the SPACE character
+ occupies a different code position in the character set in use.
+ */
+ $charStream = $this->_createCharacterStream();
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(0x20));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('b')));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $this->assertEquals('a_b', $encoder->encodeString('a b'),
+ '%s: Spaces can be represented by more readable underscores as per RFC 2047.'
+ );
+ }
+
+ public function testEqualsAndQuestionAndUnderscoreAreEncoded()
+ {
+ /* -- RFC 2047, 4.2.
+ (3) 8-bit values which correspond to printable ASCII characters other
+ than "=", "?", and "_" (underscore), MAY be represented as those
+ characters. (But see section 5 for restrictions.) In
+ particular, SPACE and TAB MUST NOT be represented as themselves
+ within encoded words.
+ */
+ $charStream = $this->_createCharacterStream();
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('=')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('?')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('_')));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $this->assertEquals('=3D=3F=5F', $encoder->encodeString('=?_'),
+ '%s: Chars =, ? and _ (underscore) may not appear as per RFC 2047.'
+ );
+ }
+
+ public function testParensAndQuotesAreEncoded()
+ {
+ /* -- RFC 2047, 5 (2).
+ A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT
+ contain the characters "(", ")" or "
+ */
+
+ $charStream = $this->_createCharacterStream();
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('(')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('"')));
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord(')')));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $this->assertEquals('=28=22=29', $encoder->encodeString('(")'),
+ '%s: Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.'
+ );
+ }
+
+ public function testOnlyCharactersAllowedInPhrasesAreUsed()
+ {
+ /* -- RFC 2047, 5.
+ (3) As a replacement for a 'word' entity within a 'phrase', for example,
+ one that precedes an address in a From, To, or Cc header. The ABNF
+ definition for 'phrase' from RFC 822 thus becomes:
+
+ phrase = 1*( encoded-word / word )
+
+ In this case the set of characters that may be used in a "Q"-encoded
+ 'encoded-word' is restricted to: <upper and lower case ASCII
+ letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
+ (underscore, ASCII 95.)>. An 'encoded-word' that appears within a
+ 'phrase' MUST be separated from any adjacent 'word', 'text' or
+ 'special' by 'linear-white-space'.
+ */
+
+ $allowedBytes = array_merge(
+ range(ord('a'), ord('z')), range(ord('A'), ord('Z')),
+ range(ord('0'), ord('9')),
+ array(ord('!'), ord('*'), ord('+'), ord('-'), ord('/'))
+ );
+
+ foreach (range(0x00, 0xFF) as $byte) {
+ $char = pack('C', $byte);
+
+ $charStream = $this->_createCharacterStream();
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array($byte));
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $encodedChar = $encoder->encodeString($char);
+
+ if (in_array($byte, $allowedBytes)) {
+ $this->assertEquals($char, $encodedChar,
+ '%s: Character '.$char.' should not be encoded.'
+ );
+ } elseif (0x20 == $byte) {
+ //Special case
+ $this->assertEquals('_', $encodedChar,
+ '%s: Space character should be replaced.'
+ );
+ } else {
+ $this->assertEquals(sprintf('=%02X', $byte), $encodedChar,
+ '%s: Byte '.$byte.' should be encoded.'
+ );
+ }
+ }
+ }
+
+ public function testEqualsNeverAppearsAtEndOfLine()
+ {
+ /* -- RFC 2047, 5 (3).
+ The 'encoded-text' in an 'encoded-word' must be self-contained;
+ 'encoded-text' MUST NOT be continued from one 'encoded-word' to
+ another. This implies that the 'encoded-text' portion of a "B"
+ 'encoded-word' will be a multiple of 4 characters long; for a "Q"
+ 'encoded-word', any "=" character that appears in the 'encoded-text'
+ portion will be followed by two hexadecimal characters.
+ */
+
+ $input = str_repeat('a', 140);
+
+ $charStream = $this->_createCharacterStream();
+
+ $output = '';
+ $seq = 0;
+ for (; $seq < 140; ++$seq) {
+ $charStream->shouldReceive('readBytes')
+ ->once()
+ ->andReturn(array(ord('a')));
+
+ if (75 == $seq) {
+ $output .= "\r\n"; // =\r\n
+ }
+ $output .= 'a';
+ }
+
+ $charStream->shouldReceive('readBytes')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $encoder = $this->_createEncoder($charStream);
+ $this->assertEquals($output, $encoder->encodeString($input));
+ }
+
+ private function _createEncoder($charStream)
+ {
+ return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream);
+ }
+
+ private function _createCharacterStream($stub = false)
+ {
+ return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php
new file mode 100644
index 0000000..1822ea6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php
@@ -0,0 +1,69 @@
+<?php
+
+class Swift_Mime_Headers_DateHeaderTest extends \PHPUnit_Framework_TestCase
+{
+ /* --
+ The following tests refer to RFC 2822, section 3.6.1 and 3.3.
+ */
+
+ public function testTypeIsDateHeader()
+ {
+ $header = $this->_getHeader('Date');
+ $this->assertEquals(Swift_Mime_Header::TYPE_DATE, $header->getFieldType());
+ }
+
+ public function testGetTimestamp()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setTimestamp($timestamp);
+ $this->assertSame($timestamp, $header->getTimestamp());
+ }
+
+ public function testTimestampCanBeSetBySetter()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setTimestamp($timestamp);
+ $this->assertSame($timestamp, $header->getTimestamp());
+ }
+
+ public function testIntegerTimestampIsConvertedToRfc2822Date()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setTimestamp($timestamp);
+ $this->assertEquals(date('r', $timestamp), $header->getFieldBody());
+ }
+
+ public function testSetBodyModel()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setFieldBodyModel($timestamp);
+ $this->assertEquals(date('r', $timestamp), $header->getFieldBody());
+ }
+
+ public function testGetBodyModel()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setTimestamp($timestamp);
+ $this->assertEquals($timestamp, $header->getFieldBodyModel());
+ }
+
+ public function testToString()
+ {
+ $timestamp = time();
+ $header = $this->_getHeader('Date');
+ $header->setTimestamp($timestamp);
+ $this->assertEquals('Date: '.date('r', $timestamp)."\r\n",
+ $header->toString()
+ );
+ }
+
+ private function _getHeader($name)
+ {
+ return new Swift_Mime_Headers_DateHeader($name, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php
new file mode 100644
index 0000000..93b3f60
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php
@@ -0,0 +1,189 @@
+<?php
+
+class Swift_Mime_Headers_IdentificationHeaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testTypeIsIdHeader()
+ {
+ $header = $this->_getHeader('Message-ID');
+ $this->assertEquals(Swift_Mime_Header::TYPE_ID, $header->getFieldType());
+ }
+
+ public function testValueMatchesMsgIdSpec()
+ {
+ /* -- RFC 2822, 3.6.4.
+ message-id = "Message-ID:" msg-id CRLF
+
+ in-reply-to = "In-Reply-To:" 1*msg-id CRLF
+
+ references = "References:" 1*msg-id CRLF
+
+ msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]
+
+ id-left = dot-atom-text / no-fold-quote / obs-id-left
+
+ id-right = dot-atom-text / no-fold-literal / obs-id-right
+
+ no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE
+
+ no-fold-literal = "[" *(dtext / quoted-pair) "]"
+ */
+
+ $header = $this->_getHeader('Message-ID');
+ $header->setId('id-left@id-right');
+ $this->assertEquals('<id-left@id-right>', $header->getFieldBody());
+ }
+
+ public function testIdCanBeRetrievedVerbatim()
+ {
+ $header = $this->_getHeader('Message-ID');
+ $header->setId('id-left@id-right');
+ $this->assertEquals('id-left@id-right', $header->getId());
+ }
+
+ public function testMultipleIdsCanBeSet()
+ {
+ $header = $this->_getHeader('References');
+ $header->setIds(array('a@b', 'x@y'));
+ $this->assertEquals(array('a@b', 'x@y'), $header->getIds());
+ }
+
+ public function testSettingMultipleIdsProducesAListValue()
+ {
+ /* -- RFC 2822, 3.6.4.
+ The "References:" and "In-Reply-To:" field each contain one or more
+ unique message identifiers, optionally separated by CFWS.
+
+ .. SNIP ..
+
+ in-reply-to = "In-Reply-To:" 1*msg-id CRLF
+
+ references = "References:" 1*msg-id CRLF
+ */
+
+ $header = $this->_getHeader('References');
+ $header->setIds(array('a@b', 'x@y'));
+ $this->assertEquals('<a@b> <x@y>', $header->getFieldBody());
+ }
+
+ public function testIdLeftCanBeQuoted()
+ {
+ /* -- RFC 2822, 3.6.4.
+ id-left = dot-atom-text / no-fold-quote / obs-id-left
+ */
+
+ $header = $this->_getHeader('References');
+ $header->setId('"ab"@c');
+ $this->assertEquals('"ab"@c', $header->getId());
+ $this->assertEquals('<"ab"@c>', $header->getFieldBody());
+ }
+
+ public function testIdLeftCanContainAnglesAsQuotedPairs()
+ {
+ /* -- RFC 2822, 3.6.4.
+ no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE
+ */
+
+ $header = $this->_getHeader('References');
+ $header->setId('"a\\<\\>b"@c');
+ $this->assertEquals('"a\\<\\>b"@c', $header->getId());
+ $this->assertEquals('<"a\\<\\>b"@c>', $header->getFieldBody());
+ }
+
+ public function testIdLeftCanBeDotAtom()
+ {
+ $header = $this->_getHeader('References');
+ $header->setId('a.b+&%$.c@d');
+ $this->assertEquals('a.b+&%$.c@d', $header->getId());
+ $this->assertEquals('<a.b+&%$.c@d>', $header->getFieldBody());
+ }
+
+ public function testInvalidIdLeftThrowsException()
+ {
+ try {
+ $header = $this->_getHeader('References');
+ $header->setId('a b c@d');
+ $this->fail(
+ 'Exception should be thrown since "a b c" is not valid id-left.'
+ );
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testIdRightCanBeDotAtom()
+ {
+ /* -- RFC 2822, 3.6.4.
+ id-right = dot-atom-text / no-fold-literal / obs-id-right
+ */
+
+ $header = $this->_getHeader('References');
+ $header->setId('a@b.c+&%$.d');
+ $this->assertEquals('a@b.c+&%$.d', $header->getId());
+ $this->assertEquals('<a@b.c+&%$.d>', $header->getFieldBody());
+ }
+
+ public function testIdRightCanBeLiteral()
+ {
+ /* -- RFC 2822, 3.6.4.
+ no-fold-literal = "[" *(dtext / quoted-pair) "]"
+ */
+
+ $header = $this->_getHeader('References');
+ $header->setId('a@[1.2.3.4]');
+ $this->assertEquals('a@[1.2.3.4]', $header->getId());
+ $this->assertEquals('<a@[1.2.3.4]>', $header->getFieldBody());
+ }
+
+ public function testInvalidIdRightThrowsException()
+ {
+ try {
+ $header = $this->_getHeader('References');
+ $header->setId('a@b c d');
+ $this->fail(
+ 'Exception should be thrown since "b c d" is not valid id-right.'
+ );
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testMissingAtSignThrowsException()
+ {
+ /* -- RFC 2822, 3.6.4.
+ msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]
+ */
+
+ try {
+ $header = $this->_getHeader('References');
+ $header->setId('abc');
+ $this->fail(
+ 'Exception should be thrown since "abc" is does not contain @.'
+ );
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testSetBodyModel()
+ {
+ $header = $this->_getHeader('Message-ID');
+ $header->setFieldBodyModel('a@b');
+ $this->assertEquals(array('a@b'), $header->getIds());
+ }
+
+ public function testGetBodyModel()
+ {
+ $header = $this->_getHeader('Message-ID');
+ $header->setId('a@b');
+ $this->assertEquals(array('a@b'), $header->getFieldBodyModel());
+ }
+
+ public function testStringValue()
+ {
+ $header = $this->_getHeader('References');
+ $header->setIds(array('a@b', 'x@y'));
+ $this->assertEquals('References: <a@b> <x@y>'."\r\n", $header->toString());
+ }
+
+ private function _getHeader($name)
+ {
+ return new Swift_Mime_Headers_IdentificationHeader($name, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php
new file mode 100644
index 0000000..0713ff4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php
@@ -0,0 +1,327 @@
+<?php
+
+class Swift_Mime_Headers_MailboxHeaderTest extends \SwiftMailerTestCase
+{
+ /* -- RFC 2822, 3.6.2 for all tests.
+ */
+
+ private $_charset = 'utf-8';
+
+ public function testTypeIsMailboxHeader()
+ {
+ $header = $this->_getHeader('To', $this->_getEncoder('Q', true));
+ $this->assertEquals(Swift_Mime_Header::TYPE_MAILBOX, $header->getFieldType());
+ }
+
+ public function testMailboxIsSetForAddress()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses('chris@swiftmailer.org');
+ $this->assertEquals(array('chris@swiftmailer.org'),
+ $header->getNameAddressStrings()
+ );
+ }
+
+ public function testMailboxIsRenderedForNameAddress()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn'));
+ $this->assertEquals(
+ array('Chris Corbyn <chris@swiftmailer.org>'), $header->getNameAddressStrings()
+ );
+ }
+
+ public function testAddressCanBeReturnedForAddress()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses('chris@swiftmailer.org');
+ $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses());
+ }
+
+ public function testAddressCanBeReturnedForNameAddress()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn'));
+ $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses());
+ }
+
+ public function testQuotesInNameAreQuoted()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn, "DHE"',
+ ));
+ $this->assertEquals(
+ array('"Chris Corbyn, \"DHE\"" <chris@swiftmailer.org>'),
+ $header->getNameAddressStrings()
+ );
+ }
+
+ public function testEscapeCharsInNameAreQuoted()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn, \\escaped\\',
+ ));
+ $this->assertEquals(
+ array('"Chris Corbyn, \\\\escaped\\\\" <chris@swiftmailer.org>'),
+ $header->getNameAddressStrings()
+ );
+ }
+
+ public function testGetMailboxesReturnsNameValuePairs()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn, DHE',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org' => 'Chris Corbyn, DHE'), $header->getNameAddresses()
+ );
+ }
+
+ public function testMultipleAddressesCanBeSetAndFetched()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses(array(
+ 'chris@swiftmailer.org', 'mark@swiftmailer.org',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
+ $header->getAddresses()
+ );
+ }
+
+ public function testMultipleAddressesAsMailboxes()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses(array(
+ 'chris@swiftmailer.org', 'mark@swiftmailer.org',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null),
+ $header->getNameAddresses()
+ );
+ }
+
+ public function testMultipleAddressesAsMailboxStrings()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses(array(
+ 'chris@swiftmailer.org', 'mark@swiftmailer.org',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
+ $header->getNameAddressStrings()
+ );
+ }
+
+ public function testMultipleNamedMailboxesReturnsMultipleAddresses()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
+ $header->getAddresses()
+ );
+ }
+
+ public function testMultipleNamedMailboxesReturnsMultipleMailboxes()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ),
+ $header->getNameAddresses()
+ );
+ }
+
+ public function testMultipleMailboxesProducesMultipleMailboxStrings()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(array(
+ 'Chris Corbyn <chris@swiftmailer.org>',
+ 'Mark Corbyn <mark@swiftmailer.org>',
+ ),
+ $header->getNameAddressStrings()
+ );
+ }
+
+ public function testSetAddressesOverwritesAnyMailboxes()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(
+ array('chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn', ),
+ $header->getNameAddresses()
+ );
+ $this->assertEquals(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
+ $header->getAddresses()
+ );
+
+ $header->setAddresses(array('chris@swiftmailer.org', 'mark@swiftmailer.org'));
+
+ $this->assertEquals(
+ array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null),
+ $header->getNameAddresses()
+ );
+ $this->assertEquals(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
+ $header->getAddresses()
+ );
+ }
+
+ public function testNameIsEncodedIfNonAscii()
+ {
+ $name = 'C'.pack('C', 0x8F).'rbyn';
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('C=8Frbyn');
+
+ $header = $this->_getHeader('From', $encoder);
+ $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name));
+
+ $addresses = $header->getNameAddressStrings();
+ $this->assertEquals(
+ 'Chris =?'.$this->_charset.'?Q?C=8Frbyn?= <chris@swiftmailer.org>',
+ array_shift($addresses)
+ );
+ }
+
+ public function testEncodingLineLengthCalculations()
+ {
+ /* -- RFC 2047, 2.
+ An 'encoded-word' may not be more than 75 characters long, including
+ 'charset', 'encoding', 'encoded-text', and delimiters.
+ */
+
+ $name = 'C'.pack('C', 0x8F).'rbyn';
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('C=8Frbyn');
+
+ $header = $this->_getHeader('From', $encoder);
+ $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name));
+
+ $header->getNameAddressStrings();
+ }
+
+ public function testGetValueReturnsMailboxStringValue()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ ));
+ $this->assertEquals(
+ 'Chris Corbyn <chris@swiftmailer.org>', $header->getFieldBody()
+ );
+ }
+
+ public function testGetValueReturnsMailboxStringValueForMultipleMailboxes()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(
+ 'Chris Corbyn <chris@swiftmailer.org>, Mark Corbyn <mark@swiftmailer.org>',
+ $header->getFieldBody()
+ );
+ }
+
+ public function testRemoveAddressesWithSingleValue()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $header->removeAddresses('chris@swiftmailer.org');
+ $this->assertEquals(array('mark@swiftmailer.org'),
+ $header->getAddresses()
+ );
+ }
+
+ public function testRemoveAddressesWithList()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $header->removeAddresses(
+ array('chris@swiftmailer.org', 'mark@swiftmailer.org')
+ );
+ $this->assertEquals(array(), $header->getAddresses());
+ }
+
+ public function testSetBodyModel()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setFieldBodyModel('chris@swiftmailer.org');
+ $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getNameAddresses());
+ }
+
+ public function testGetBodyModel()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setAddresses(array('chris@swiftmailer.org'));
+ $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getFieldBodyModel());
+ }
+
+ public function testToString()
+ {
+ $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
+ $header->setNameAddresses(array(
+ 'chris@swiftmailer.org' => 'Chris Corbyn',
+ 'mark@swiftmailer.org' => 'Mark Corbyn',
+ ));
+ $this->assertEquals(
+ 'From: Chris Corbyn <chris@swiftmailer.org>, '.
+ 'Mark Corbyn <mark@swiftmailer.org>'."\r\n",
+ $header->toString()
+ );
+ }
+
+ private function _getHeader($name, $encoder)
+ {
+ $header = new Swift_Mime_Headers_MailboxHeader($name, $encoder, new Swift_Mime_Grammar());
+ $header->setCharset($this->_charset);
+
+ return $header;
+ }
+
+ private function _getEncoder($type, $stub = false)
+ {
+ $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
+ $encoder->shouldReceive('getName')
+ ->zeroOrMoreTimes()
+ ->andReturn($type);
+
+ return $encoder;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php
new file mode 100644
index 0000000..cd027cc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php
@@ -0,0 +1,398 @@
+<?php
+
+class Swift_Mime_Headers_ParameterizedHeaderTest extends \SwiftMailerTestCase
+{
+ private $_charset = 'utf-8';
+ private $_lang = 'en-us';
+
+ public function testTypeIsParameterizedHeader()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $this->assertEquals(Swift_Mime_Header::TYPE_PARAMETERIZED, $header->getFieldType());
+ }
+
+ public function testValueIsReturnedVerbatim()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setValue('text/plain');
+ $this->assertEquals('text/plain', $header->getValue());
+ }
+
+ public function testParametersAreAppended()
+ {
+ /* -- RFC 2045, 5.1
+ parameter := attribute "=" value
+
+ attribute := token
+ ; Matching of attributes
+ ; is ALWAYS case-insensitive.
+
+ value := token / quoted-string
+
+ token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
+ or tspecials>
+
+ tspecials := "(" / ")" / "<" / ">" / "@" /
+ "," / ";" / ":" / "\" / <">
+ "/" / "[" / "]" / "?" / "="
+ ; Must be in quoted-string,
+ ; to use within parameter values
+ */
+
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setValue('text/plain');
+ $header->setParameters(array('charset' => 'utf-8'));
+ $this->assertEquals('text/plain; charset=utf-8', $header->getFieldBody());
+ }
+
+ public function testSpaceInParamResultsInQuotedString()
+ {
+ $header = $this->_getHeader('Content-Disposition',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setValue('attachment');
+ $header->setParameters(array('filename' => 'my file.txt'));
+ $this->assertEquals('attachment; filename="my file.txt"',
+ $header->getFieldBody()
+ );
+ }
+
+ public function testLongParamsAreBrokenIntoMultipleAttributeStrings()
+ {
+ /* -- RFC 2231, 3.
+ The asterisk character ("*") followed
+ by a decimal count is employed to indicate that multiple parameters
+ are being used to encapsulate a single parameter value. The count
+ starts at 0 and increments by 1 for each subsequent section of the
+ parameter value. Decimal values are used and neither leading zeroes
+ nor gaps in the sequence are allowed.
+
+ The original parameter value is recovered by concatenating the
+ various sections of the parameter, in order. For example, the
+ content-type field
+
+ Content-Type: message/external-body; access-type=URL;
+ URL*0="ftp://";
+ URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"
+
+ is semantically identical to
+
+ Content-Type: message/external-body; access-type=URL;
+ URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"
+
+ Note that quotes around parameter values are part of the value
+ syntax; they are NOT part of the value itself. Furthermore, it is
+ explicitly permitted to have a mixture of quoted and unquoted
+ continuation fields.
+ */
+
+ $value = str_repeat('a', 180);
+
+ $encoder = $this->_getParameterEncoder();
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), 63, \Mockery::any())
+ ->andReturn(str_repeat('a', 63)."\r\n".
+ str_repeat('a', 63)."\r\n".str_repeat('a', 54));
+
+ $header = $this->_getHeader('Content-Disposition',
+ $this->_getHeaderEncoder('Q', true), $encoder
+ );
+ $header->setValue('attachment');
+ $header->setParameters(array('filename' => $value));
+ $header->setMaxLineLength(78);
+ $this->assertEquals(
+ 'attachment; '.
+ 'filename*0*=utf-8\'\''.str_repeat('a', 63).";\r\n ".
+ 'filename*1*='.str_repeat('a', 63).";\r\n ".
+ 'filename*2*='.str_repeat('a', 54),
+ $header->getFieldBody()
+ );
+ }
+
+ public function testEncodedParamDataIncludesCharsetAndLanguage()
+ {
+ /* -- RFC 2231, 4.
+ Asterisks ("*") are reused to provide the indicator that language and
+ character set information is present and encoding is being used. A
+ single quote ("'") is used to delimit the character set and language
+ information at the beginning of the parameter value. Percent signs
+ ("%") are used as the encoding flag, which agrees with RFC 2047.
+
+ Specifically, an asterisk at the end of a parameter name acts as an
+ indicator that character set and language information may appear at
+ the beginning of the parameter value. A single quote is used to
+ separate the character set, language, and actual value information in
+ the parameter value string, and an percent sign is used to flag
+ octets encoded in hexadecimal. For example:
+
+ Content-Type: application/x-stuff;
+ title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A
+
+ Note that it is perfectly permissible to leave either the character
+ set or language field blank. Note also that the single quote
+ delimiters MUST be present even when one of the field values is
+ omitted.
+ */
+
+ $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 10);
+
+ $encoder = $this->_getParameterEncoder();
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, 12, 62, \Mockery::any())
+ ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 10));
+
+ $header = $this->_getHeader('Content-Disposition',
+ $this->_getHeaderEncoder('Q', true), $encoder
+ );
+ $header->setValue('attachment');
+ $header->setParameters(array('filename' => $value));
+ $header->setMaxLineLength(78);
+ $header->setLanguage($this->_lang);
+ $this->assertEquals(
+ 'attachment; filename*='.$this->_charset."'".$this->_lang."'".
+ str_repeat('a', 20).'%8F'.str_repeat('a', 10),
+ $header->getFieldBody()
+ );
+ }
+
+ public function testMultipleEncodedParamLinesAreFormattedCorrectly()
+ {
+ /* -- RFC 2231, 4.1.
+ Character set and language information may be combined with the
+ parameter continuation mechanism. For example:
+
+ Content-Type: application/x-stuff
+ title*0*=us-ascii'en'This%20is%20even%20more%20
+ title*1*=%2A%2A%2Afun%2A%2A%2A%20
+ title*2="isn't it!"
+
+ Note that:
+
+ (1) Language and character set information only appear at
+ the beginning of a given parameter value.
+
+ (2) Continuations do not provide a facility for using more
+ than one character set or language in the same
+ parameter value.
+
+ (3) A value presented using multiple continuations may
+ contain a mixture of encoded and unencoded segments.
+
+ (4) The first segment of a continuation MUST be encoded if
+ language and character set information are given.
+
+ (5) If the first segment of a continued parameter value is
+ encoded the language and character set field delimiters
+ MUST be present even when the fields are left blank.
+ */
+
+ $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 60);
+
+ $encoder = $this->_getParameterEncoder();
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, 12, 62, \Mockery::any())
+ ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 28)."\r\n".
+ str_repeat('a', 32));
+
+ $header = $this->_getHeader('Content-Disposition',
+ $this->_getHeaderEncoder('Q', true), $encoder
+ );
+ $header->setValue('attachment');
+ $header->setParameters(array('filename' => $value));
+ $header->setMaxLineLength(78);
+ $header->setLanguage($this->_lang);
+ $this->assertEquals(
+ 'attachment; filename*0*='.$this->_charset."'".$this->_lang."'".
+ str_repeat('a', 20).'%8F'.str_repeat('a', 28).";\r\n ".
+ 'filename*1*='.str_repeat('a', 32),
+ $header->getFieldBody()
+ );
+ }
+
+ public function testToString()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setValue('text/html');
+ $header->setParameters(array('charset' => 'utf-8'));
+ $this->assertEquals('Content-Type: text/html; charset=utf-8'."\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testValueCanBeEncodedIfNonAscii()
+ {
+ $value = 'fo'.pack('C', 0x8F).'bar';
+
+ $encoder = $this->_getHeaderEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo=8Fbar');
+
+ $header = $this->_getHeader('X-Foo', $encoder, $this->_getParameterEncoder(true));
+ $header->setValue($value);
+ $header->setParameters(array('lookslike' => 'foobar'));
+ $this->assertEquals('X-Foo: =?utf-8?Q?fo=8Fbar?=; lookslike=foobar'."\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testValueAndParamCanBeEncodedIfNonAscii()
+ {
+ $value = 'fo'.pack('C', 0x8F).'bar';
+
+ $encoder = $this->_getHeaderEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo=8Fbar');
+
+ $paramEncoder = $this->_getParameterEncoder();
+ $paramEncoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo%8Fbar');
+
+ $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder);
+ $header->setValue($value);
+ $header->setParameters(array('says' => $value));
+ $this->assertEquals("X-Foo: =?utf-8?Q?fo=8Fbar?=; says*=utf-8''fo%8Fbar\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet()
+ {
+ $value = 'fo'.pack('C', 0x8F).'bar';
+
+ $encoder = $this->_getHeaderEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo=8Fbar');
+
+ $header = $this->_getHeader('X-Foo', $encoder, null);
+ $header->setValue('bar');
+ $header->setParameters(array('says' => $value));
+ $this->assertEquals("X-Foo: bar; says=\"=?utf-8?Q?fo=8Fbar?=\"\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testLanguageInformationAppearsInEncodedWords()
+ {
+ /* -- RFC 2231, 5.
+ 5. Language specification in Encoded Words
+
+ RFC 2047 provides support for non-US-ASCII character sets in RFC 822
+ message header comments, phrases, and any unstructured text field.
+ This is done by defining an encoded word construct which can appear
+ in any of these places. Given that these are fields intended for
+ display, it is sometimes necessary to associate language information
+ with encoded words as well as just the character set. This
+ specification extends the definition of an encoded word to allow the
+ inclusion of such information. This is simply done by suffixing the
+ character set specification with an asterisk followed by the language
+ tag. For example:
+
+ From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
+ */
+
+ $value = 'fo'.pack('C', 0x8F).'bar';
+
+ $encoder = $this->_getHeaderEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo=8Fbar');
+
+ $paramEncoder = $this->_getParameterEncoder();
+ $paramEncoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo%8Fbar');
+
+ $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder);
+ $header->setLanguage('en');
+ $header->setValue($value);
+ $header->setParameters(array('says' => $value));
+ $this->assertEquals("X-Foo: =?utf-8*en?Q?fo=8Fbar?=; says*=utf-8'en'fo%8Fbar\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testSetBodyModel()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setFieldBodyModel('text/html');
+ $this->assertEquals('text/html', $header->getValue());
+ }
+
+ public function testGetBodyModel()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setValue('text/plain');
+ $this->assertEquals('text/plain', $header->getFieldBodyModel());
+ }
+
+ public function testSetParameter()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes'));
+ $header->setParameter('delsp', 'no');
+ $this->assertEquals(array('charset' => 'utf-8', 'delsp' => 'no'),
+ $header->getParameters()
+ );
+ }
+
+ public function testGetParameter()
+ {
+ $header = $this->_getHeader('Content-Type',
+ $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
+ );
+ $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes'));
+ $this->assertEquals('utf-8', $header->getParameter('charset'));
+ }
+
+ private function _getHeader($name, $encoder, $paramEncoder)
+ {
+ $header = new Swift_Mime_Headers_ParameterizedHeader($name, $encoder,
+ $paramEncoder, new Swift_Mime_Grammar()
+ );
+ $header->setCharset($this->_charset);
+
+ return $header;
+ }
+
+ private function _getHeaderEncoder($type, $stub = false)
+ {
+ $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
+ $encoder->shouldReceive('getName')
+ ->zeroOrMoreTimes()
+ ->andReturn($type);
+
+ return $encoder;
+ }
+
+ private function _getParameterEncoder($stub = false)
+ {
+ return $this->getMockery('Swift_Encoder')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php
new file mode 100644
index 0000000..a9f35e9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php
@@ -0,0 +1,77 @@
+<?php
+
+class Swift_Mime_Headers_PathHeaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testTypeIsPathHeader()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $this->assertEquals(Swift_Mime_Header::TYPE_PATH, $header->getFieldType());
+ }
+
+ public function testSingleAddressCanBeSetAndFetched()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('chris@swiftmailer.org');
+ $this->assertEquals('chris@swiftmailer.org', $header->getAddress());
+ }
+
+ public function testAddressMustComplyWithRfc2822()
+ {
+ try {
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('chr is@swiftmailer.org');
+ $this->fail('Addresses not valid according to RFC 2822 addr-spec grammar must be rejected.');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testValueIsAngleAddrWithValidAddress()
+ {
+ /* -- RFC 2822, 3.6.7.
+
+ return = "Return-Path:" path CRLF
+
+ path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
+ obs-path
+ */
+
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('chris@swiftmailer.org');
+ $this->assertEquals('<chris@swiftmailer.org>', $header->getFieldBody());
+ }
+
+ public function testValueIsEmptyAngleBracketsIfEmptyAddressSet()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('');
+ $this->assertEquals('<>', $header->getFieldBody());
+ }
+
+ public function testSetBodyModel()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $header->setFieldBodyModel('foo@bar.tld');
+ $this->assertEquals('foo@bar.tld', $header->getAddress());
+ }
+
+ public function testGetBodyModel()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('foo@bar.tld');
+ $this->assertEquals('foo@bar.tld', $header->getFieldBodyModel());
+ }
+
+ public function testToString()
+ {
+ $header = $this->_getHeader('Return-Path');
+ $header->setAddress('chris@swiftmailer.org');
+ $this->assertEquals('Return-Path: <chris@swiftmailer.org>'."\r\n",
+ $header->toString()
+ );
+ }
+
+ private function _getHeader($name)
+ {
+ return new Swift_Mime_Headers_PathHeader($name, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php
new file mode 100644
index 0000000..2e1dc8c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php
@@ -0,0 +1,355 @@
+<?php
+
+class Swift_Mime_Headers_UnstructuredHeaderTest extends \SwiftMailerTestCase
+{
+ private $_charset = 'utf-8';
+
+ public function testTypeIsTextHeader()
+ {
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $this->assertEquals(Swift_Mime_Header::TYPE_TEXT, $header->getFieldType());
+ }
+
+ public function testGetNameReturnsNameVerbatim()
+ {
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $this->assertEquals('Subject', $header->getFieldName());
+ }
+
+ public function testGetValueReturnsValueVerbatim()
+ {
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $header->setValue('Test');
+ $this->assertEquals('Test', $header->getValue());
+ }
+
+ public function testBasicStructureIsKeyValuePair()
+ {
+ /* -- RFC 2822, 2.2
+ Header fields are lines composed of a field name, followed by a colon
+ (":"), followed by a field body, and terminated by CRLF.
+ */
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $header->setValue('Test');
+ $this->assertEquals('Subject: Test'."\r\n", $header->toString());
+ }
+
+ public function testLongHeadersAreFoldedAtWordBoundary()
+ {
+ /* -- RFC 2822, 2.2.3
+ Each header field is logically a single line of characters comprising
+ the field name, the colon, and the field body. For convenience
+ however, and to deal with the 998/78 character limitations per line,
+ the field body portion of a header field can be split into a multiple
+ line representation; this is called "folding". The general rule is
+ that wherever this standard allows for folding white space (not
+ simply WSP characters), a CRLF may be inserted before any WSP.
+ */
+
+ $value = 'The quick brown fox jumped over the fence, he was a very very '.
+ 'scary brown fox with a bushy tail';
+ $header = $this->_getHeader('X-Custom-Header',
+ $this->_getEncoder('Q', true)
+ );
+ $header->setValue($value);
+ $header->setMaxLineLength(78); //A safe [RFC 2822, 2.2.3] default
+ /*
+ X-Custom-Header: The quick brown fox jumped over the fence, he was a very very
+ scary brown fox with a bushy tail
+ */
+ $this->assertEquals(
+ 'X-Custom-Header: The quick brown fox jumped over the fence, he was a'.
+ ' very very'."\r\n".//Folding
+ ' scary brown fox with a bushy tail'."\r\n",
+ $header->toString(), '%s: The header should have been folded at 78th char'
+ );
+ }
+
+ public function testPrintableAsciiOnlyAppearsInHeaders()
+ {
+ /* -- RFC 2822, 2.2.
+ A field name MUST be composed of printable US-ASCII characters (i.e.,
+ characters that have values between 33 and 126, inclusive), except
+ colon. A field body may be composed of any US-ASCII characters,
+ except for CR and LF.
+ */
+
+ $nonAsciiChar = pack('C', 0x8F);
+ $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true));
+ $header->setValue($nonAsciiChar);
+ $this->assertRegExp(
+ '~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+\r\n$~s',
+ $header->toString()
+ );
+ }
+
+ public function testEncodedWordsFollowGeneralStructure()
+ {
+ /* -- RFC 2047, 1.
+ Generally, an "encoded-word" is a sequence of printable ASCII
+ characters that begins with "=?", ends with "?=", and has two "?"s in
+ between.
+ */
+
+ $nonAsciiChar = pack('C', 0x8F);
+ $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true));
+ $header->setValue($nonAsciiChar);
+ $this->assertRegExp(
+ '~^X-Test: \=?.*?\?.*?\?.*?\?=\r\n$~s',
+ $header->toString()
+ );
+ }
+
+ public function testEncodedWordIncludesCharsetAndEncodingMethodAndText()
+ {
+ /* -- RFC 2047, 2.
+ An 'encoded-word' is defined by the following ABNF grammar. The
+ notation of RFC 822 is used, with the exception that white space
+ characters MUST NOT appear between components of an 'encoded-word'.
+
+ encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+ */
+
+ $nonAsciiChar = pack('C', 0x8F);
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('=8F');
+
+ $header = $this->_getHeader('X-Test', $encoder);
+ $header->setValue($nonAsciiChar);
+ $this->assertEquals(
+ 'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testEncodedWordsAreUsedToEncodedNonPrintableAscii()
+ {
+ //SPACE and TAB permitted
+ $nonPrintableBytes = array_merge(
+ range(0x00, 0x08), range(0x10, 0x19), array(0x7F)
+ );
+
+ foreach ($nonPrintableBytes as $byte) {
+ $char = pack('C', $byte);
+ $encodedChar = sprintf('=%02X', $byte);
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn($encodedChar);
+
+ $header = $this->_getHeader('X-A', $encoder);
+ $header->setValue($char);
+
+ $this->assertEquals(
+ 'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n",
+ $header->toString(), '%s: Non-printable ascii should be encoded'
+ );
+ }
+ }
+
+ public function testEncodedWordsAreUsedToEncode8BitOctets()
+ {
+ $_8BitBytes = range(0x80, 0xFF);
+
+ foreach ($_8BitBytes as $byte) {
+ $char = pack('C', $byte);
+ $encodedChar = sprintf('=%02X', $byte);
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn($encodedChar);
+
+ $header = $this->_getHeader('X-A', $encoder);
+ $header->setValue($char);
+
+ $this->assertEquals(
+ 'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n",
+ $header->toString(), '%s: 8-bit octets should be encoded'
+ );
+ }
+ }
+
+ public function testEncodedWordsAreNoMoreThan75CharsPerLine()
+ {
+ /* -- RFC 2047, 2.
+ An 'encoded-word' may not be more than 75 characters long, including
+ 'charset', 'encoding', 'encoded-text', and delimiters.
+
+ ... SNIP ...
+
+ While there is no limit to the length of a multiple-line header
+ field, each line of a header field that contains one or more
+ 'encoded-word's is limited to 76 characters.
+ */
+
+ $nonAsciiChar = pack('C', 0x8F);
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('=8F');
+ //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
+ //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63
+
+ //* X-Test: is 8 chars
+ $header = $this->_getHeader('X-Test', $encoder);
+ $header->setValue($nonAsciiChar);
+
+ $this->assertEquals(
+ 'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testFWSPIsUsedWhenEncoderReturnsMultipleLines()
+ {
+ /* --RFC 2047, 2.
+ If it is desirable to encode more text than will fit in an 'encoded-word' of
+ 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may
+ be used.
+ */
+
+ //Note the Mock does NOT return 8F encoded, the 8F merely triggers
+ // encoding for the sake of testing
+ $nonAsciiChar = pack('C', 0x8F);
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($nonAsciiChar, 8, 63, \Mockery::any())
+ ->andReturn('line_one_here'."\r\n".'line_two_here');
+
+ //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
+ //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63
+
+ //* X-Test: is 8 chars
+ $header = $this->_getHeader('X-Test', $encoder);
+ $header->setValue($nonAsciiChar);
+
+ $this->assertEquals(
+ 'X-Test: =?'.$this->_charset.'?Q?line_one_here?='."\r\n".
+ ' =?'.$this->_charset.'?Q?line_two_here?='."\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testAdjacentWordsAreEncodedTogether()
+ {
+ /* -- RFC 2047, 5 (1)
+ Ordinary ASCII text and 'encoded-word's may appear together in the
+ same header field. However, an 'encoded-word' that appears in a
+ header field defined as '*text' MUST be separated from any adjacent
+ 'encoded-word' or 'text' by 'linear-white-space'.
+
+ -- RFC 2047, 2.
+ IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's
+ by an RFC 822 parser. As a consequence, unencoded white space
+ characters (such as SPACE and HTAB) are FORBIDDEN within an
+ 'encoded-word'.
+ */
+
+ //It would be valid to encode all words needed, however it's probably
+ // easiest to encode the longest amount required at a time
+
+ $word = 'w'.pack('C', 0x8F).'rd';
+ $text = 'start '.$word.' '.$word.' then end '.$word;
+ // 'start', ' word word', ' and end', ' word'
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($word.' '.$word, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('w=8Frd_w=8Frd');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($word, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('w=8Frd');
+
+ $header = $this->_getHeader('X-Test', $encoder);
+ $header->setValue($text);
+
+ $headerString = $header->toString();
+
+ $this->assertEquals('X-Test: start =?'.$this->_charset.'?Q?'.
+ 'w=8Frd_w=8Frd?= then end =?'.$this->_charset.'?Q?'.
+ 'w=8Frd?='."\r\n", $headerString,
+ '%s: Adjacent encoded words should appear grouped with WSP encoded'
+ );
+ }
+
+ public function testLanguageInformationAppearsInEncodedWords()
+ {
+ /* -- RFC 2231, 5.
+ 5. Language specification in Encoded Words
+
+ RFC 2047 provides support for non-US-ASCII character sets in RFC 822
+ message header comments, phrases, and any unstructured text field.
+ This is done by defining an encoded word construct which can appear
+ in any of these places. Given that these are fields intended for
+ display, it is sometimes necessary to associate language information
+ with encoded words as well as just the character set. This
+ specification extends the definition of an encoded word to allow the
+ inclusion of such information. This is simply done by suffixing the
+ character set specification with an asterisk followed by the language
+ tag. For example:
+
+ From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
+ */
+
+ $value = 'fo'.pack('C', 0x8F).'bar';
+
+ $encoder = $this->_getEncoder('Q');
+ $encoder->shouldReceive('encodeString')
+ ->once()
+ ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn('fo=8Fbar');
+
+ $header = $this->_getHeader('Subject', $encoder);
+ $header->setLanguage('en');
+ $header->setValue($value);
+ $this->assertEquals("Subject: =?utf-8*en?Q?fo=8Fbar?=\r\n",
+ $header->toString()
+ );
+ }
+
+ public function testSetBodyModel()
+ {
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $header->setFieldBodyModel('test');
+ $this->assertEquals('test', $header->getValue());
+ }
+
+ public function testGetBodyModel()
+ {
+ $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
+ $header->setValue('test');
+ $this->assertEquals('test', $header->getFieldBodyModel());
+ }
+
+ private function _getHeader($name, $encoder)
+ {
+ $header = new Swift_Mime_Headers_UnstructuredHeader($name, $encoder, new Swift_Mime_Grammar());
+ $header->setCharset($this->_charset);
+
+ return $header;
+ }
+
+ private function _getEncoder($type, $stub = false)
+ {
+ $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
+ $encoder->shouldReceive('getName')
+ ->zeroOrMoreTimes()
+ ->andReturn($type);
+
+ return $encoder;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php
new file mode 100644
index 0000000..738ac68
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php
@@ -0,0 +1,231 @@
+<?php
+
+class Swift_Mime_MimePartTest extends Swift_Mime_AbstractMimeEntityTest
+{
+ public function testNestingLevelIsSubpart()
+ {
+ $part = $this->_createMimePart($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(
+ Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, $part->getNestingLevel()
+ );
+ }
+
+ public function testCharsetIsReturnedFromHeader()
+ {
+ /* -- RFC 2046, 4.1.2.
+ A critical parameter that may be specified in the Content-Type field
+ for "text/plain" data is the character set. This is specified with a
+ "charset" parameter, as in:
+
+ Content-type: text/plain; charset=iso-8859-1
+
+ Unlike some other parameter values, the values of the charset
+ parameter are NOT case sensitive. The default character set, which
+ must be assumed in the absence of a charset parameter, is US-ASCII.
+ */
+
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('charset' => 'iso-8859-1')
+ );
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('iso-8859-1', $part->getCharset());
+ }
+
+ public function testCharsetIsSetInHeader()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('charset' => 'iso-8859-1'), false
+ );
+ $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->setCharset('utf-8');
+ }
+
+ public function testCharsetIsSetInHeaderIfPassedToSetBody()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('charset' => 'iso-8859-1'), false
+ );
+ $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->setBody('', 'text/plian', 'utf-8');
+ }
+
+ public function testSettingCharsetNotifiesEncoder()
+ {
+ $encoder = $this->_createEncoder('quoted-printable', false);
+ $encoder->expects($this->once())
+ ->method('charsetChanged')
+ ->with('utf-8');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(),
+ $encoder, $this->_createCache()
+ );
+ $part->setCharset('utf-8');
+ }
+
+ public function testSettingCharsetNotifiesHeaders()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('charsetChanged')
+ ->zeroOrMoreTimes()
+ ->with('utf-8');
+
+ $part = $this->_createMimePart($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $part->setCharset('utf-8');
+ }
+
+ public function testSettingCharsetNotifiesChildren()
+ {
+ $child = $this->_createChild(0, '', false);
+ $child->shouldReceive('charsetChanged')
+ ->once()
+ ->with('windows-874');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->setChildren(array($child));
+ $part->setCharset('windows-874');
+ }
+
+ public function testCharsetChangeUpdatesCharset()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('charset' => 'iso-8859-1'), false
+ );
+ $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->charsetChanged('utf-8');
+ }
+
+ public function testSettingCharsetClearsCache()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+ $cache = $this->_createCache(false);
+
+ $entity = $this->_createEntity($headers, $this->_createEncoder(),
+ $cache
+ );
+
+ $entity->setBody("blah\r\nblah!");
+ $entity->toString();
+
+ // Initialize the expectation here because we only care about what happens in setCharset()
+ $cache->shouldReceive('clearKey')
+ ->once()
+ ->with(\Mockery::any(), 'body');
+
+ $entity->setCharset('iso-2022');
+ }
+
+ public function testFormatIsReturnedFromHeader()
+ {
+ /* -- RFC 3676.
+ */
+
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('format' => 'flowed')
+ );
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('flowed', $part->getFormat());
+ }
+
+ public function testFormatIsSetInHeader()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $cType->shouldReceive('setParameter')->once()->with('format', 'fixed');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->setFormat('fixed');
+ }
+
+ public function testDelSpIsReturnedFromHeader()
+ {
+ /* -- RFC 3676.
+ */
+
+ $cType = $this->_createHeader('Content-Type', 'text/plain',
+ array('delsp' => 'no')
+ );
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertFalse($part->getDelSp());
+ }
+
+ public function testDelSpIsSetInHeader()
+ {
+ $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
+ $cType->shouldReceive('setParameter')->once()->with('delsp', 'yes');
+
+ $part = $this->_createMimePart($this->_createHeaderSet(array(
+ 'Content-Type' => $cType, )),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $part->setDelSp(true);
+ }
+
+ public function testFluidInterface()
+ {
+ $part = $this->_createMimePart($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $this->assertSame($part,
+ $part
+ ->setContentType('text/plain')
+ ->setEncoder($this->_createEncoder())
+ ->setId('foo@bar')
+ ->setDescription('my description')
+ ->setMaxLineLength(998)
+ ->setBody('xx')
+ ->setBoundary('xyz')
+ ->setChildren(array())
+ ->setCharset('utf-8')
+ ->setFormat('flowed')
+ ->setDelSp(true)
+ );
+ }
+
+ //abstract
+ protected function _createEntity($headers, $encoder, $cache)
+ {
+ return $this->_createMimePart($headers, $encoder, $cache);
+ }
+
+ protected function _createMimePart($headers, $encoder, $cache)
+ {
+ return new Swift_Mime_MimePart($headers, $encoder, $cache, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php
new file mode 100644
index 0000000..6a87abf
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php
@@ -0,0 +1,166 @@
+<?php
+
+class Swift_Mime_SimpleHeaderFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ private $_factory;
+
+ protected function setUp()
+ {
+ $this->_factory = $this->_createFactory();
+ }
+
+ public function testMailboxHeaderIsCorrectType()
+ {
+ $header = $this->_factory->createMailboxHeader('X-Foo');
+ $this->assertInstanceOf('Swift_Mime_Headers_MailboxHeader', $header);
+ }
+
+ public function testMailboxHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createMailboxHeader('X-Foo');
+ $this->assertEquals('X-Foo', $header->getFieldName());
+ }
+
+ public function testMailboxHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createMailboxHeader('X-Foo',
+ array('foo@bar' => 'FooBar')
+ );
+ $this->assertEquals(array('foo@bar' => 'FooBar'), $header->getFieldBodyModel());
+ }
+
+ public function testDateHeaderHasCorrectType()
+ {
+ $header = $this->_factory->createDateHeader('X-Date');
+ $this->assertInstanceOf('Swift_Mime_Headers_DateHeader', $header);
+ }
+
+ public function testDateHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createDateHeader('X-Date');
+ $this->assertEquals('X-Date', $header->getFieldName());
+ }
+
+ public function testDateHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createDateHeader('X-Date', 123);
+ $this->assertEquals(123, $header->getFieldBodyModel());
+ }
+
+ public function testTextHeaderHasCorrectType()
+ {
+ $header = $this->_factory->createTextHeader('X-Foo');
+ $this->assertInstanceOf('Swift_Mime_Headers_UnstructuredHeader', $header);
+ }
+
+ public function testTextHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createTextHeader('X-Foo');
+ $this->assertEquals('X-Foo', $header->getFieldName());
+ }
+
+ public function testTextHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createTextHeader('X-Foo', 'bar');
+ $this->assertEquals('bar', $header->getFieldBodyModel());
+ }
+
+ public function testParameterizedHeaderHasCorrectType()
+ {
+ $header = $this->_factory->createParameterizedHeader('X-Foo');
+ $this->assertInstanceOf('Swift_Mime_Headers_ParameterizedHeader', $header);
+ }
+
+ public function testParameterizedHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createParameterizedHeader('X-Foo');
+ $this->assertEquals('X-Foo', $header->getFieldName());
+ }
+
+ public function testParameterizedHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar');
+ $this->assertEquals('bar', $header->getFieldBodyModel());
+ }
+
+ public function testParameterizedHeaderHasCorrectParams()
+ {
+ $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar',
+ array('zip' => 'button')
+ );
+ $this->assertEquals(array('zip' => 'button'), $header->getParameters());
+ }
+
+ public function testIdHeaderHasCorrectType()
+ {
+ $header = $this->_factory->createIdHeader('X-ID');
+ $this->assertInstanceOf('Swift_Mime_Headers_IdentificationHeader', $header);
+ }
+
+ public function testIdHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createIdHeader('X-ID');
+ $this->assertEquals('X-ID', $header->getFieldName());
+ }
+
+ public function testIdHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createIdHeader('X-ID', 'xyz@abc');
+ $this->assertEquals(array('xyz@abc'), $header->getFieldBodyModel());
+ }
+
+ public function testPathHeaderHasCorrectType()
+ {
+ $header = $this->_factory->createPathHeader('X-Path');
+ $this->assertInstanceOf('Swift_Mime_Headers_PathHeader', $header);
+ }
+
+ public function testPathHeaderHasCorrectName()
+ {
+ $header = $this->_factory->createPathHeader('X-Path');
+ $this->assertEquals('X-Path', $header->getFieldName());
+ }
+
+ public function testPathHeaderHasCorrectModel()
+ {
+ $header = $this->_factory->createPathHeader('X-Path', 'foo@bar');
+ $this->assertEquals('foo@bar', $header->getFieldBodyModel());
+ }
+
+ public function testCharsetChangeNotificationNotifiesEncoders()
+ {
+ $encoder = $this->_createHeaderEncoder();
+ $encoder->expects($this->once())
+ ->method('charsetChanged')
+ ->with('utf-8');
+ $paramEncoder = $this->_createParamEncoder();
+ $paramEncoder->expects($this->once())
+ ->method('charsetChanged')
+ ->with('utf-8');
+
+ $factory = $this->_createFactory($encoder, $paramEncoder);
+
+ $factory->charsetChanged('utf-8');
+ }
+
+ private function _createFactory($encoder = null, $paramEncoder = null)
+ {
+ return new Swift_Mime_SimpleHeaderFactory(
+ $encoder
+ ? $encoder : $this->_createHeaderEncoder(),
+ $paramEncoder
+ ? $paramEncoder : $this->_createParamEncoder(),
+ new Swift_Mime_Grammar()
+ );
+ }
+
+ private function _createHeaderEncoder()
+ {
+ return $this->getMockBuilder('Swift_Mime_HeaderEncoder')->getMock();
+ }
+
+ private function _createParamEncoder()
+ {
+ return $this->getMockBuilder('Swift_Encoder')->getMock();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php
new file mode 100644
index 0000000..bed1c13
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php
@@ -0,0 +1,737 @@
+<?php
+
+class Swift_Mime_SimpleHeaderSetTest extends \PHPUnit_Framework_TestCase
+{
+ public function testAddMailboxHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createMailboxHeader')
+ ->with('From', array('person@domain' => 'Person'))
+ ->will($this->returnValue($this->_createHeader('From')));
+
+ $set = $this->_createSet($factory);
+ $set->addMailboxHeader('From', array('person@domain' => 'Person'));
+ }
+
+ public function testAddDateHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createDateHeader')
+ ->with('Date', 1234)
+ ->will($this->returnValue($this->_createHeader('Date')));
+
+ $set = $this->_createSet($factory);
+ $set->addDateHeader('Date', 1234);
+ }
+
+ public function testAddTextHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createTextHeader')
+ ->with('Subject', 'some text')
+ ->will($this->returnValue($this->_createHeader('Subject')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Subject', 'some text');
+ }
+
+ public function testAddParameterizedHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createParameterizedHeader')
+ ->with('Content-Type', 'text/plain', array('charset' => 'utf-8'))
+ ->will($this->returnValue($this->_createHeader('Content-Type')));
+
+ $set = $this->_createSet($factory);
+ $set->addParameterizedHeader('Content-Type', 'text/plain',
+ array('charset' => 'utf-8')
+ );
+ }
+
+ public function testAddIdHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ }
+
+ public function testAddPathHeaderDelegatesToFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createPathHeader')
+ ->with('Return-Path', 'some@path')
+ ->will($this->returnValue($this->_createHeader('Return-Path')));
+
+ $set = $this->_createSet($factory);
+ $set->addPathHeader('Return-Path', 'some@path');
+ }
+
+ public function testHasReturnsFalseWhenNoHeaders()
+ {
+ $set = $this->_createSet($this->_createFactory());
+ $this->assertFalse($set->has('Some-Header'));
+ }
+
+ public function testAddedMailboxHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createMailboxHeader')
+ ->with('From', array('person@domain' => 'Person'))
+ ->will($this->returnValue($this->_createHeader('From')));
+
+ $set = $this->_createSet($factory);
+ $set->addMailboxHeader('From', array('person@domain' => 'Person'));
+ $this->assertTrue($set->has('From'));
+ }
+
+ public function testAddedDateHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createDateHeader')
+ ->with('Date', 1234)
+ ->will($this->returnValue($this->_createHeader('Date')));
+
+ $set = $this->_createSet($factory);
+ $set->addDateHeader('Date', 1234);
+ $this->assertTrue($set->has('Date'));
+ }
+
+ public function testAddedTextHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createTextHeader')
+ ->with('Subject', 'some text')
+ ->will($this->returnValue($this->_createHeader('Subject')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Subject', 'some text');
+ $this->assertTrue($set->has('Subject'));
+ }
+
+ public function testAddedParameterizedHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createParameterizedHeader')
+ ->with('Content-Type', 'text/plain', array('charset' => 'utf-8'))
+ ->will($this->returnValue($this->_createHeader('Content-Type')));
+
+ $set = $this->_createSet($factory);
+ $set->addParameterizedHeader('Content-Type', 'text/plain',
+ array('charset' => 'utf-8')
+ );
+ $this->assertTrue($set->has('Content-Type'));
+ }
+
+ public function testAddedIdHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertTrue($set->has('Message-ID'));
+ }
+
+ public function testAddedPathHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createPathHeader')
+ ->with('Return-Path', 'some@path')
+ ->will($this->returnValue($this->_createHeader('Return-Path')));
+
+ $set = $this->_createSet($factory);
+ $set->addPathHeader('Return-Path', 'some@path');
+ $this->assertTrue($set->has('Return-Path'));
+ }
+
+ public function testNewlySetHeaderIsSeenByHas()
+ {
+ $factory = $this->_createFactory();
+ $header = $this->_createHeader('X-Foo', 'bar');
+ $set = $this->_createSet($factory);
+ $set->set($header);
+ $this->assertTrue($set->has('X-Foo'));
+ }
+
+ public function testHasCanAcceptOffset()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertTrue($set->has('Message-ID', 0));
+ }
+
+ public function testHasWithIllegalOffsetReturnsFalse()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertFalse($set->has('Message-ID', 1));
+ }
+
+ public function testHasCanDistinguishMultipleHeaders()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($this->_createHeader('Message-ID')));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $this->assertTrue($set->has('Message-ID', 1));
+ }
+
+ public function testGetWithUnspecifiedOffset()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertSame($header, $set->get('Message-ID'));
+ }
+
+ public function testGetWithSpeiciedOffset()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Message-ID');
+ $header2 = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($header1));
+ $factory->expects($this->at(2))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'more@id')
+ ->will($this->returnValue($header2));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $set->addIdHeader('Message-ID', 'more@id');
+ $this->assertSame($header1, $set->get('Message-ID', 1));
+ }
+
+ public function testGetReturnsNullIfHeaderNotSet()
+ {
+ $set = $this->_createSet($this->_createFactory());
+ $this->assertNull($set->get('Message-ID', 99));
+ }
+
+ public function testGetAllReturnsAllHeadersMatchingName()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Message-ID');
+ $header2 = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($header1));
+ $factory->expects($this->at(2))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'more@id')
+ ->will($this->returnValue($header2));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $set->addIdHeader('Message-ID', 'more@id');
+
+ $this->assertEquals(array($header0, $header1, $header2),
+ $set->getAll('Message-ID')
+ );
+ }
+
+ public function testGetAllReturnsAllHeadersIfNoArguments()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Subject');
+ $header2 = $this->_createHeader('To');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Subject', 'thing')
+ ->will($this->returnValue($header1));
+ $factory->expects($this->at(2))
+ ->method('createIdHeader')
+ ->with('To', 'person@example.org')
+ ->will($this->returnValue($header2));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Subject', 'thing');
+ $set->addIdHeader('To', 'person@example.org');
+
+ $this->assertEquals(array($header0, $header1, $header2),
+ $set->getAll()
+ );
+ }
+
+ public function testGetAllReturnsEmptyArrayIfNoneSet()
+ {
+ $set = $this->_createSet($this->_createFactory());
+ $this->assertEquals(array(), $set->getAll('Received'));
+ }
+
+ public function testRemoveWithUnspecifiedOffset()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->remove('Message-ID');
+ $this->assertFalse($set->has('Message-ID'));
+ }
+
+ public function testRemoveWithSpecifiedIndexRemovesHeader()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($header1));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $set->remove('Message-ID', 0);
+ $this->assertFalse($set->has('Message-ID', 0));
+ $this->assertTrue($set->has('Message-ID', 1));
+ $this->assertTrue($set->has('Message-ID'));
+ $set->remove('Message-ID', 1);
+ $this->assertFalse($set->has('Message-ID', 1));
+ $this->assertFalse($set->has('Message-ID'));
+ }
+
+ public function testRemoveWithSpecifiedIndexLeavesOtherHeaders()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($header1));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $set->remove('Message-ID', 1);
+ $this->assertTrue($set->has('Message-ID', 0));
+ }
+
+ public function testRemoveWithInvalidOffsetDoesNothing()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->remove('Message-ID', 50);
+ $this->assertTrue($set->has('Message-ID'));
+ }
+
+ public function testRemoveAllRemovesAllHeadersWithName()
+ {
+ $header0 = $this->_createHeader('Message-ID');
+ $header1 = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header0));
+ $factory->expects($this->at(1))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'other@id')
+ ->will($this->returnValue($header1));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->addIdHeader('Message-ID', 'other@id');
+ $set->removeAll('Message-ID');
+ $this->assertFalse($set->has('Message-ID', 0));
+ $this->assertFalse($set->has('Message-ID', 1));
+ }
+
+ public function testHasIsNotCaseSensitive()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertTrue($set->has('message-id'));
+ }
+
+ public function testGetIsNotCaseSensitive()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertSame($header, $set->get('message-id'));
+ }
+
+ public function testGetAllIsNotCaseSensitive()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $this->assertEquals(array($header), $set->getAll('message-id'));
+ }
+
+ public function testRemoveIsNotCaseSensitive()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->remove('message-id');
+ $this->assertFalse($set->has('Message-ID'));
+ }
+
+ public function testRemoveAllIsNotCaseSensitive()
+ {
+ $header = $this->_createHeader('Message-ID');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createIdHeader')
+ ->with('Message-ID', 'some@id')
+ ->will($this->returnValue($header));
+
+ $set = $this->_createSet($factory);
+ $set->addIdHeader('Message-ID', 'some@id');
+ $set->removeAll('message-id');
+ $this->assertFalse($set->has('Message-ID'));
+ }
+
+ public function testNewInstance()
+ {
+ $set = $this->_createSet($this->_createFactory());
+ $instance = $set->newInstance();
+ $this->assertInstanceOf('Swift_Mime_HeaderSet', $instance);
+ }
+
+ public function testToStringJoinsHeadersTogether()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Foo', 'bar')
+ ->will($this->returnValue($this->_createHeader('Foo', 'bar')));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('Zip', 'buttons')
+ ->will($this->returnValue($this->_createHeader('Zip', 'buttons')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Foo', 'bar');
+ $set->addTextHeader('Zip', 'buttons');
+ $this->assertEquals(
+ "Foo: bar\r\n".
+ "Zip: buttons\r\n",
+ $set->toString()
+ );
+ }
+
+ public function testHeadersWithoutBodiesAreNotDisplayed()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Foo', 'bar')
+ ->will($this->returnValue($this->_createHeader('Foo', 'bar')));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('Zip', '')
+ ->will($this->returnValue($this->_createHeader('Zip', '')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Foo', 'bar');
+ $set->addTextHeader('Zip', '');
+ $this->assertEquals(
+ "Foo: bar\r\n",
+ $set->toString()
+ );
+ }
+
+ public function testHeadersWithoutBodiesCanBeForcedToDisplay()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Foo', '')
+ ->will($this->returnValue($this->_createHeader('Foo', '')));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('Zip', '')
+ ->will($this->returnValue($this->_createHeader('Zip', '')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Foo', '');
+ $set->addTextHeader('Zip', '');
+ $set->setAlwaysDisplayed(array('Foo', 'Zip'));
+ $this->assertEquals(
+ "Foo: \r\n".
+ "Zip: \r\n",
+ $set->toString()
+ );
+ }
+
+ public function testHeaderSequencesCanBeSpecified()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Third', 'three')
+ ->will($this->returnValue($this->_createHeader('Third', 'three')));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('First', 'one')
+ ->will($this->returnValue($this->_createHeader('First', 'one')));
+ $factory->expects($this->at(2))
+ ->method('createTextHeader')
+ ->with('Second', 'two')
+ ->will($this->returnValue($this->_createHeader('Second', 'two')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Third', 'three');
+ $set->addTextHeader('First', 'one');
+ $set->addTextHeader('Second', 'two');
+
+ $set->defineOrdering(array('First', 'Second', 'Third'));
+
+ $this->assertEquals(
+ "First: one\r\n".
+ "Second: two\r\n".
+ "Third: three\r\n",
+ $set->toString()
+ );
+ }
+
+ public function testUnsortedHeadersAppearAtEnd()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Fourth', 'four')
+ ->will($this->returnValue($this->_createHeader('Fourth', 'four')));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('Fifth', 'five')
+ ->will($this->returnValue($this->_createHeader('Fifth', 'five')));
+ $factory->expects($this->at(2))
+ ->method('createTextHeader')
+ ->with('Third', 'three')
+ ->will($this->returnValue($this->_createHeader('Third', 'three')));
+ $factory->expects($this->at(3))
+ ->method('createTextHeader')
+ ->with('First', 'one')
+ ->will($this->returnValue($this->_createHeader('First', 'one')));
+ $factory->expects($this->at(4))
+ ->method('createTextHeader')
+ ->with('Second', 'two')
+ ->will($this->returnValue($this->_createHeader('Second', 'two')));
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Fourth', 'four');
+ $set->addTextHeader('Fifth', 'five');
+ $set->addTextHeader('Third', 'three');
+ $set->addTextHeader('First', 'one');
+ $set->addTextHeader('Second', 'two');
+
+ $set->defineOrdering(array('First', 'Second', 'Third'));
+
+ $this->assertEquals(
+ "First: one\r\n".
+ "Second: two\r\n".
+ "Third: three\r\n".
+ "Fourth: four\r\n".
+ "Fifth: five\r\n",
+ $set->toString()
+ );
+ }
+
+ public function testSettingCharsetNotifiesAlreadyExistingHeaders()
+ {
+ $subject = $this->_createHeader('Subject', 'some text');
+ $xHeader = $this->_createHeader('X-Header', 'some text');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Subject', 'some text')
+ ->will($this->returnValue($subject));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('X-Header', 'some text')
+ ->will($this->returnValue($xHeader));
+ $subject->expects($this->once())
+ ->method('setCharset')
+ ->with('utf-8');
+ $xHeader->expects($this->once())
+ ->method('setCharset')
+ ->with('utf-8');
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Subject', 'some text');
+ $set->addTextHeader('X-Header', 'some text');
+
+ $set->setCharset('utf-8');
+ }
+
+ public function testCharsetChangeNotifiesAlreadyExistingHeaders()
+ {
+ $subject = $this->_createHeader('Subject', 'some text');
+ $xHeader = $this->_createHeader('X-Header', 'some text');
+ $factory = $this->_createFactory();
+ $factory->expects($this->at(0))
+ ->method('createTextHeader')
+ ->with('Subject', 'some text')
+ ->will($this->returnValue($subject));
+ $factory->expects($this->at(1))
+ ->method('createTextHeader')
+ ->with('X-Header', 'some text')
+ ->will($this->returnValue($xHeader));
+ $subject->expects($this->once())
+ ->method('setCharset')
+ ->with('utf-8');
+ $xHeader->expects($this->once())
+ ->method('setCharset')
+ ->with('utf-8');
+
+ $set = $this->_createSet($factory);
+ $set->addTextHeader('Subject', 'some text');
+ $set->addTextHeader('X-Header', 'some text');
+
+ $set->charsetChanged('utf-8');
+ }
+
+ public function testCharsetChangeNotifiesFactory()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('charsetChanged')
+ ->with('utf-8');
+
+ $set = $this->_createSet($factory);
+
+ $set->setCharset('utf-8');
+ }
+
+ private function _createSet($factory)
+ {
+ return new Swift_Mime_SimpleHeaderSet($factory);
+ }
+
+ private function _createFactory()
+ {
+ return $this->getMockBuilder('Swift_Mime_HeaderFactory')->getMock();
+ }
+
+ private function _createHeader($name, $body = '')
+ {
+ $header = $this->getMockBuilder('Swift_Mime_Header')->getMock();
+ $header->expects($this->any())
+ ->method('getFieldName')
+ ->will($this->returnValue($name));
+ $header->expects($this->any())
+ ->method('toString')
+ ->will($this->returnValue(sprintf("%s: %s\r\n", $name, $body)));
+ $header->expects($this->any())
+ ->method('getFieldBody')
+ ->will($this->returnValue($body));
+
+ return $header;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php
new file mode 100644
index 0000000..e5d225c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php
@@ -0,0 +1,827 @@
+<?php
+
+class Swift_Mime_SimpleMessageTest extends Swift_Mime_MimePartTest
+{
+ public function testNestingLevelIsSubpart()
+ {
+ //Overridden
+ }
+
+ public function testNestingLevelIsTop()
+ {
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(
+ Swift_Mime_MimeEntity::LEVEL_TOP, $message->getNestingLevel()
+ );
+ }
+
+ public function testDateIsReturnedFromHeader()
+ {
+ $date = $this->_createHeader('Date', 123);
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Date' => $date)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(123, $message->getDate());
+ }
+
+ public function testDateIsSetInHeader()
+ {
+ $date = $this->_createHeader('Date', 123, array(), false);
+ $date->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(1234);
+ $date->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Date' => $date)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setDate(1234);
+ }
+
+ public function testDateHeaderIsCreatedIfNonePresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addDateHeader')
+ ->once()
+ ->with('Date', 1234);
+ $headers->shouldReceive('addDateHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setDate(1234);
+ }
+
+ public function testDateHeaderIsAddedDuringConstruction()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addDateHeader')
+ ->once()
+ ->with('Date', '/^[0-9]+$/D');
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ }
+
+ public function testIdIsReturnedFromHeader()
+ {
+ /* -- RFC 2045, 7.
+ In constructing a high-level user agent, it may be desirable to allow
+ one body to make reference to another. Accordingly, bodies may be
+ labelled using the "Content-ID" header field, which is syntactically
+ identical to the "Message-ID" header field
+ */
+
+ $messageId = $this->_createHeader('Message-ID', 'a@b');
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Message-ID' => $messageId)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('a@b', $message->getId());
+ }
+
+ public function testIdIsSetInHeader()
+ {
+ $messageId = $this->_createHeader('Message-ID', 'a@b', array(), false);
+ $messageId->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('x@y');
+ $messageId->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Message-ID' => $messageId)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setId('x@y');
+ }
+
+ public function testIdIsAutoGenerated()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addIdHeader')
+ ->once()
+ ->with('Message-ID', '/^.*?@.*?$/D');
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ }
+
+ public function testSubjectIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.5.
+ */
+
+ $subject = $this->_createHeader('Subject', 'example subject');
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Subject' => $subject)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('example subject', $message->getSubject());
+ }
+
+ public function testSubjectIsSetInHeader()
+ {
+ $subject = $this->_createHeader('Subject', '', array(), false);
+ $subject->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('foo');
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Subject' => $subject)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setSubject('foo');
+ }
+
+ public function testSubjectHeaderIsCreatedIfNotPresent()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addTextHeader')
+ ->once()
+ ->with('Subject', 'example subject');
+ $headers->shouldReceive('addTextHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setSubject('example subject');
+ }
+
+ public function testReturnPathIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.7.
+ */
+
+ $path = $this->_createHeader('Return-Path', 'bounces@domain');
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Return-Path' => $path)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals('bounces@domain', $message->getReturnPath());
+ }
+
+ public function testReturnPathIsSetInHeader()
+ {
+ $path = $this->_createHeader('Return-Path', '', array(), false);
+ $path->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('bounces@domain');
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Return-Path' => $path)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setReturnPath('bounces@domain');
+ }
+
+ public function testReturnPathHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addPathHeader')
+ ->once()
+ ->with('Return-Path', 'bounces@domain');
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setReturnPath('bounces@domain');
+ }
+
+ public function testSenderIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.2.
+ */
+
+ $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Sender' => $sender)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('sender@domain' => 'Name'), $message->getSender());
+ }
+
+ public function testSenderIsSetInHeader()
+ {
+ $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name'),
+ array(), false
+ );
+ $sender->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Sender' => $sender)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setSender(array('other@domain' => 'Other'));
+ }
+
+ public function testSenderHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Sender', (array) 'sender@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setSender('sender@domain');
+ }
+
+ public function testNameCanBeUsedInSenderHeader()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Sender', array('sender@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setSender('sender@domain', 'Name');
+ }
+
+ public function testFromIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.2.
+ */
+
+ $from = $this->_createHeader('From', array('from@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('From' => $from)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('from@domain' => 'Name'), $message->getFrom());
+ }
+
+ public function testFromIsSetInHeader()
+ {
+ $from = $this->_createHeader('From', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $from->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('From' => $from)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setFrom(array('other@domain' => 'Other'));
+ }
+
+ public function testFromIsAddedToHeadersDuringAddFrom()
+ {
+ $from = $this->_createHeader('From', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $from->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('From' => $from)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->addFrom('other@domain', 'Other');
+ }
+
+ public function testFromHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('From', (array) 'from@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setFrom('from@domain');
+ }
+
+ public function testPersonalNameCanBeUsedInFromAddress()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('From', array('from@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setFrom('from@domain', 'Name');
+ }
+
+ public function testReplyToIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.2.
+ */
+
+ $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Reply-To' => $reply)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('reply@domain' => 'Name'), $message->getReplyTo());
+ }
+
+ public function testReplyToIsSetInHeader()
+ {
+ $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name'),
+ array(), false
+ );
+ $reply->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Reply-To' => $reply)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setReplyTo(array('other@domain' => 'Other'));
+ }
+
+ public function testReplyToIsAddedToHeadersDuringAddReplyTo()
+ {
+ $replyTo = $this->_createHeader('Reply-To', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $replyTo->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Reply-To' => $replyTo)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->addReplyTo('other@domain', 'Other');
+ }
+
+ public function testReplyToHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Reply-To', (array) 'reply@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setReplyTo('reply@domain');
+ }
+
+ public function testNameCanBeUsedInReplyTo()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Reply-To', array('reply@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setReplyTo('reply@domain', 'Name');
+ }
+
+ public function testToIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.3.
+ */
+
+ $to = $this->_createHeader('To', array('to@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('To' => $to)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('to@domain' => 'Name'), $message->getTo());
+ }
+
+ public function testToIsSetInHeader()
+ {
+ $to = $this->_createHeader('To', array('to@domain' => 'Name'),
+ array(), false
+ );
+ $to->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('To' => $to)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setTo(array('other@domain' => 'Other'));
+ }
+
+ public function testToIsAddedToHeadersDuringAddTo()
+ {
+ $to = $this->_createHeader('To', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $to->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('To' => $to)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->addTo('other@domain', 'Other');
+ }
+
+ public function testToHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('To', (array) 'to@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setTo('to@domain');
+ }
+
+ public function testNameCanBeUsedInToHeader()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('To', array('to@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setTo('to@domain', 'Name');
+ }
+
+ public function testCcIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.3.
+ */
+
+ $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Cc' => $cc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('cc@domain' => 'Name'), $message->getCc());
+ }
+
+ public function testCcIsSetInHeader()
+ {
+ $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name'),
+ array(), false
+ );
+ $cc->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Cc' => $cc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setCc(array('other@domain' => 'Other'));
+ }
+
+ public function testCcIsAddedToHeadersDuringAddCc()
+ {
+ $cc = $this->_createHeader('Cc', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $cc->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Cc' => $cc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->addCc('other@domain', 'Other');
+ }
+
+ public function testCcHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Cc', (array) 'cc@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setCc('cc@domain');
+ }
+
+ public function testNameCanBeUsedInCcHeader()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Cc', array('cc@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setCc('cc@domain', 'Name');
+ }
+
+ public function testBccIsReturnedFromHeader()
+ {
+ /* -- RFC 2822, 3.6.3.
+ */
+
+ $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name'));
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Bcc' => $bcc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('bcc@domain' => 'Name'), $message->getBcc());
+ }
+
+ public function testBccIsSetInHeader()
+ {
+ $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name'),
+ array(), false
+ );
+ $bcc->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Bcc' => $bcc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setBcc(array('other@domain' => 'Other'));
+ }
+
+ public function testBccIsAddedToHeadersDuringAddBcc()
+ {
+ $bcc = $this->_createHeader('Bcc', array('from@domain' => 'Name'),
+ array(), false
+ );
+ $bcc->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Bcc' => $bcc)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->addBcc('other@domain', 'Other');
+ }
+
+ public function testBccHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Bcc', (array) 'bcc@domain');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setBcc('bcc@domain');
+ }
+
+ public function testNameCanBeUsedInBcc()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Bcc', array('bcc@domain' => 'Name'));
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setBcc('bcc@domain', 'Name');
+ }
+
+ public function testPriorityIsReadFromHeader()
+ {
+ $prio = $this->_createHeader('X-Priority', '2 (High)');
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('X-Priority' => $prio)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(2, $message->getPriority());
+ }
+
+ public function testPriorityIsSetInHeader()
+ {
+ $prio = $this->_createHeader('X-Priority', '2 (High)', array(), false);
+ $prio->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('5 (Lowest)');
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('X-Priority' => $prio)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setPriority($message::PRIORITY_LOWEST);
+ }
+
+ public function testPriorityHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addTextHeader')
+ ->once()
+ ->with('X-Priority', '4 (Low)');
+ $headers->shouldReceive('addTextHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setPriority($message::PRIORITY_LOW);
+ }
+
+ public function testReadReceiptAddressReadFromHeader()
+ {
+ $rcpt = $this->_createHeader('Disposition-Notification-To',
+ array('chris@swiftmailer.org' => 'Chris')
+ );
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertEquals(array('chris@swiftmailer.org' => 'Chris'),
+ $message->getReadReceiptTo()
+ );
+ }
+
+ public function testReadReceiptIsSetInHeader()
+ {
+ $rcpt = $this->_createHeader('Disposition-Notification-To', array(), array(), false);
+ $rcpt->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('mark@swiftmailer.org');
+
+ $message = $this->_createMessage(
+ $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $message->setReadReceiptTo('mark@swiftmailer.org');
+ }
+
+ public function testReadReceiptHeaderIsAddedIfNoneSet()
+ {
+ $headers = $this->_createHeaderSet(array(), false);
+ $headers->shouldReceive('addMailboxHeader')
+ ->once()
+ ->with('Disposition-Notification-To', 'mark@swiftmailer.org');
+ $headers->shouldReceive('addMailboxHeader')
+ ->zeroOrMoreTimes();
+
+ $message = $this->_createMessage($headers, $this->_createEncoder(),
+ $this->_createCache()
+ );
+ $message->setReadReceiptTo('mark@swiftmailer.org');
+ }
+
+ public function testChildrenCanBeAttached()
+ {
+ $child1 = $this->_createChild();
+ $child2 = $this->_createChild();
+
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $message->attach($child1);
+ $message->attach($child2);
+
+ $this->assertEquals(array($child1, $child2), $message->getChildren());
+ }
+
+ public function testChildrenCanBeDetached()
+ {
+ $child1 = $this->_createChild();
+ $child2 = $this->_createChild();
+
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $message->attach($child1);
+ $message->attach($child2);
+
+ $message->detach($child1);
+
+ $this->assertEquals(array($child2), $message->getChildren());
+ }
+
+ public function testEmbedAttachesChild()
+ {
+ $child = $this->_createChild();
+
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $message->embed($child);
+
+ $this->assertEquals(array($child), $message->getChildren());
+ }
+
+ public function testEmbedReturnsValidCid()
+ {
+ $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_RELATED, '',
+ false
+ );
+ $child->shouldReceive('getId')
+ ->zeroOrMoreTimes()
+ ->andReturn('foo@bar');
+
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+
+ $this->assertEquals('cid:foo@bar', $message->embed($child));
+ }
+
+ public function testFluidInterface()
+ {
+ $child = $this->_createChild();
+ $message = $this->_createMessage($this->_createHeaderSet(),
+ $this->_createEncoder(), $this->_createCache()
+ );
+ $this->assertSame($message,
+ $message
+ ->setContentType('text/plain')
+ ->setEncoder($this->_createEncoder())
+ ->setId('foo@bar')
+ ->setDescription('my description')
+ ->setMaxLineLength(998)
+ ->setBody('xx')
+ ->setBoundary('xyz')
+ ->setChildren(array())
+ ->setCharset('iso-8859-1')
+ ->setFormat('flowed')
+ ->setDelSp(false)
+ ->setSubject('subj')
+ ->setDate(123)
+ ->setReturnPath('foo@bar')
+ ->setSender('foo@bar')
+ ->setFrom(array('x@y' => 'XY'))
+ ->setReplyTo(array('ab@cd' => 'ABCD'))
+ ->setTo(array('chris@site.tld', 'mark@site.tld'))
+ ->setCc('john@somewhere.tld')
+ ->setBcc(array('one@site', 'two@site' => 'Two'))
+ ->setPriority($message::PRIORITY_LOW)
+ ->setReadReceiptTo('a@b')
+ ->attach($child)
+ ->detach($child)
+ );
+ }
+
+ //abstract
+ protected function _createEntity($headers, $encoder, $cache)
+ {
+ return $this->_createMessage($headers, $encoder, $cache);
+ }
+
+ protected function _createMimePart($headers, $encoder, $cache)
+ {
+ return $this->_createMessage($headers, $encoder, $cache);
+ }
+
+ private function _createMessage($headers, $encoder, $cache)
+ {
+ return new Swift_Mime_SimpleMessage($headers, $encoder, $cache, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php
new file mode 100644
index 0000000..fa2a8d4
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php
@@ -0,0 +1,9 @@
+<?php
+
+class Swift_Mime_SimpleMimeEntityTest extends Swift_Mime_AbstractMimeEntityTest
+{
+ protected function _createEntity($headerFactory, $encoder, $cache)
+ {
+ return new Swift_Mime_SimpleMimeEntity($headerFactory, $encoder, $cache, new Swift_Mime_Grammar());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/AntiFloodPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/AntiFloodPluginTest.php
new file mode 100644
index 0000000..463e4eb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/AntiFloodPluginTest.php
@@ -0,0 +1,93 @@
+<?php
+
+class Swift_Plugins_AntiFloodPluginTest extends \PHPUnit_Framework_TestCase
+{
+ public function testThresholdCanBeSetAndFetched()
+ {
+ $plugin = new Swift_Plugins_AntiFloodPlugin(10);
+ $this->assertEquals(10, $plugin->getThreshold());
+ $plugin->setThreshold(100);
+ $this->assertEquals(100, $plugin->getThreshold());
+ }
+
+ public function testSleepTimeCanBeSetAndFetched()
+ {
+ $plugin = new Swift_Plugins_AntiFloodPlugin(10, 5);
+ $this->assertEquals(5, $plugin->getSleepTime());
+ $plugin->setSleepTime(1);
+ $this->assertEquals(1, $plugin->getSleepTime());
+ }
+
+ public function testPluginStopsConnectionAfterThreshold()
+ {
+ $transport = $this->_createTransport();
+ $transport->expects($this->once())
+ ->method('start');
+ $transport->expects($this->once())
+ ->method('stop');
+
+ $evt = $this->_createSendEvent($transport);
+
+ $plugin = new Swift_Plugins_AntiFloodPlugin(10);
+ for ($i = 0; $i < 12; ++$i) {
+ $plugin->sendPerformed($evt);
+ }
+ }
+
+ public function testPluginCanStopAndStartMultipleTimes()
+ {
+ $transport = $this->_createTransport();
+ $transport->expects($this->exactly(5))
+ ->method('start');
+ $transport->expects($this->exactly(5))
+ ->method('stop');
+
+ $evt = $this->_createSendEvent($transport);
+
+ $plugin = new Swift_Plugins_AntiFloodPlugin(2);
+ for ($i = 0; $i < 11; ++$i) {
+ $plugin->sendPerformed($evt);
+ }
+ }
+
+ public function testPluginCanSleepDuringRestart()
+ {
+ $sleeper = $this->getMockBuilder('Swift_Plugins_Sleeper')->getMock();
+ $sleeper->expects($this->once())
+ ->method('sleep')
+ ->with(10);
+
+ $transport = $this->_createTransport();
+ $transport->expects($this->once())
+ ->method('start');
+ $transport->expects($this->once())
+ ->method('stop');
+
+ $evt = $this->_createSendEvent($transport);
+
+ $plugin = new Swift_Plugins_AntiFloodPlugin(99, 10, $sleeper);
+ for ($i = 0; $i < 101; ++$i) {
+ $plugin->sendPerformed($evt);
+ }
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+
+ private function _createSendEvent($transport)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_SendEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getSource')
+ ->will($this->returnValue($transport));
+ $evt->expects($this->any())
+ ->method('getTransport')
+ ->will($this->returnValue($transport));
+
+ return $evt;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php
new file mode 100644
index 0000000..869cfc8
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php
@@ -0,0 +1,128 @@
+<?php
+
+class Swift_Plugins_BandwidthMonitorPluginTest extends \PHPUnit_Framework_TestCase
+{
+ private $_monitor;
+
+ private $_bytes = 0;
+
+ protected function setUp()
+ {
+ $this->_monitor = new Swift_Plugins_BandwidthMonitorPlugin();
+ }
+
+ public function testBytesOutIncreasesWhenCommandsSent()
+ {
+ $evt = $this->_createCommandEvent("RCPT TO:<foo@bar.com>\r\n");
+
+ $this->assertEquals(0, $this->_monitor->getBytesOut());
+ $this->_monitor->commandSent($evt);
+ $this->assertEquals(23, $this->_monitor->getBytesOut());
+ $this->_monitor->commandSent($evt);
+ $this->assertEquals(46, $this->_monitor->getBytesOut());
+ }
+
+ public function testBytesInIncreasesWhenResponsesReceived()
+ {
+ $evt = $this->_createResponseEvent("250 Ok\r\n");
+
+ $this->assertEquals(0, $this->_monitor->getBytesIn());
+ $this->_monitor->responseReceived($evt);
+ $this->assertEquals(8, $this->_monitor->getBytesIn());
+ $this->_monitor->responseReceived($evt);
+ $this->assertEquals(16, $this->_monitor->getBytesIn());
+ }
+
+ public function testCountersCanBeReset()
+ {
+ $evt = $this->_createResponseEvent("250 Ok\r\n");
+
+ $this->assertEquals(0, $this->_monitor->getBytesIn());
+ $this->_monitor->responseReceived($evt);
+ $this->assertEquals(8, $this->_monitor->getBytesIn());
+ $this->_monitor->responseReceived($evt);
+ $this->assertEquals(16, $this->_monitor->getBytesIn());
+
+ $evt = $this->_createCommandEvent("RCPT TO:<foo@bar.com>\r\n");
+
+ $this->assertEquals(0, $this->_monitor->getBytesOut());
+ $this->_monitor->commandSent($evt);
+ $this->assertEquals(23, $this->_monitor->getBytesOut());
+ $this->_monitor->commandSent($evt);
+ $this->assertEquals(46, $this->_monitor->getBytesOut());
+
+ $this->_monitor->reset();
+
+ $this->assertEquals(0, $this->_monitor->getBytesOut());
+ $this->assertEquals(0, $this->_monitor->getBytesIn());
+ }
+
+ public function testBytesOutIncreasesAccordingToMessageLength()
+ {
+ $message = $this->_createMessageWithByteCount(6);
+ $evt = $this->_createSendEvent($message);
+
+ $this->assertEquals(0, $this->_monitor->getBytesOut());
+ $this->_monitor->sendPerformed($evt);
+ $this->assertEquals(6, $this->_monitor->getBytesOut());
+ $this->_monitor->sendPerformed($evt);
+ $this->assertEquals(12, $this->_monitor->getBytesOut());
+ }
+
+ private function _createSendEvent($message)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_SendEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getMessage')
+ ->will($this->returnValue($message));
+
+ return $evt;
+ }
+
+ private function _createCommandEvent($command)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_CommandEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getCommand')
+ ->will($this->returnValue($command));
+
+ return $evt;
+ }
+
+ private function _createResponseEvent($response)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_ResponseEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getResponse')
+ ->will($this->returnValue($response));
+
+ return $evt;
+ }
+
+ private function _createMessageWithByteCount($bytes)
+ {
+ $this->_bytes = $bytes;
+ $msg = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+ $msg->expects($this->any())
+ ->method('toByteStream')
+ ->will($this->returnCallback(array($this, '_write')));
+ /* $this->_checking(Expectations::create()
+ -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write'))
+ ); */
+
+ return $msg;
+ }
+
+ public function _write($is)
+ {
+ for ($i = 0; $i < $this->_bytes; ++$i) {
+ $is->write('x');
+ }
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php
new file mode 100644
index 0000000..8019dfb
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php
@@ -0,0 +1,267 @@
+<?php
+
+class Swift_Plugins_DecoratorPluginTest extends \SwiftMailerTestCase
+{
+ public function testMessageBodyReceivesReplacements()
+ {
+ $message = $this->_createMessage(
+ $this->_createHeaders(),
+ array('zip@button.tld' => 'Zipathon'),
+ array('chris.corbyn@swiftmailer.org' => 'Chris'),
+ 'Subject',
+ 'Hello {name}, you are customer #{id}'
+ );
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Hello Zip, you are customer #456');
+ $message->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+
+ $plugin = $this->_createPlugin(
+ array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
+ );
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReplacementsCanBeAppliedToSameMessageMultipleTimes()
+ {
+ $message = $this->_createMessage(
+ $this->_createHeaders(),
+ array('zip@button.tld' => 'Zipathon', 'foo@bar.tld' => 'Foo'),
+ array('chris.corbyn@swiftmailer.org' => 'Chris'),
+ 'Subject',
+ 'Hello {name}, you are customer #{id}'
+ );
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Hello Zip, you are customer #456');
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Hello {name}, you are customer #{id}');
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Hello Foo, you are customer #123');
+ $message->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+
+ $plugin = $this->_createPlugin(
+ array(
+ 'foo@bar.tld' => array('{name}' => 'Foo', '{id}' => '123'),
+ 'zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'),
+ )
+ );
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReplacementsCanBeMadeInHeaders()
+ {
+ $headers = $this->_createHeaders(array(
+ $returnPathHeader = $this->_createHeader('Return-Path', 'foo-{id}@swiftmailer.org'),
+ $toHeader = $this->_createHeader('Subject', 'A message for {name}!'),
+ ));
+
+ $message = $this->_createMessage(
+ $headers,
+ array('zip@button.tld' => 'Zipathon'),
+ array('chris.corbyn@swiftmailer.org' => 'Chris'),
+ 'A message for {name}!',
+ 'Hello {name}, you are customer #{id}'
+ );
+
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Hello Zip, you are customer #456');
+ $toHeader->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('A message for Zip!');
+ $returnPathHeader->shouldReceive('setFieldBodyModel')
+ ->once()
+ ->with('foo-456@swiftmailer.org');
+ $message->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+ $toHeader->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+ $returnPathHeader->shouldReceive('setFieldBodyModel')
+ ->zeroOrMoreTimes();
+
+ $plugin = $this->_createPlugin(
+ array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
+ );
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReplacementsAreMadeOnSubparts()
+ {
+ $part1 = $this->_createPart('text/plain', 'Your name is {name}?', '1@x');
+ $part2 = $this->_createPart('text/html', 'Your <em>name</em> is {name}?', '2@x');
+ $message = $this->_createMessage(
+ $this->_createHeaders(),
+ array('zip@button.tld' => 'Zipathon'),
+ array('chris.corbyn@swiftmailer.org' => 'Chris'),
+ 'A message for {name}!',
+ 'Subject'
+ );
+ $message->shouldReceive('getChildren')
+ ->zeroOrMoreTimes()
+ ->andReturn(array($part1, $part2));
+ $part1->shouldReceive('setBody')
+ ->once()
+ ->with('Your name is Zip?');
+ $part2->shouldReceive('setBody')
+ ->once()
+ ->with('Your <em>name</em> is Zip?');
+ $part1->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+ $part2->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+
+ $plugin = $this->_createPlugin(
+ array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
+ );
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReplacementsCanBeTakenFromCustomReplacementsObject()
+ {
+ $message = $this->_createMessage(
+ $this->_createHeaders(),
+ array('foo@bar' => 'Foobar', 'zip@zap' => 'Zip zap'),
+ array('chris.corbyn@swiftmailer.org' => 'Chris'),
+ 'Subject',
+ 'Something {a}'
+ );
+
+ $replacements = $this->_createReplacements();
+
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Something b');
+ $message->shouldReceive('setBody')
+ ->once()
+ ->with('Something c');
+ $message->shouldReceive('setBody')
+ ->zeroOrMoreTimes();
+ $replacements->shouldReceive('getReplacementsFor')
+ ->once()
+ ->with('foo@bar')
+ ->andReturn(array('{a}' => 'b'));
+ $replacements->shouldReceive('getReplacementsFor')
+ ->once()
+ ->with('zip@zap')
+ ->andReturn(array('{a}' => 'c'));
+
+ $plugin = $this->_createPlugin($replacements);
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+
+ private function _createMessage($headers, $to = array(), $from = null, $subject = null,
+ $body = null)
+ {
+ $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ foreach ($to as $addr => $name) {
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array($addr => $name));
+ }
+ $message->shouldReceive('getHeaders')
+ ->zeroOrMoreTimes()
+ ->andReturn($headers);
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn($from);
+ $message->shouldReceive('getSubject')
+ ->zeroOrMoreTimes()
+ ->andReturn($subject);
+ $message->shouldReceive('getBody')
+ ->zeroOrMoreTimes()
+ ->andReturn($body);
+
+ return $message;
+ }
+
+ private function _createPlugin($replacements)
+ {
+ return new Swift_Plugins_DecoratorPlugin($replacements);
+ }
+
+ private function _createReplacements()
+ {
+ return $this->getMockery('Swift_Plugins_Decorator_Replacements')->shouldIgnoreMissing();
+ }
+
+ private function _createSendEvent(Swift_Mime_Message $message)
+ {
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $evt->shouldReceive('getMessage')
+ ->zeroOrMoreTimes()
+ ->andReturn($message);
+
+ return $evt;
+ }
+
+ private function _createPart($type, $body, $id)
+ {
+ $part = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing();
+ $part->shouldReceive('getContentType')
+ ->zeroOrMoreTimes()
+ ->andReturn($type);
+ $part->shouldReceive('getBody')
+ ->zeroOrMoreTimes()
+ ->andReturn($body);
+ $part->shouldReceive('getId')
+ ->zeroOrMoreTimes()
+ ->andReturn($id);
+
+ return $part;
+ }
+
+ private function _createHeaders($headers = array())
+ {
+ $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();
+ $set->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->andReturn($headers);
+
+ foreach ($headers as $header) {
+ $set->set($header);
+ }
+
+ return $set;
+ }
+
+ private function _createHeader($name, $body = '')
+ {
+ $header = $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing();
+ $header->shouldReceive('getFieldName')
+ ->zeroOrMoreTimes()
+ ->andReturn($name);
+ $header->shouldReceive('getFieldBodyModel')
+ ->zeroOrMoreTimes()
+ ->andReturn($body);
+
+ return $header;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php
new file mode 100644
index 0000000..bfe4cb7
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php
@@ -0,0 +1,188 @@
+<?php
+
+class Swift_Plugins_LoggerPluginTest extends \SwiftMailerTestCase
+{
+ public function testLoggerDelegatesAddingEntries()
+ {
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with('foo');
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->add('foo');
+ }
+
+ public function testLoggerDelegatesDumpingEntries()
+ {
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('dump')
+ ->will($this->returnValue('foobar'));
+
+ $plugin = $this->_createPlugin($logger);
+ $this->assertEquals('foobar', $plugin->dump());
+ }
+
+ public function testLoggerDelegatesClearingEntries()
+ {
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('clear');
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->clear();
+ }
+
+ public function testCommandIsSentToLogger()
+ {
+ $evt = $this->_createCommandEvent("foo\r\n");
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->regExp('~foo\r\n~'));
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->commandSent($evt);
+ }
+
+ public function testResponseIsSentToLogger()
+ {
+ $evt = $this->_createResponseEvent("354 Go ahead\r\n");
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->regExp('~354 Go ahead\r\n~'));
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->responseReceived($evt);
+ }
+
+ public function testTransportBeforeStartChangeIsSentToLogger()
+ {
+ $evt = $this->_createTransportChangeEvent();
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->anything());
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->beforeTransportStarted($evt);
+ }
+
+ public function testTransportStartChangeIsSentToLogger()
+ {
+ $evt = $this->_createTransportChangeEvent();
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->anything());
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->transportStarted($evt);
+ }
+
+ public function testTransportStopChangeIsSentToLogger()
+ {
+ $evt = $this->_createTransportChangeEvent();
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->anything());
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->transportStopped($evt);
+ }
+
+ public function testTransportBeforeStopChangeIsSentToLogger()
+ {
+ $evt = $this->_createTransportChangeEvent();
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->anything());
+
+ $plugin = $this->_createPlugin($logger);
+ $plugin->beforeTransportStopped($evt);
+ }
+
+ public function testExceptionsArePassedToDelegateAndLeftToBubbleUp()
+ {
+ $transport = $this->_createTransport();
+ $evt = $this->_createTransportExceptionEvent();
+ $logger = $this->_createLogger();
+ $logger->expects($this->once())
+ ->method('add')
+ ->with($this->anything());
+
+ $plugin = $this->_createPlugin($logger);
+ try {
+ $plugin->exceptionThrown($evt);
+ $this->fail('Exception should bubble up.');
+ } catch (Swift_TransportException $ex) {
+ }
+ }
+
+ private function _createLogger()
+ {
+ return $this->getMockBuilder('Swift_Plugins_Logger')->getMock();
+ }
+
+ private function _createPlugin($logger)
+ {
+ return new Swift_Plugins_LoggerPlugin($logger);
+ }
+
+ private function _createCommandEvent($command)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_CommandEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getCommand')
+ ->will($this->returnValue($command));
+
+ return $evt;
+ }
+
+ private function _createResponseEvent($response)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_ResponseEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getResponse')
+ ->will($this->returnValue($response));
+
+ return $evt;
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+
+ private function _createTransportChangeEvent()
+ {
+ $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getSource')
+ ->will($this->returnValue($this->_createTransport()));
+
+ return $evt;
+ }
+
+ public function _createTransportExceptionEvent()
+ {
+ $evt = $this->getMockBuilder('Swift_Events_TransportExceptionEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getException')
+ ->will($this->returnValue(new Swift_TransportException('')));
+
+ return $evt;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php
new file mode 100644
index 0000000..880bb32
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php
@@ -0,0 +1,65 @@
+<?php
+
+class Swift_Plugins_Loggers_ArrayLoggerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testAddingSingleEntryDumpsSingleLine()
+ {
+ $logger = new Swift_Plugins_Loggers_ArrayLogger();
+ $logger->add(">> Foo\r\n");
+ $this->assertEquals(">> Foo\r\n", $logger->dump());
+ }
+
+ public function testAddingMultipleEntriesDumpsMultipleLines()
+ {
+ $logger = new Swift_Plugins_Loggers_ArrayLogger();
+ $logger->add(">> FOO\r\n");
+ $logger->add("<< 502 That makes no sense\r\n");
+ $logger->add(">> RSET\r\n");
+ $logger->add("<< 250 OK\r\n");
+
+ $this->assertEquals(
+ ">> FOO\r\n".PHP_EOL.
+ "<< 502 That makes no sense\r\n".PHP_EOL.
+ ">> RSET\r\n".PHP_EOL.
+ "<< 250 OK\r\n",
+ $logger->dump()
+ );
+ }
+
+ public function testLogCanBeCleared()
+ {
+ $logger = new Swift_Plugins_Loggers_ArrayLogger();
+ $logger->add(">> FOO\r\n");
+ $logger->add("<< 502 That makes no sense\r\n");
+ $logger->add(">> RSET\r\n");
+ $logger->add("<< 250 OK\r\n");
+
+ $this->assertEquals(
+ ">> FOO\r\n".PHP_EOL.
+ "<< 502 That makes no sense\r\n".PHP_EOL.
+ ">> RSET\r\n".PHP_EOL.
+ "<< 250 OK\r\n",
+ $logger->dump()
+ );
+
+ $logger->clear();
+
+ $this->assertEquals('', $logger->dump());
+ }
+
+ public function testLengthCanBeTruncated()
+ {
+ $logger = new Swift_Plugins_Loggers_ArrayLogger(2);
+ $logger->add(">> FOO\r\n");
+ $logger->add("<< 502 That makes no sense\r\n");
+ $logger->add(">> RSET\r\n");
+ $logger->add("<< 250 OK\r\n");
+
+ $this->assertEquals(
+ ">> RSET\r\n".PHP_EOL.
+ "<< 250 OK\r\n",
+ $logger->dump(),
+ '%s: Log should be truncated to last 2 entries'
+ );
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php
new file mode 100644
index 0000000..6134fe6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php
@@ -0,0 +1,24 @@
+<?php
+
+class Swift_Plugins_Loggers_EchoLoggerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testAddingEntryDumpsSingleLineWithoutHtml()
+ {
+ $logger = new Swift_Plugins_Loggers_EchoLogger(false);
+ ob_start();
+ $logger->add('>> Foo');
+ $data = ob_get_clean();
+
+ $this->assertEquals('>> Foo'.PHP_EOL, $data);
+ }
+
+ public function testAddingEntryDumpsEscapedLineWithHtml()
+ {
+ $logger = new Swift_Plugins_Loggers_EchoLogger(true);
+ ob_start();
+ $logger->add('>> Foo');
+ $data = ob_get_clean();
+
+ $this->assertEquals('&gt;&gt; Foo<br />'.PHP_EOL, $data);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php
new file mode 100644
index 0000000..cbd368f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php
@@ -0,0 +1,101 @@
+<?php
+
+class Swift_Plugins_PopBeforeSmtpPluginTest extends \PHPUnit_Framework_TestCase
+{
+ public function testPluginConnectsToPop3HostBeforeTransportStarts()
+ {
+ $connection = $this->_createConnection();
+ $connection->expects($this->once())
+ ->method('connect');
+
+ $plugin = $this->_createPlugin('pop.host.tld', 110);
+ $plugin->setConnection($connection);
+
+ $transport = $this->_createTransport();
+ $evt = $this->_createTransportChangeEvent($transport);
+
+ $plugin->beforeTransportStarted($evt);
+ }
+
+ public function testPluginDisconnectsFromPop3HostBeforeTransportStarts()
+ {
+ $connection = $this->_createConnection();
+ $connection->expects($this->once())
+ ->method('disconnect');
+
+ $plugin = $this->_createPlugin('pop.host.tld', 110);
+ $plugin->setConnection($connection);
+
+ $transport = $this->_createTransport();
+ $evt = $this->_createTransportChangeEvent($transport);
+
+ $plugin->beforeTransportStarted($evt);
+ }
+
+ public function testPluginDoesNotConnectToSmtpIfBoundToDifferentTransport()
+ {
+ $connection = $this->_createConnection();
+ $connection->expects($this->never())
+ ->method('disconnect');
+ $connection->expects($this->never())
+ ->method('connect');
+
+ $smtp = $this->_createTransport();
+
+ $plugin = $this->_createPlugin('pop.host.tld', 110);
+ $plugin->setConnection($connection);
+ $plugin->bindSmtp($smtp);
+
+ $transport = $this->_createTransport();
+ $evt = $this->_createTransportChangeEvent($transport);
+
+ $plugin->beforeTransportStarted($evt);
+ }
+
+ public function testPluginCanBindToSpecificTransport()
+ {
+ $connection = $this->_createConnection();
+ $connection->expects($this->once())
+ ->method('connect');
+
+ $smtp = $this->_createTransport();
+
+ $plugin = $this->_createPlugin('pop.host.tld', 110);
+ $plugin->setConnection($connection);
+ $plugin->bindSmtp($smtp);
+
+ $evt = $this->_createTransportChangeEvent($smtp);
+
+ $plugin->beforeTransportStarted($evt);
+ }
+
+ private function _createTransport()
+ {
+ return $this->getMockBuilder('Swift_Transport')->getMock();
+ }
+
+ private function _createTransportChangeEvent($transport)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getSource')
+ ->will($this->returnValue($transport));
+ $evt->expects($this->any())
+ ->method('getTransport')
+ ->will($this->returnValue($transport));
+
+ return $evt;
+ }
+
+ public function _createConnection()
+ {
+ return $this->getMockBuilder('Swift_Plugins_Pop_Pop3Connection')->getMock();
+ }
+
+ public function _createPlugin($host, $port, $crypto = null)
+ {
+ return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php
new file mode 100644
index 0000000..bfd5669
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php
@@ -0,0 +1,183 @@
+<?php
+
+class Swift_Plugins_RedirectingPluginTest extends \PHPUnit_Framework_TestCase
+{
+ public function testRecipientCanBeSetAndFetched()
+ {
+ $plugin = new Swift_Plugins_RedirectingPlugin('fabien@example.com');
+ $this->assertEquals('fabien@example.com', $plugin->getRecipient());
+ $plugin->setRecipient('chris@example.com');
+ $this->assertEquals('chris@example.com', $plugin->getRecipient());
+ }
+
+ public function testPluginChangesRecipients()
+ {
+ $message = Swift_Message::newInstance()
+ ->setSubject('...')
+ ->setFrom(array('john@example.com' => 'John Doe'))
+ ->setTo($to = array(
+ 'fabien-to@example.com' => 'Fabien (To)',
+ 'chris-to@example.com' => 'Chris (To)',
+ ))
+ ->setCc($cc = array(
+ 'fabien-cc@example.com' => 'Fabien (Cc)',
+ 'chris-cc@example.com' => 'Chris (Cc)',
+ ))
+ ->setBcc($bcc = array(
+ 'fabien-bcc@example.com' => 'Fabien (Bcc)',
+ 'chris-bcc@example.com' => 'Chris (Bcc)',
+ ))
+ ->setBody('...')
+ ;
+
+ $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com');
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), array('god@example.com' => ''));
+ $this->assertEquals($message->getCc(), array());
+ $this->assertEquals($message->getBcc(), array());
+
+ $plugin->sendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), $to);
+ $this->assertEquals($message->getCc(), $cc);
+ $this->assertEquals($message->getBcc(), $bcc);
+ }
+
+ public function testPluginRespectsUnsetToList()
+ {
+ $message = Swift_Message::newInstance()
+ ->setSubject('...')
+ ->setFrom(array('john@example.com' => 'John Doe'))
+ ->setCc($cc = array(
+ 'fabien-cc@example.com' => 'Fabien (Cc)',
+ 'chris-cc@example.com' => 'Chris (Cc)',
+ ))
+ ->setBcc($bcc = array(
+ 'fabien-bcc@example.com' => 'Fabien (Bcc)',
+ 'chris-bcc@example.com' => 'Chris (Bcc)',
+ ))
+ ->setBody('...')
+ ;
+
+ $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com');
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), array('god@example.com' => ''));
+ $this->assertEquals($message->getCc(), array());
+ $this->assertEquals($message->getBcc(), array());
+
+ $plugin->sendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), array());
+ $this->assertEquals($message->getCc(), $cc);
+ $this->assertEquals($message->getBcc(), $bcc);
+ }
+
+ public function testPluginRespectsAWhitelistOfPatterns()
+ {
+ $message = Swift_Message::newInstance()
+ ->setSubject('...')
+ ->setFrom(array('john@example.com' => 'John Doe'))
+ ->setTo($to = array(
+ 'fabien-to@example.com' => 'Fabien (To)',
+ 'chris-to@example.com' => 'Chris (To)',
+ 'lars-to@internal.com' => 'Lars (To)',
+ ))
+ ->setCc($cc = array(
+ 'fabien-cc@example.com' => 'Fabien (Cc)',
+ 'chris-cc@example.com' => 'Chris (Cc)',
+ 'lars-cc@internal.org' => 'Lars (Cc)',
+ ))
+ ->setBcc($bcc = array(
+ 'fabien-bcc@example.com' => 'Fabien (Bcc)',
+ 'chris-bcc@example.com' => 'Chris (Bcc)',
+ 'john-bcc@example.org' => 'John (Bcc)',
+ ))
+ ->setBody('...')
+ ;
+
+ $recipient = 'god@example.com';
+ $patterns = array('/^.*@internal.[a-z]+$/', '/^john-.*$/');
+
+ $plugin = new Swift_Plugins_RedirectingPlugin($recipient, $patterns);
+
+ $this->assertEquals($recipient, $plugin->getRecipient());
+ $this->assertEquals($plugin->getWhitelist(), $patterns);
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), array('lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null));
+ $this->assertEquals($message->getCc(), array('lars-cc@internal.org' => 'Lars (Cc)'));
+ $this->assertEquals($message->getBcc(), array('john-bcc@example.org' => 'John (Bcc)'));
+
+ $plugin->sendPerformed($evt);
+
+ $this->assertEquals($message->getTo(), $to);
+ $this->assertEquals($message->getCc(), $cc);
+ $this->assertEquals($message->getBcc(), $bcc);
+ }
+
+ public function testArrayOfRecipientsCanBeExplicitlyDefined()
+ {
+ $message = Swift_Message::newInstance()
+ ->setSubject('...')
+ ->setFrom(array('john@example.com' => 'John Doe'))
+ ->setTo(array(
+ 'fabien@example.com' => 'Fabien',
+ 'chris@example.com' => 'Chris (To)',
+ 'lars-to@internal.com' => 'Lars (To)',
+ ))
+ ->setCc(array(
+ 'fabien@example.com' => 'Fabien',
+ 'chris-cc@example.com' => 'Chris (Cc)',
+ 'lars-cc@internal.org' => 'Lars (Cc)',
+ ))
+ ->setBcc(array(
+ 'fabien@example.com' => 'Fabien',
+ 'chris-bcc@example.com' => 'Chris (Bcc)',
+ 'john-bcc@example.org' => 'John (Bcc)',
+ ))
+ ->setBody('...')
+ ;
+
+ $recipients = array('god@example.com', 'fabien@example.com');
+ $patterns = array('/^.*@internal.[a-z]+$/');
+
+ $plugin = new Swift_Plugins_RedirectingPlugin($recipients, $patterns);
+
+ $evt = $this->_createSendEvent($message);
+
+ $plugin->beforeSendPerformed($evt);
+
+ $this->assertEquals(
+ $message->getTo(),
+ array('fabien@example.com' => 'Fabien', 'lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null)
+ );
+ $this->assertEquals(
+ $message->getCc(),
+ array('fabien@example.com' => 'Fabien', 'lars-cc@internal.org' => 'Lars (Cc)')
+ );
+ $this->assertEquals($message->getBcc(), array('fabien@example.com' => 'Fabien'));
+ }
+
+ private function _createSendEvent(Swift_Mime_Message $message)
+ {
+ $evt = $this->getMockBuilder('Swift_Events_SendEvent')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $evt->expects($this->any())
+ ->method('getMessage')
+ ->will($this->returnValue($message));
+
+ return $evt;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php
new file mode 100644
index 0000000..5ba5d5c
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php
@@ -0,0 +1,86 @@
+<?php
+
+class Swift_Plugins_ReporterPluginTest extends \SwiftMailerTestCase
+{
+ public function testReportingPasses()
+ {
+ $message = $this->_createMessage();
+ $evt = $this->_createSendEvent();
+ $reporter = $this->_createReporter();
+
+ $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
+ $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
+ $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array());
+ $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
+
+ $plugin = new Swift_Plugins_ReporterPlugin($reporter);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReportingFailedTo()
+ {
+ $message = $this->_createMessage();
+ $evt = $this->_createSendEvent();
+ $reporter = $this->_createReporter();
+
+ $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo', 'zip@button' => 'Zip'));
+ $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
+ $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
+ $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
+ $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);
+
+ $plugin = new Swift_Plugins_ReporterPlugin($reporter);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReportingFailedCc()
+ {
+ $message = $this->_createMessage();
+ $evt = $this->_createSendEvent();
+ $reporter = $this->_createReporter();
+
+ $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
+ $message->shouldReceive('getCc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test'));
+ $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
+ $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
+ $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
+ $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);
+ $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS);
+
+ $plugin = new Swift_Plugins_ReporterPlugin($reporter);
+ $plugin->sendPerformed($evt);
+ }
+
+ public function testReportingFailedBcc()
+ {
+ $message = $this->_createMessage();
+ $evt = $this->_createSendEvent();
+ $reporter = $this->_createReporter();
+
+ $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
+ $message->shouldReceive('getBcc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test'));
+ $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
+ $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
+ $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
+ $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);
+ $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS);
+
+ $plugin = new Swift_Plugins_ReporterPlugin($reporter);
+ $plugin->sendPerformed($evt);
+ }
+
+ private function _createMessage()
+ {
+ return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ }
+
+ private function _createSendEvent()
+ {
+ return $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ }
+
+ private function _createReporter()
+ {
+ return $this->getMockery('Swift_Plugins_Reporter')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php
new file mode 100644
index 0000000..20aae57
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php
@@ -0,0 +1,64 @@
+<?php
+
+class Swift_Plugins_Reporters_HitReporterTest extends \PHPUnit_Framework_TestCase
+{
+ private $_hitReporter;
+ private $_message;
+
+ protected function setUp()
+ {
+ $this->_hitReporter = new Swift_Plugins_Reporters_HitReporter();
+ $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+ }
+
+ public function testReportingFail()
+ {
+ $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->assertEquals(array('foo@bar.tld'),
+ $this->_hitReporter->getFailedRecipients()
+ );
+ }
+
+ public function testMultipleReports()
+ {
+ $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->_hitReporter->notify($this->_message, 'zip@button',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->assertEquals(array('foo@bar.tld', 'zip@button'),
+ $this->_hitReporter->getFailedRecipients()
+ );
+ }
+
+ public function testReportingPassIsIgnored()
+ {
+ $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->_hitReporter->notify($this->_message, 'zip@button',
+ Swift_Plugins_Reporter::RESULT_PASS
+ );
+ $this->assertEquals(array('foo@bar.tld'),
+ $this->_hitReporter->getFailedRecipients()
+ );
+ }
+
+ public function testBufferCanBeCleared()
+ {
+ $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->_hitReporter->notify($this->_message, 'zip@button',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $this->assertEquals(array('foo@bar.tld', 'zip@button'),
+ $this->_hitReporter->getFailedRecipients()
+ );
+ $this->_hitReporter->clear();
+ $this->assertEquals(array(), $this->_hitReporter->getFailedRecipients());
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php
new file mode 100644
index 0000000..fb0bc97
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php
@@ -0,0 +1,54 @@
+<?php
+
+class Swift_Plugins_Reporters_HtmlReporterTest extends \PHPUnit_Framework_TestCase
+{
+ private $_html;
+ private $_message;
+
+ protected function setUp()
+ {
+ $this->_html = new Swift_Plugins_Reporters_HtmlReporter();
+ $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
+ }
+
+ public function testReportingPass()
+ {
+ ob_start();
+ $this->_html->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_PASS
+ );
+ $html = ob_get_clean();
+
+ $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass');
+ $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address');
+ }
+
+ public function testReportingFail()
+ {
+ ob_start();
+ $this->_html->notify($this->_message, 'zip@button',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $html = ob_get_clean();
+
+ $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail');
+ $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address');
+ }
+
+ public function testMultipleReports()
+ {
+ ob_start();
+ $this->_html->notify($this->_message, 'foo@bar.tld',
+ Swift_Plugins_Reporter::RESULT_PASS
+ );
+ $this->_html->notify($this->_message, 'zip@button',
+ Swift_Plugins_Reporter::RESULT_FAIL
+ );
+ $html = ob_get_clean();
+
+ $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass');
+ $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address');
+ $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail');
+ $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php
new file mode 100644
index 0000000..309f506
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php
@@ -0,0 +1,102 @@
+<?php
+
+class Swift_Plugins_ThrottlerPluginTest extends \SwiftMailerTestCase
+{
+ public function testBytesPerMinuteThrottling()
+ {
+ $sleeper = $this->_createSleeper();
+ $timer = $this->_createTimer();
+
+ //10MB/min
+ $plugin = new Swift_Plugins_ThrottlerPlugin(
+ 10000000, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE,
+ $sleeper, $timer
+ );
+
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(0);
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 0.6
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 1.2 (sleep 1)
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 1.8
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2.4 (sleep 1)
+ $sleeper->shouldReceive('sleep')->twice()->with(1);
+
+ //10,000,000 bytes per minute
+ //100,000 bytes per email
+
+ // .: (10,000,000/100,000)/60 emails per second = 1.667 emais/sec
+
+ $message = $this->_createMessageWithByteCount(100000); //100KB
+
+ $evt = $this->_createSendEvent($message);
+
+ for ($i = 0; $i < 5; ++$i) {
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+ }
+
+ public function testMessagesPerMinuteThrottling()
+ {
+ $sleeper = $this->_createSleeper();
+ $timer = $this->_createTimer();
+
+ //60/min
+ $plugin = new Swift_Plugins_ThrottlerPlugin(
+ 60, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE,
+ $sleeper, $timer
+ );
+
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(0);
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(0); //expected 1 (sleep 1)
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 3 (sleep 1)
+ $timer->shouldReceive('getTimestamp')->once()->andReturn(4); //expected 4
+ $sleeper->shouldReceive('sleep')->twice()->with(1);
+
+ //60 messages per minute
+ //1 message per second
+
+ $message = $this->_createMessageWithByteCount(10);
+
+ $evt = $this->_createSendEvent($message);
+
+ for ($i = 0; $i < 5; ++$i) {
+ $plugin->beforeSendPerformed($evt);
+ $plugin->sendPerformed($evt);
+ }
+ }
+
+ private function _createSleeper()
+ {
+ return $this->getMockery('Swift_Plugins_Sleeper');
+ }
+
+ private function _createTimer()
+ {
+ return $this->getMockery('Swift_Plugins_Timer');
+ }
+
+ private function _createMessageWithByteCount($bytes)
+ {
+ $msg = $this->getMockery('Swift_Mime_Message');
+ $msg->shouldReceive('toByteStream')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function ($is) use ($bytes) {
+ for ($i = 0; $i < $bytes; ++$i) {
+ $is->write('x');
+ }
+ });
+
+ return $msg;
+ }
+
+ private function _createSendEvent($message)
+ {
+ $evt = $this->getMockery('Swift_Events_SendEvent');
+ $evt->shouldReceive('getMessage')
+ ->zeroOrMoreTimes()
+ ->andReturn($message);
+
+ return $evt;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php
new file mode 100644
index 0000000..5eda223
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php
@@ -0,0 +1,225 @@
+<?php
+
+class Swift_Signers_DKIMSignerTest extends \SwiftMailerTestCase
+{
+ protected function setUp()
+ {
+ if (PHP_VERSION_ID < 50400 && !defined('OPENSSL_ALGO_SHA256')) {
+ $this->markTestSkipped('skipping because of https://bugs.php.net/bug.php?id=61421');
+ }
+ }
+
+ public function testBasicSigningHeaderManipulation()
+ {
+ $headers = $this->_createHeaders();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ /* @var $signer Swift_Signers_HeaderSigner */
+ $altered = $signer->getAlteredHeaders();
+ $signer->reset();
+ // Headers
+ $signer->setHeaders($headers);
+ // Body
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ // Signing
+ $signer->addSignature($headers);
+ }
+
+ // SHA1 Signing
+ public function testSigningSHA1()
+ {
+ $headerSet = $this->_createHeaderSet();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ $signer->setHashAlgorithm('rsa-sha1');
+ $signer->setSignatureTimestamp('1299879181');
+ $altered = $signer->getAlteredHeaders();
+ $this->assertEquals(array('DKIM-Signature'), $altered);
+ $signer->reset();
+ $signer->setHeaders($headerSet);
+ $this->assertFalse($headerSet->has('DKIM-Signature'));
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ $signer->addSignature($headerSet);
+ $this->assertTrue($headerSet->has('DKIM-Signature'));
+ $dkim = $headerSet->getAll('DKIM-Signature');
+ $sig = reset($dkim);
+ $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha1; bh=wlbYcY9O9OPInGJ4D0E/rGsvMLE=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=RMSNelzM2O5MAAnMjT3G3/VF36S3DGJXoPCXR001F1WDReu0prGphWjuzK/m6V1pwqQL8cCNg Hi74mTx2bvyAvmkjvQtJf1VMUOCc9WHGcm1Yec66I3ZWoNMGSWZ1EKAm2CtTzyG0IFw4ml9DI wSkyAFxlgicckDD6FibhqwX4w=');
+ }
+
+ // SHA256 Signing
+ public function testSigning256()
+ {
+ $headerSet = $this->_createHeaderSet();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ $signer->setHashAlgorithm('rsa-sha256');
+ $signer->setSignatureTimestamp('1299879181');
+ $altered = $signer->getAlteredHeaders();
+ $this->assertEquals(array('DKIM-Signature'), $altered);
+ $signer->reset();
+ $signer->setHeaders($headerSet);
+ $this->assertFalse($headerSet->has('DKIM-Signature'));
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ $signer->addSignature($headerSet);
+ $this->assertTrue($headerSet->has('DKIM-Signature'));
+ $dkim = $headerSet->getAll('DKIM-Signature');
+ $sig = reset($dkim);
+ $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=jqPmieHzF5vR9F4mXCAkowuphpO4iJ8IAVuioh1BFZ3VITXZj5jlOFxULJMBiiApm2keJirnh u4mzogj444QkpT3lJg8/TBGAYQPdcvkG3KC0jdyN6QpSgpITBJG2BwWa+keXsv2bkQgLRAzNx qRhP45vpHCKun0Tg9LrwW/KCg=');
+ }
+
+ // Relaxed/Relaxed Hash Signing
+ public function testSigningRelaxedRelaxed256()
+ {
+ $headerSet = $this->_createHeaderSet();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ $signer->setHashAlgorithm('rsa-sha256');
+ $signer->setSignatureTimestamp('1299879181');
+ $signer->setBodyCanon('relaxed');
+ $signer->setHeaderCanon('relaxed');
+ $altered = $signer->getAlteredHeaders();
+ $this->assertEquals(array('DKIM-Signature'), $altered);
+ $signer->reset();
+ $signer->setHeaders($headerSet);
+ $this->assertFalse($headerSet->has('DKIM-Signature'));
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ $signer->addSignature($headerSet);
+ $this->assertTrue($headerSet->has('DKIM-Signature'));
+ $dkim = $headerSet->getAll('DKIM-Signature');
+ $sig = reset($dkim);
+ $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed/relaxed; t=1299879181; b=gzOI+PX6HpZKQFzwwmxzcVJsyirdLXOS+4pgfCpVHQIdqYusKLrhlLeFBTNoz75HrhNvGH6T0 Rt3w5aTqkrWfUuAEYt0Ns14GowLM7JojaFN+pZ4eYnRB3CBBgW6fee4NEMD5WPca3uS09tr1E 10RYh9ILlRtl+84sovhx5id3Y=');
+ }
+
+ // Relaxed/Simple Hash Signing
+ public function testSigningRelaxedSimple256()
+ {
+ $headerSet = $this->_createHeaderSet();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ $signer->setHashAlgorithm('rsa-sha256');
+ $signer->setSignatureTimestamp('1299879181');
+ $signer->setHeaderCanon('relaxed');
+ $altered = $signer->getAlteredHeaders();
+ $this->assertEquals(array('DKIM-Signature'), $altered);
+ $signer->reset();
+ $signer->setHeaders($headerSet);
+ $this->assertFalse($headerSet->has('DKIM-Signature'));
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ $signer->addSignature($headerSet);
+ $this->assertTrue($headerSet->has('DKIM-Signature'));
+ $dkim = $headerSet->getAll('DKIM-Signature');
+ $sig = reset($dkim);
+ $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed; t=1299879181; b=dLPJNec5v81oelyzGOY0qPqTlGnQeNfUNBOrV/JKbStr3NqWGI9jH4JAe2YvO2V32lfPNoby1 4MMzZ6EPkaZkZDDSPa+53YbCPQAlqiD9QZZIUe2UNM33HN8yAMgiWEF5aP7MbQnxeVZMfVLEl 9S8qOImu+K5JZqhQQTL0dgLwA=');
+ }
+
+ // Simple/Relaxed Hash Signing
+ public function testSigningSimpleRelaxed256()
+ {
+ $headerSet = $this->_createHeaderSet();
+ $messageContent = 'Hello World';
+ $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
+ $signer->setHashAlgorithm('rsa-sha256');
+ $signer->setSignatureTimestamp('1299879181');
+ $signer->setBodyCanon('relaxed');
+ $altered = $signer->getAlteredHeaders();
+ $this->assertEquals(array('DKIM-Signature'), $altered);
+ $signer->reset();
+ $signer->setHeaders($headerSet);
+ $this->assertFalse($headerSet->has('DKIM-Signature'));
+ $signer->startBody();
+ $signer->write($messageContent);
+ $signer->endBody();
+ $signer->addSignature($headerSet);
+ $this->assertTrue($headerSet->has('DKIM-Signature'));
+ $dkim = $headerSet->getAll('DKIM-Signature');
+ $sig = reset($dkim);
+ $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=simple/relaxed; t=1299879181; b=M5eomH/zamyzix9kOes+6YLzQZxuJdBP4x3nP9zF2N26eMLG2/cBKbnNyqiOTDhJdYfWPbLIa 1CWnjST0j5p4CpeOkGYuiE+M4TWEZwhRmRWootlPO3Ii6XpbBJKFk1o9zviS7OmXblUUE4aqb yRSIMDhtLdCK5GlaCneFLN7RQ=');
+ }
+
+ private function _createHeaderSet()
+ {
+ $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream());
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
+ $grammar = new Swift_Mime_Grammar();
+ $headers = new Swift_Mime_SimpleHeaderSet(new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar));
+
+ return $headers;
+ }
+
+ /**
+ * @return Swift_Mime_Headers
+ */
+ private function _createHeaders()
+ {
+ $x = 0;
+ $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream());
+ $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
+ $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
+
+ $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
+ $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
+ $grammar = new Swift_Mime_Grammar();
+ $headerFactory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar);
+ $headers = $this->getMockery('Swift_Mime_HeaderSet');
+
+ $headers->shouldReceive('listAll')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('From', 'To', 'Date', 'Subject'));
+ $headers->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->with('From')
+ ->andReturn(true);
+ $headers->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->with('From')
+ ->andReturn(array($headerFactory->createMailboxHeader('From', 'test@test.test')));
+ $headers->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->with('To')
+ ->andReturn(true);
+ $headers->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->with('To')
+ ->andReturn(array($headerFactory->createMailboxHeader('To', 'test@test.test')));
+ $headers->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->with('Date')
+ ->andReturn(true);
+ $headers->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->with('Date')
+ ->andReturn(array($headerFactory->createTextHeader('Date', 'Fri, 11 Mar 2011 20:56:12 +0000 (GMT)')));
+ $headers->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->with('Subject')
+ ->andReturn(true);
+ $headers->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->with('Subject')
+ ->andReturn(array($headerFactory->createTextHeader('Subject', 'Foo Bar Text Message')));
+ $headers->shouldReceive('addTextHeader')
+ ->zeroOrMoreTimes()
+ ->with('DKIM-Signature', \Mockery::any())
+ ->andReturn(true);
+ $headers->shouldReceive('getAll')
+ ->zeroOrMoreTimes()
+ ->with('DKIM-Signature')
+ ->andReturn(array($headerFactory->createTextHeader('DKIM-Signature', 'Foo Bar Text Message')));
+
+ return $headers;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php
new file mode 100644
index 0000000..ce99bc6
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @todo
+ */
+class Swift_Signers_OpenDKIMSignerTest extends \SwiftMailerTestCase
+{
+ protected function setUp()
+ {
+ if (!extension_loaded('opendkim')) {
+ $this->markTestSkipped(
+ 'Need OpenDKIM extension run these tests.'
+ );
+ }
+ }
+
+ public function testBasicSigningHeaderManipulation()
+ {
+ }
+
+ // Default Signing
+ public function testSigningDefaults()
+ {
+ }
+
+ // SHA256 Signing
+ public function testSigning256()
+ {
+ }
+
+ // Relaxed/Relaxed Hash Signing
+ public function testSigningRelaxedRelaxed256()
+ {
+ }
+
+ // Relaxed/Simple Hash Signing
+ public function testSigningRelaxedSimple256()
+ {
+ }
+
+ // Simple/Relaxed Hash Signing
+ public function testSigningSimpleRelaxed256()
+ {
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php
new file mode 100644
index 0000000..5069c1f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php
@@ -0,0 +1,554 @@
+<?php
+
+class Swift_Signers_SMimeSignerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var Swift_StreamFilters_StringReplacementFilterFactory
+ */
+ protected $replacementFactory;
+
+ protected $samplesDir;
+
+ protected function setUp()
+ {
+ $this->replacementFactory = Swift_DependencyContainer::getInstance()
+ ->lookup('transport.replacementfactory');
+
+ $this->samplesDir = str_replace('\\', '/', realpath(__DIR__.'/../../../_samples/')).'/';
+ }
+
+ public function testUnSingedMessage()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $this->assertEquals('Here is the message itself', $message->getBody());
+ }
+
+ public function testSingedMessage()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
+ $message->attachSigner($signer);
+
+ $messageStream = $this->newFilteredStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!($boundary = $this->getBoundary($headers['content-type']))) {
+ return false;
+ }
+
+ $expectedBody = <<<OEL
+This is an S/MIME signed message
+
+--$boundary
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+Here is the message itself
+--$boundary
+Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime\.p7s"
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+--$boundary--
+OEL;
+ $this->assertValidVerify($expectedBody, $messageStream);
+ unset($messageStream);
+ }
+
+ public function testSingedMessageExtraCerts()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign2.crt', $this->samplesDir.'smime/sign2.key', PKCS7_DETACHED, $this->samplesDir.'smime/intermediate.crt');
+ $message->attachSigner($signer);
+
+ $messageStream = $this->newFilteredStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!($boundary = $this->getBoundary($headers['content-type']))) {
+ return false;
+ }
+
+ $expectedBody = <<<OEL
+This is an S/MIME signed message
+
+--$boundary
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+Here is the message itself
+--$boundary
+Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime\.p7s"
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+--$boundary--
+OEL;
+ $this->assertValidVerify($expectedBody, $messageStream);
+ unset($messageStream);
+ }
+
+ public function testSingedMessageBinary()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key', PKCS7_BINARY);
+ $message->attachSigner($signer);
+
+ $messageStream = $this->newFilteredStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=signed\-data;#', $headers['content-type'])) {
+ $this->fail('Content-type does not match.');
+
+ return false;
+ }
+
+ $this->assertEquals($headers['content-transfer-encoding'], 'base64');
+ $this->assertEquals($headers['content-disposition'], 'attachment; filename="smime.p7m"');
+
+ $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
+
+ $messageStreamClean = $this->newFilteredStream();
+
+ $this->assertValidVerify($expectedBody, $messageStream);
+ unset($messageStreamClean, $messageStream);
+ }
+
+ public function testSingedMessageWithAttachments()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $message->attach(Swift_Attachment::fromPath($this->samplesDir.'/files/textfile.zip'));
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
+ $message->attachSigner($signer);
+
+ $messageStream = $this->newFilteredStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!($boundary = $this->getBoundary($headers['content-type']))) {
+ return false;
+ }
+
+ $expectedBody = <<<OEL
+This is an S/MIME signed message
+
+--$boundary
+Content-Type: multipart/mixed;
+ boundary="([a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?\\ ]{0,69}[a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?])"
+
+
+--\\1
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+Here is the message itself
+
+--\\1
+Content-Type: application/zip; name=textfile\\.zip
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=textfile\\.zip
+
+UEsDBAoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMABUAdGV4dGZpbGUudHh0VVQJAAN3vr5Hd76\\+R1V4
+BAD1AfUBVGhpcyBpcyBwYXJ0IG9mIGEgU3dpZnQgTWFpbGVyIHY0IHNtb2tlIHRlc3QuClBLAQIX
+AwoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMAA0AAAAAAAEAAACkgQAAAAB0ZXh0ZmlsZS50eHRVVAUA
+A3e\\+vkdVeAAAUEsFBgAAAAABAAEARwAAAG0AAAAAAA==
+
+--\\1--
+
+--$boundary
+Content-Type: application/(x\-)?pkcs7-signature; name="smime\\.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime\\.p7s"
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+--$boundary--
+OEL;
+
+ $this->assertValidVerify($expectedBody, $messageStream);
+ unset($messageStream);
+ }
+
+ public function testEncryptedMessage()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $originalMessage = $this->cleanMessage($message->toString());
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
+ $message->attachSigner($signer);
+
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
+ $this->fail('Content-type does not match.');
+
+ return false;
+ }
+
+ $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
+
+ $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
+ $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
+ }
+
+ $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
+ unset($decryptedMessageStream, $messageStream);
+ }
+
+ public function testEncryptedMessageWithMultipleCerts()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $originalMessage = $this->cleanMessage($message->toString());
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setEncryptCertificate(array($this->samplesDir.'smime/encrypt.crt', $this->samplesDir.'smime/encrypt2.crt'));
+ $message->attachSigner($signer);
+
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
+ $this->fail('Content-type does not match.');
+
+ return false;
+ }
+
+ $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
+
+ $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
+ $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
+ }
+
+ $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
+ unset($decryptedMessageStream);
+
+ $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt2.crt', array('file://'.$this->samplesDir.'smime/encrypt2.key', 'swift'))) {
+ $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
+ }
+
+ $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
+ unset($decryptedMessageStream, $messageStream);
+ }
+
+ public function testSignThenEncryptedMessage()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $signer = new Swift_Signers_SMimeSigner();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
+ $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
+ $message->attachSigner($signer);
+
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
+ $this->fail('Content-type does not match.');
+
+ return false;
+ }
+
+ $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
+
+ $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
+ $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
+ }
+
+ $entityString = $decryptedMessageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!($boundary = $this->getBoundary($headers['content-type']))) {
+ return false;
+ }
+
+ $expectedBody = <<<OEL
+This is an S/MIME signed message
+
+--$boundary
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+Here is the message itself
+--$boundary
+Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime\.p7s"
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+--$boundary--
+OEL;
+
+ if (!$this->assertValidVerify($expectedBody, $decryptedMessageStream)) {
+ return false;
+ }
+
+ unset($decryptedMessageStream, $messageStream);
+ }
+
+ public function testEncryptThenSignMessage()
+ {
+ $message = Swift_SignedMessage::newInstance('Wonderful Subject')
+ ->setFrom(array('john@doe.com' => 'John Doe'))
+ ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
+ ->setBody('Here is the message itself');
+
+ $originalMessage = $this->cleanMessage($message->toString());
+
+ $signer = Swift_Signers_SMimeSigner::newInstance();
+ $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
+ $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
+ $signer->setSignThenEncrypt(false);
+ $message->attachSigner($signer);
+
+ $messageStream = $this->newFilteredStream();
+ $message->toByteStream($messageStream);
+ $messageStream->commit();
+
+ $entityString = $messageStream->getContent();
+ $headers = self::getHeadersOfMessage($entityString);
+
+ if (!($boundary = $this->getBoundary($headers['content-type']))) {
+ return false;
+ }
+
+ $expectedBody = <<<OEL
+This is an S/MIME signed message
+
+--$boundary
+(?P<encrypted_message>MIME-Version: 1\.0
+Content-Disposition: attachment; filename="smime\.p7m"
+Content-Type: application/(x\-)?pkcs7-mime; smime-type=enveloped-data; name="smime\.p7m"
+Content-Transfer-Encoding: base64
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+
+)--$boundary
+Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime\.p7s"
+
+(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
+
+--$boundary--
+OEL;
+
+ if (!$this->assertValidVerify($expectedBody, $messageStream)) {
+ return false;
+ }
+
+ $expectedBody = str_replace("\n", "\r\n", $expectedBody);
+ if (!preg_match('%'.$expectedBody.'*%m', $entityString, $entities)) {
+ $this->fail('Failed regex match.');
+
+ return false;
+ }
+
+ $messageStreamClean = new Swift_ByteStream_TemporaryFileByteStream();
+ $messageStreamClean->write($entities['encrypted_message']);
+
+ $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
+
+ if (!openssl_pkcs7_decrypt($messageStreamClean->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
+ $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
+ }
+
+ $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
+ unset($messageStreamClean, $messageStream, $decryptedMessageStream);
+ }
+
+ protected function assertValidVerify($expected, Swift_ByteStream_TemporaryFileByteStream $messageStream)
+ {
+ $actual = $messageStream->getContent();
+
+ // File is UNIX encoded so convert them to correct line ending
+ $expected = str_replace("\n", "\r\n", $expected);
+
+ $actual = trim(self::getBodyOfMessage($actual));
+ if (!$this->assertRegExp('%^'.$expected.'$\s*%m', $actual)) {
+ return false;
+ }
+
+ $opensslOutput = new Swift_ByteStream_TemporaryFileByteStream();
+ $verify = openssl_pkcs7_verify($messageStream->getPath(), null, $opensslOutput->getPath(), array($this->samplesDir.'smime/ca.crt'));
+
+ if (false === $verify) {
+ $this->fail('Verification of the message failed.');
+
+ return false;
+ } elseif (-1 === $verify) {
+ $this->fail(sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string()));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function getBoundary($contentType)
+ {
+ if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $contentType, $contentTypeData)) {
+ $this->fail('Failed to find Boundary parameter');
+
+ return false;
+ }
+
+ return trim($contentTypeData[1], '"');
+ }
+
+ protected function newFilteredStream()
+ {
+ $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
+ $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
+ $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
+
+ return $messageStream;
+ }
+
+ protected static function getBodyOfMessage($message)
+ {
+ return substr($message, strpos($message, "\r\n\r\n"));
+ }
+
+ /**
+ * Strips of the sender headers and Mime-Version.
+ *
+ * @param Swift_ByteStream_TemporaryFileByteStream $messageStream
+ * @param Swift_ByteStream_TemporaryFileByteStream $inputStream
+ */
+ protected function cleanMessage($content)
+ {
+ $newContent = '';
+
+ $headers = self::getHeadersOfMessage($content);
+ foreach ($headers as $headerName => $value) {
+ if (!in_array($headerName, array('content-type', 'content-transfer-encoding', 'content-disposition'))) {
+ continue;
+ }
+
+ $headerName = explode('-', $headerName);
+ $headerName = array_map('ucfirst', $headerName);
+ $headerName = implode('-', $headerName);
+
+ if (strlen($value) > 62) {
+ $value = wordwrap($value, 62, "\n ");
+ }
+
+ $newContent .= "$headerName: $value\r\n";
+ }
+
+ return $newContent."\r\n".ltrim(self::getBodyOfMessage($content));
+ }
+
+ /**
+ * Returns the headers of the message.
+ *
+ * Header-names are lowercase.
+ *
+ * @param string $message
+ *
+ * @return array
+ */
+ protected static function getHeadersOfMessage($message)
+ {
+ $headersPosEnd = strpos($message, "\r\n\r\n");
+ $headerData = substr($message, 0, $headersPosEnd);
+ $headerLines = explode("\r\n", $headerData);
+
+ if (empty($headerLines)) {
+ return array();
+ }
+
+ $headers = array();
+
+ foreach ($headerLines as $headerLine) {
+ if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
+ $headers[$currentHeaderName] .= ' '.trim($headerLine);
+ continue;
+ }
+
+ $header = explode(':', $headerLine, 2);
+ $currentHeaderName = strtolower($header[0]);
+ $headers[$currentHeaderName] = trim($header[1]);
+ }
+
+ return $headers;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php
new file mode 100644
index 0000000..c85bdc1
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php
@@ -0,0 +1,129 @@
+<?php
+
+class Swift_StreamFilters_ByteArrayReplacementFilterTest extends \PHPUnit_Framework_TestCase
+{
+ public function testBasicReplacementsAreMade()
+ {
+ $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64));
+ $this->assertEquals(
+ array(0x59, 0x60, 0x63, 0x64, 0x65),
+ $filter->filter(array(0x59, 0x60, 0x61, 0x62, 0x65))
+ );
+ }
+
+ public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer()
+ {
+ $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64));
+ $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)),
+ '%s: Filter should buffer since 0x61 0x62 is the needle and the ending '.
+ '0x61 could be from 0x61 0x62'
+ );
+ }
+
+ public function testFilterCanMakeMultipleReplacements()
+ {
+ $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(0x63));
+ $this->assertEquals(
+ array(0x60, 0x63, 0x60, 0x63, 0x60),
+ $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60))
+ );
+ }
+
+ public function testMultipleReplacementsCanBeDifferent()
+ {
+ $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(array(0x63), array(0x64)));
+ $this->assertEquals(
+ array(0x60, 0x63, 0x60, 0x64, 0x60),
+ $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60))
+ );
+ }
+
+ public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString()
+ {
+ $filter = $this->_createFilter(array(0x0D, 0x0A), array(0x0A));
+ $this->assertFalse($filter->shouldBuffer(array(0x61, 0x62, 0x0D, 0x0A, 0x63)),
+ '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF'
+ );
+ }
+
+ public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString()
+ {
+ $filter = $this->_createFilter(array(array(0x61, 0x62), array(0x63)), array(0x64));
+ $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)),
+ '%s: Filter should buffer since 0x61 0x62 is a needle and the ending '.
+ '0x61 could be from 0x61 0x62'
+ );
+ }
+
+ public function testConvertingAllLineEndingsToCRLFWhenInputIsLF()
+ {
+ $filter = $this->_createFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ );
+
+ $this->assertEquals(
+ array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
+ $filter->filter(array(0x60, 0x0A, 0x61, 0x0A, 0x62, 0x0A, 0x63))
+ );
+ }
+
+ public function testConvertingAllLineEndingsToCRLFWhenInputIsCR()
+ {
+ $filter = $this->_createFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ );
+
+ $this->assertEquals(
+ array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
+ $filter->filter(array(0x60, 0x0D, 0x61, 0x0D, 0x62, 0x0D, 0x63))
+ );
+ }
+
+ public function testConvertingAllLineEndingsToCRLFWhenInputIsCRLF()
+ {
+ $filter = $this->_createFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ );
+
+ $this->assertEquals(
+ array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
+ $filter->filter(array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63))
+ );
+ }
+
+ public function testConvertingAllLineEndingsToCRLFWhenInputIsLFCR()
+ {
+ $filter = $this->_createFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ );
+
+ $this->assertEquals(
+ array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63),
+ $filter->filter(array(0x60, 0x0A, 0x0D, 0x61, 0x0A, 0x0D, 0x62, 0x0A, 0x0D, 0x63))
+ );
+ }
+
+ public function testConvertingAllLineEndingsToCRLFWhenInputContainsLFLF()
+ {
+ //Lighthouse Bug #23
+
+ $filter = $this->_createFilter(
+ array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
+ array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
+ );
+
+ $this->assertEquals(
+ array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63),
+ $filter->filter(array(0x60, 0x0A, 0x0A, 0x61, 0x0A, 0x0A, 0x62, 0x0A, 0x0A, 0x63))
+ );
+ }
+
+ private function _createFilter($search, $replace)
+ {
+ return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php
new file mode 100644
index 0000000..c14d5dc
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php
@@ -0,0 +1,36 @@
+<?php
+
+class Swift_StreamFilters_StringReplacementFilterFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInstancesOfStringReplacementFilterAreCreated()
+ {
+ $factory = $this->_createFactory();
+ $this->assertInstanceOf(
+ 'Swift_StreamFilters_StringReplacementFilter',
+ $factory->createFilter('a', 'b')
+ );
+ }
+
+ public function testSameInstancesAreCached()
+ {
+ $factory = $this->_createFactory();
+ $filter1 = $factory->createFilter('a', 'b');
+ $filter2 = $factory->createFilter('a', 'b');
+ $this->assertSame($filter1, $filter2, '%s: Instances should be cached');
+ }
+
+ public function testDifferingInstancesAreNotCached()
+ {
+ $factory = $this->_createFactory();
+ $filter1 = $factory->createFilter('a', 'b');
+ $filter2 = $factory->createFilter('a', 'c');
+ $this->assertNotEquals($filter1, $filter2,
+ '%s: Differing instances should not be cached'
+ );
+ }
+
+ private function _createFactory()
+ {
+ return new Swift_StreamFilters_StringReplacementFilterFactory();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php
new file mode 100644
index 0000000..681e235
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php
@@ -0,0 +1,59 @@
+<?php
+
+class Swift_StreamFilters_StringReplacementFilterTest extends \PHPUnit_Framework_TestCase
+{
+ public function testBasicReplacementsAreMade()
+ {
+ $filter = $this->_createFilter('foo', 'bar');
+ $this->assertEquals('XbarYbarZ', $filter->filter('XfooYfooZ'));
+ }
+
+ public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer()
+ {
+ $filter = $this->_createFilter('foo', 'bar');
+ $this->assertTrue($filter->shouldBuffer('XfooYf'),
+ '%s: Filter should buffer since "foo" is the needle and the ending '.
+ '"f" could be from "foo"'
+ );
+ }
+
+ public function testFilterCanMakeMultipleReplacements()
+ {
+ $filter = $this->_createFilter(array('a', 'b'), 'foo');
+ $this->assertEquals('XfooYfooZ', $filter->filter('XaYbZ'));
+ }
+
+ public function testMultipleReplacementsCanBeDifferent()
+ {
+ $filter = $this->_createFilter(array('a', 'b'), array('foo', 'zip'));
+ $this->assertEquals('XfooYzipZ', $filter->filter('XaYbZ'));
+ }
+
+ public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString()
+ {
+ $filter = $this->_createFilter("\r\n", "\n");
+ $this->assertFalse($filter->shouldBuffer("foo\r\nbar"),
+ '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF'
+ );
+ }
+
+ public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString()
+ {
+ $filter = $this->_createFilter(array('foo', 'zip'), 'bar');
+ $this->assertTrue($filter->shouldBuffer('XfooYzi'),
+ '%s: Filter should buffer since "zip" is a needle and the ending '.
+ '"zi" could be from "zip"'
+ );
+ }
+
+ public function testShouldBufferReturnsFalseOnEmptyBuffer()
+ {
+ $filter = $this->_createFilter("\r\n", "\n");
+ $this->assertFalse($filter->shouldBuffer(''));
+ }
+
+ private function _createFilter($search, $replace)
+ {
+ return new Swift_StreamFilters_StringReplacementFilter($search, $replace);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php
new file mode 100644
index 0000000..81bda4f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php
@@ -0,0 +1,558 @@
+<?php
+
+require_once __DIR__.'/AbstractSmtpTest.php';
+
+abstract class Swift_Transport_AbstractSmtpEventSupportTest extends Swift_Transport_AbstractSmtpTest
+{
+ public function testRegisterPluginLoadsPluginInEventDispatcher()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $listener = $this->getMockery('Swift_Events_EventListener');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $dispatcher->shouldReceive('bindEventListener')
+ ->once()
+ ->with($listener);
+
+ $smtp->registerPlugin($listener);
+ }
+
+ public function testSendingDispatchesBeforeSendEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $message = $this->_createMessage();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
+ $dispatcher->shouldReceive('createSendEvent')
+ ->once()
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeSendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(1, $smtp->send($message));
+ }
+
+ public function testSendingDispatchesSendEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $message = $this->_createMessage();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
+ $dispatcher->shouldReceive('createSendEvent')
+ ->once()
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'sendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(1, $smtp->send($message));
+ }
+
+ public function testSendEventCapturesFailures()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("500 Not now\r\n");
+ $dispatcher->shouldReceive('createSendEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'sendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ $evt->shouldReceive('setFailedRecipients')
+ ->once()
+ ->with(array('mark@swiftmailer.org'));
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(0, $smtp->send($message));
+ }
+
+ public function testSendEventHasResultFailedIfAllFailures()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("500 Not now\r\n");
+ $dispatcher->shouldReceive('createSendEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'sendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ $evt->shouldReceive('setResult')
+ ->once()
+ ->with(Swift_Events_SendEvent::RESULT_FAILED);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(0, $smtp->send($message));
+ }
+
+ public function testSendEventHasResultTentativeIfSomeFailures()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'mark@swiftmailer.org' => 'Mark',
+ 'chris@site.tld' => 'Chris',
+ ));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("500 Not now\r\n");
+ $dispatcher->shouldReceive('createSendEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'sendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ $evt->shouldReceive('setResult')
+ ->once()
+ ->with(Swift_Events_SendEvent::RESULT_TENTATIVE);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(1, $smtp->send($message));
+ }
+
+ public function testSendEventHasResultSuccessIfNoFailures()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'mark@swiftmailer.org' => 'Mark',
+ 'chris@site.tld' => 'Chris',
+ ));
+ $dispatcher->shouldReceive('createSendEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'sendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ $evt->shouldReceive('setResult')
+ ->once()
+ ->with(Swift_Events_SendEvent::RESULT_SUCCESS);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(2, $smtp->send($message));
+ }
+
+ public function testCancellingEventBubbleBeforeSendStopsEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('chris@swiftmailer.org' => null));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
+ $dispatcher->shouldReceive('createSendEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeSendPerformed');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(true);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(0, $smtp->send($message));
+ }
+
+ public function testStartingTransportDispatchesTransportChangeEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'transportStarted');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(false);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ }
+
+ public function testStartingTransportDispatchesBeforeTransportChangeEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeTransportStarted');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(false);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ }
+
+ public function testCancellingBubbleBeforeTransportStartStopsEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeTransportStarted');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(true);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+
+ $this->assertFalse($smtp->isStarted(),
+ '%s: Transport should not be started since event bubble was cancelled'
+ );
+ }
+
+ public function testStoppingTransportDispatchesTransportChangeEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'transportStopped');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->stop();
+ }
+
+ public function testStoppingTransportDispatchesBeforeTransportChangeEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeTransportStopped');
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->stop();
+ }
+
+ public function testCancellingBubbleBeforeTransportStoppedStopsEvent()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $hasRun = false;
+ $dispatcher->shouldReceive('createTransportChangeEvent')
+ ->atLeast()->once()
+ ->with($smtp)
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'beforeTransportStopped')
+ ->andReturnUsing(function () use (&$hasRun) {
+ $hasRun = true;
+ });
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->zeroOrMoreTimes();
+ $evt->shouldReceive('bubbleCancelled')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$hasRun) {
+ return $hasRun;
+ });
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->stop();
+
+ $this->assertTrue($smtp->isStarted(),
+ '%s: Transport should not be stopped since event bubble was cancelled'
+ );
+ }
+
+ public function testResponseEventsAreGenerated()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_ResponseEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createResponseEvent')
+ ->atLeast()->once()
+ ->with($smtp, \Mockery::any(), \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->atLeast()->once()
+ ->with($evt, 'responseReceived');
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ }
+
+ public function testCommandEventsAreGenerated()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_CommandEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $dispatcher->shouldReceive('createCommandEvent')
+ ->once()
+ ->with($smtp, \Mockery::any(), \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'commandSent');
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ }
+
+ public function testExceptionsCauseExceptionEvents()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $buf->shouldReceive('readLine')
+ ->atLeast()->once()
+ ->andReturn("503 I'm sleepy, go away!\r\n");
+ $dispatcher->shouldReceive('createTransportExceptionEvent')
+ ->zeroOrMoreTimes()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->once()
+ ->with($evt, 'exceptionThrown');
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(false);
+
+ try {
+ $smtp->start();
+ $this->fail('TransportException should be thrown on invalid response');
+ } catch (Swift_TransportException $e) {
+ }
+ }
+
+ public function testExceptionBubblesCanBeCancelled()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher(false);
+ $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
+ $smtp = $this->_getTransport($buf, $dispatcher);
+
+ $buf->shouldReceive('readLine')
+ ->atLeast()->once()
+ ->andReturn("503 I'm sleepy, go away!\r\n");
+ $dispatcher->shouldReceive('createTransportExceptionEvent')
+ ->twice()
+ ->with($smtp, \Mockery::any())
+ ->andReturn($evt);
+ $dispatcher->shouldReceive('dispatchEvent')
+ ->twice()
+ ->with($evt, 'exceptionThrown');
+ $evt->shouldReceive('bubbleCancelled')
+ ->atLeast()->once()
+ ->andReturn(true);
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ }
+
+ protected function _createEventDispatcher($stub = true)
+ {
+ return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php
new file mode 100644
index 0000000..f49b489
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php
@@ -0,0 +1,1249 @@
+<?php
+
+abstract class Swift_Transport_AbstractSmtpTest extends \SwiftMailerTestCase
+{
+ /** Abstract test method */
+ abstract protected function _getTransport($buf);
+
+ public function testStartAccepts220ServiceGreeting()
+ {
+ /* -- RFC 2821, 4.2.
+
+ Greeting = "220 " Domain [ SP text ] CRLF
+
+ -- RFC 2822, 4.3.2.
+
+ CONNECTION ESTABLISHMENT
+ S: 220
+ E: 554
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+ $smtp->start();
+ $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection');
+ } catch (Exception $e) {
+ $this->fail('220 is a valid SMTP greeting and should be accepted');
+ }
+ }
+
+ public function testBadGreetingCausesException()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("554 I'm busy\r\n");
+ $this->_finishBuffer($buf);
+ try {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+ $smtp->start();
+ $this->fail('554 greeting indicates an error and should cause an exception');
+ } catch (Exception $e) {
+ $this->assertFalse($smtp->isStarted(), '%s: start() should have failed');
+ }
+ }
+
+ public function testStartSendsHeloToInitiate()
+ {
+ /* -- RFC 2821, 3.2.
+
+ 3.2 Client Initiation
+
+ Once the server has sent the welcoming message and the client has
+ received it, the client normally sends the EHLO command to the
+ server, indicating the client's identity. In addition to opening the
+ session, use of EHLO indicates that the client is able to process
+ service extensions and requests that the server provide a list of the
+ extensions it supports. Older SMTP systems which are unable to
+ support service extensions and contemporary clients which do not
+ require service extensions in the mail session being initiated, MAY
+ use HELO instead of EHLO. Servers MUST NOT return the extended
+ EHLO-style response to a HELO command. For a particular connection
+ attempt, if the server returns a "command not recognized" response to
+ EHLO, the client SHOULD be able to fall back and send HELO.
+
+ In the EHLO command the host sending the command identifies itself;
+ the command may be interpreted as saying "Hello, I am <domain>" (and,
+ in the case of EHLO, "and I support service extension requests").
+
+ -- RFC 2281, 4.1.1.1.
+
+ ehlo = "EHLO" SP Domain CRLF
+ helo = "HELO" SP Domain CRLF
+
+ -- RFC 2821, 4.3.2.
+
+ EHLO or HELO
+ S: 250
+ E: 504, 550
+
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^HELO .*?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 ServerName'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ } catch (Exception $e) {
+ $this->fail('Starting SMTP should send HELO and accept 250 response');
+ }
+ }
+
+ public function testInvalidHeloResponseCausesException()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^HELO .*?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('504 WTF'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+ $smtp->start();
+ $this->fail('Non 250 HELO response should raise Exception');
+ } catch (Exception $e) {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
+ }
+ }
+
+ public function testDomainNameIsPlacedInHelo()
+ {
+ /* -- RFC 2821, 4.1.4.
+
+ The SMTP client MUST, if possible, ensure that the domain parameter
+ to the EHLO command is a valid principal host name (not a CNAME or MX
+ name) for its host. If this is not possible (e.g., when the client's
+ address is dynamically assigned and the client does not have an
+ obvious name), an address literal SHOULD be substituted for the
+ domain name and supplemental information provided that will assist in
+ identifying the client.
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("HELO mydomain.com\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 ServerName'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->setLocalDomain('mydomain.com');
+ $smtp->start();
+ }
+
+ public function testSuccessfulMailCommand()
+ {
+ /* -- RFC 2821, 3.3.
+
+ There are three steps to SMTP mail transactions. The transaction
+ starts with a MAIL command which gives the sender identification.
+
+ .....
+
+ The first step in the procedure is the MAIL command.
+
+ MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
+
+ -- RFC 2821, 4.1.1.2.
+
+ Syntax:
+
+ "MAIL FROM:" ("<>" / Reverse-Path)
+ [SP Mail-parameters] CRLF
+ -- RFC 2821, 4.1.2.
+
+ Reverse-path = Path
+ Forward-path = Path
+ Path = "<" [ A-d-l ":" ] Mailbox ">"
+ A-d-l = At-domain *( "," A-d-l )
+ ; Note that this form, the so-called "source route",
+ ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be
+ ; ignored.
+ At-domain = "@" domain
+
+ -- RFC 2821, 4.3.2.
+
+ MAIL
+ S: 250
+ E: 552, 451, 452, 550, 553, 503
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ } catch (Exception $e) {
+ $this->fail('MAIL FROM should accept a 250 response');
+ }
+ }
+
+ public function testInvalidResponseCodeFromMailCausesException()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('553 Bad'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ $this->fail('MAIL FROM should accept a 250 response');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testSenderIsPreferredOverFrom()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getSender')
+ ->once()
+ ->andReturn(array('another@domain.com' => 'Someone'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<another@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testReturnPathIsPreferredOverSender()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getSender')
+ ->once()
+ ->andReturn(array('another@domain.com' => 'Someone'));
+ $message->shouldReceive('getReturnPath')
+ ->once()
+ ->andReturn('more@domain.com');
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<more@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testSuccessfulRcptCommandWith250Response()
+ {
+ /* -- RFC 2821, 3.3.
+
+ The second step in the procedure is the RCPT command.
+
+ RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
+
+ The first or only argument to this command includes a forward-path
+ (normally a mailbox and domain, always surrounded by "<" and ">"
+ brackets) identifying one recipient. If accepted, the SMTP server
+ returns a 250 OK reply and stores the forward-path. If the recipient
+ is known not to be a deliverable address, the SMTP server returns a
+ 550 reply, typically with a string such as "no such user - " and the
+ mailbox name (other circumstances and reply codes are possible).
+ This step of the procedure can be repeated any number of times.
+
+ -- RFC 2821, 4.1.1.3.
+
+ This command is used to identify an individual recipient of the mail
+ data; multiple recipients are specified by multiple use of this
+ command. The argument field contains a forward-path and may contain
+ optional parameters.
+
+ The forward-path normally consists of the required destination
+ mailbox. Sending systems SHOULD not generate the optional list of
+ hosts known as a source route.
+
+ .......
+
+ "RCPT TO:" ("<Postmaster@" domain ">" / "<Postmaster>" / Forward-Path)
+ [SP Rcpt-parameters] CRLF
+
+ -- RFC 2821, 4.2.2.
+
+ 250 Requested mail action okay, completed
+ 251 User not local; will forward to <forward-path>
+ (See section 3.4)
+ 252 Cannot VRFY user, but will accept message and attempt
+ delivery
+
+ -- RFC 2821, 4.3.2.
+
+ RCPT
+ S: 250, 251 (but see section 3.4 for discussion of 251 and 551)
+ E: 550, 551, 552, 553, 450, 451, 452, 503, 550
+ */
+
+ //We'll treat 252 as accepted since it isn't really a failure
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ } catch (Exception $e) {
+ $this->fail('RCPT TO should accept a 250 response');
+ }
+ }
+
+ public function testMailFromCommandIsOnlySentOncePerMessage()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->never()
+ ->with("MAIL FROM:<me@domain.com>\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testMultipleRecipientsSendsMultipleRcpt()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array(
+ 'foo@bar' => null,
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<zip@button>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<test@domain>\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testCcRecipientsSendsMultipleRcpt()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getCc')
+ ->once()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<zip@button>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<test@domain>\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testSendReturnsNumberOfSuccessfulRecipients()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getCc')
+ ->once()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<zip@button>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('501 Nobody here'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<test@domain>\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(2, $smtp->send($message),
+ '%s: 1 of 3 recipients failed so 2 should be returned'
+ );
+ }
+
+ public function testRsetIsSentIfNoSuccessfulRecipients()
+ {
+ /* --RFC 2821, 4.1.1.5.
+
+ This command specifies that the current mail transaction will be
+ aborted. Any stored sender, recipients, and mail data MUST be
+ discarded, and all buffers and state tables cleared. The receiver
+ MUST send a "250 OK" reply to a RSET command with no arguments. A
+ reset command may be issued by the client at any time.
+
+ -- RFC 2821, 4.3.2.
+
+ RSET
+ S: 250
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('503 Bad'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RSET\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(0, $smtp->send($message),
+ '%s: 1 of 1 recipients failed so 0 should be returned'
+ );
+ }
+
+ public function testSuccessfulDataCommand()
+ {
+ /* -- RFC 2821, 3.3.
+
+ The third step in the procedure is the DATA command (or some
+ alternative specified in a service extension).
+
+ DATA <CRLF>
+
+ If accepted, the SMTP server returns a 354 Intermediate reply and
+ considers all succeeding lines up to but not including the end of
+ mail data indicator to be the message text.
+
+ -- RFC 2821, 4.1.1.4.
+
+ The receiver normally sends a 354 response to DATA, and then treats
+ the lines (strings ending in <CRLF> sequences, as described in
+ section 2.3.7) following the command as mail data from the sender.
+ This command causes the mail data to be appended to the mail data
+ buffer. The mail data may contain any of the 128 ASCII character
+ codes, although experience has indicated that use of control
+ characters other than SP, HT, CR, and LF may cause problems and
+ SHOULD be avoided when possible.
+
+ -- RFC 2821, 4.3.2.
+
+ DATA
+ I: 354 -> data -> S: 250
+ E: 552, 554, 451, 452
+ E: 451, 554, 503
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("DATA\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('354 Go ahead'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ } catch (Exception $e) {
+ $this->fail('354 is the expected response to DATA');
+ }
+ }
+
+ public function testBadDataResponseCausesException()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("DATA\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('451 Bad'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ $this->fail('354 is the expected response to DATA (not observed)');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testMessageIsStreamedToBufferForData()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("DATA\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('354 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("\r\n.\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 OK'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testBadResponseAfterDataTransmissionCausesException()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->once()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->once()
+ ->andReturn(array('foo@bar' => null));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("DATA\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('354 OK'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("\r\n.\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('554 Error'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ $smtp->send($message);
+ $this->fail('250 is the expected response after a DATA transmission (not observed)');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testBccRecipientsAreRemovedFromHeaders()
+ {
+ /* -- RFC 2821, 7.2.
+
+ Addresses that do not appear in the message headers may appear in the
+ RCPT commands to an SMTP server for a number of reasons. The two
+ most common involve the use of a mailing address as a "list exploder"
+ (a single address that resolves into multiple addresses) and the
+ appearance of "blind copies". Especially when more than one RCPT
+ command is present, and in order to avoid defeating some of the
+ purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy
+ the full set of RCPT command arguments into the headers, either as
+ part of trace headers or as informational or private-extension
+ headers. Since this rule is often violated in practice, and cannot
+ be enforced, sending SMTP systems that are aware of "bcc" use MAY
+ find it helpful to send each blind copy as a separate message
+ transaction containing only a single RCPT command.
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getBcc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array());
+ $message->shouldReceive('setBcc')
+ ->zeroOrMoreTimes();
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testEachBccRecipientIsSentASeparateMessage()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getBcc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $message->shouldReceive('setBcc')
+ ->atLeast()->once()
+ ->with(array());
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array('zip@button' => 'Zip Button'));
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array('test@domain' => 'Test user'));
+ $message->shouldReceive('setBcc')
+ ->atLeast()->once()
+ ->with(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
+ $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
+ $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3);
+ $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4);
+ $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n");
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(5);
+ $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(6);
+ $buf->shouldReceive('readLine')->once()->with(6)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(7);
+ $buf->shouldReceive('readLine')->once()->with(7)->andReturn("354 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(8);
+ $buf->shouldReceive('readLine')->once()->with(8)->andReturn("250 OK\r\n");
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(9);
+ $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(10);
+ $buf->shouldReceive('readLine')->once()->with(10)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(11);
+ $buf->shouldReceive('readLine')->once()->with(11)->andReturn("354 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(12);
+ $buf->shouldReceive('readLine')->once()->with(12)->andReturn("250 OK\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(3, $smtp->send($message));
+ }
+
+ public function testMessageStateIsRestoredOnFailure()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getBcc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array());
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain.com>\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("DATA\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn("451 No\r\n");
+
+ $this->_finishBuffer($buf);
+
+ $smtp->start();
+ try {
+ $smtp->send($message);
+ $this->fail('A bad response was given so exception is expected');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testStopSendsQuitCommand()
+ {
+ /* -- RFC 2821, 4.1.1.10.
+
+ This command specifies that the receiver MUST send an OK reply, and
+ then close the transmission channel.
+
+ The receiver MUST NOT intentionally close the transmission channel
+ until it receives and replies to a QUIT command (even if there was an
+ error). The sender MUST NOT intentionally close the transmission
+ channel until it sends a QUIT command and SHOULD wait until it
+ receives the reply (even if there was an error response to a previous
+ command). If the connection is closed prematurely due to violations
+ of the above or system or network failure, the server MUST cancel any
+ pending transaction, but not undo any previously completed
+ transaction, and generally MUST act as if the command or transaction
+ in progress had received a temporary error (i.e., a 4yz response).
+
+ The QUIT command may be issued at any time.
+
+ Syntax:
+ "QUIT" CRLF
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("QUIT\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("221 Bye\r\n");
+ $buf->shouldReceive('terminate')
+ ->once();
+
+ $this->_finishBuffer($buf);
+
+ $this->assertFalse($smtp->isStarted());
+ $smtp->start();
+ $this->assertTrue($smtp->isStarted());
+ $smtp->stop();
+ $this->assertFalse($smtp->isStarted());
+ }
+
+ public function testBufferCanBeFetched()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ref = $smtp->getBuffer();
+ $this->assertEquals($buf, $ref);
+ }
+
+ public function testBufferCanBeWrittenToUsingExecuteCommand()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with("FOO\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with(1)
+ ->andReturn("250 OK\r\n");
+
+ $res = $smtp->executeCommand("FOO\r\n");
+ $this->assertEquals("250 OK\r\n", $res);
+ }
+
+ public function testResponseCodesAreValidated()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with("FOO\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with(1)
+ ->andReturn("551 Not ok\r\n");
+
+ try {
+ $smtp->executeCommand("FOO\r\n", array(250, 251));
+ $this->fail('A 250 or 251 response was needed but 551 was returned.');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testFailedRecipientsCanBeCollectedByReference()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('getBcc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+ $message->shouldReceive('setBcc')
+ ->atLeast()->once()
+ ->with(array());
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array('zip@button' => 'Zip Button'));
+ $message->shouldReceive('setBcc')
+ ->once()
+ ->with(array('test@domain' => 'Test user'));
+ $message->shouldReceive('setBcc')
+ ->atLeast()->once()
+ ->with(array(
+ 'zip@button' => 'Zip Button',
+ 'test@domain' => 'Test user',
+ ));
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
+ $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
+ $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3);
+ $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4);
+ $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n");
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(5);
+ $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(6);
+ $buf->shouldReceive('readLine')->once()->with(6)->andReturn("500 Bad\r\n");
+ $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(7);
+ $buf->shouldReceive('readLine')->once()->with(7)->andReturn("250 OK\r\n");
+
+ $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(9);
+ $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(10);
+ $buf->shouldReceive('readLine')->once()->with(10)->andReturn("500 Bad\r\n");
+ $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(11);
+ $buf->shouldReceive('readLine')->once()->with(11)->andReturn("250 OK\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $this->assertEquals(1, $smtp->send($message, $failures));
+ $this->assertEquals(array('zip@button', 'test@domain'), $failures,
+ '%s: Failures should be caught in an array'
+ );
+ }
+
+ public function testSendingRegeneratesMessageId()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $message = $this->_createMessage();
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain.com' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+ $message->shouldReceive('generateId')
+ ->once();
+
+ $this->_finishBuffer($buf);
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ protected function _getBuffer()
+ {
+ return $this->getMockery('Swift_Transport_IoBuffer')->shouldIgnoreMissing();
+ }
+
+ protected function _createMessage()
+ {
+ return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ }
+
+ protected function _finishBuffer($buf)
+ {
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with(0)
+ ->andReturn('220 server.com foo'."\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with('~^(EH|HE)LO .*?\r\n$~D')
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn('250 ServerName'."\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with('~^MAIL FROM:<.*?>\r\n$~D')
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with('~^RCPT TO:<.*?>\r\n$~D')
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with("DATA\r\n")
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn("354 OK\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with("\r\n.\r\n")
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->with("RSET\r\n")
+ ->andReturn($x = uniqid());
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->with($x)
+ ->andReturn("250 OK\r\n");
+
+ $buf->shouldReceive('write')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ $buf->shouldReceive('readLine')
+ ->zeroOrMoreTimes()
+ ->andReturn(false);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php
new file mode 100644
index 0000000..aca03a9
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php
@@ -0,0 +1,64 @@
+<?php
+
+class Swift_Transport_Esmtp_Auth_CramMd5AuthenticatorTest extends \SwiftMailerTestCase
+{
+ private $_agent;
+
+ protected function setUp()
+ {
+ $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
+ }
+
+ public function testKeywordIsCramMd5()
+ {
+ /* -- RFC 2195, 2.
+ The authentication type associated with CRAM is "CRAM-MD5".
+ */
+
+ $cram = $this->_getAuthenticator();
+ $this->assertEquals('CRAM-MD5', $cram->getAuthKeyword());
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ $cram = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("AUTH CRAM-MD5\r\n", array(334))
+ ->andReturn('334 '.base64_encode('<foo@bar>')."\r\n");
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(\Mockery::any(), array(235));
+
+ $this->assertTrue($cram->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: The buffer accepted all commands authentication should succeed'
+ );
+ }
+
+ public function testAuthenticationFailureSendRsetAndReturnFalse()
+ {
+ $cram = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("AUTH CRAM-MD5\r\n", array(334))
+ ->andReturn('334 '.base64_encode('<foo@bar>')."\r\n");
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(\Mockery::any(), array(235))
+ ->andThrow(new Swift_TransportException(''));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("RSET\r\n", array(250));
+
+ $this->assertFalse($cram->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: Authentication fails, so RSET should be sent'
+ );
+ }
+
+ private function _getAuthenticator()
+ {
+ return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php
new file mode 100644
index 0000000..13f0209
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php
@@ -0,0 +1,64 @@
+<?php
+
+class Swift_Transport_Esmtp_Auth_LoginAuthenticatorTest extends \SwiftMailerTestCase
+{
+ private $_agent;
+
+ protected function setUp()
+ {
+ $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
+ }
+
+ public function testKeywordIsLogin()
+ {
+ $login = $this->_getAuthenticator();
+ $this->assertEquals('LOGIN', $login->getAuthKeyword());
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ $login = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("AUTH LOGIN\r\n", array(334));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(base64_encode('jack')."\r\n", array(334));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(base64_encode('pass')."\r\n", array(235));
+
+ $this->assertTrue($login->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: The buffer accepted all commands authentication should succeed'
+ );
+ }
+
+ public function testAuthenticationFailureSendRsetAndReturnFalse()
+ {
+ $login = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("AUTH LOGIN\r\n", array(334));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(base64_encode('jack')."\r\n", array(334));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(base64_encode('pass')."\r\n", array(235))
+ ->andThrow(new Swift_TransportException(''));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("RSET\r\n", array(250));
+
+ $this->assertFalse($login->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: Authentication fails, so RSET should be sent'
+ );
+ }
+
+ private function _getAuthenticator()
+ {
+ return new Swift_Transport_Esmtp_Auth_LoginAuthenticator();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php
new file mode 100644
index 0000000..911d258
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php
@@ -0,0 +1,213 @@
+<?php
+
+class Swift_Transport_Esmtp_Auth_NTLMAuthenticatorTest extends \SwiftMailerTestCase
+{
+ private $_message1 = '4e544c4d535350000100000007020000';
+ private $_message2 = '4e544c4d53535000020000000c000c003000000035828980514246973ea892c10000000000000000460046003c00000054004500530054004e00540002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d0000000000';
+ private $_message3 = '4e544c4d5353500003000000180018006000000076007600780000000c000c0040000000080008004c0000000c000c0054000000000000009a0000000102000054004500530054004e00540074006500730074004d0045004d00420045005200bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000';
+
+ protected function setUp()
+ {
+ if (!function_exists('openssl_encrypt') || !function_exists('openssl_random_pseudo_bytes') || !function_exists('bcmul') || !function_exists('iconv')) {
+ $this->markTestSkipped('One of the required functions is not available.');
+ }
+ }
+
+ public function testKeywordIsNtlm()
+ {
+ $login = $this->_getAuthenticator();
+ $this->assertEquals('NTLM', $login->getAuthKeyword());
+ }
+
+ public function testMessage1Generator()
+ {
+ $login = $this->_getAuthenticator();
+ $message1 = $this->_invokePrivateMethod('createMessage1', $login);
+
+ $this->assertEquals($this->_message1, bin2hex($message1), '%s: We send the smallest ntlm message which should never fail.');
+ }
+
+ public function testLMv1Generator()
+ {
+ $password = 'test1234';
+ $challenge = 'b019d38bad875c9d';
+ $lmv1 = '1879f60127f8a877022132ec221bcbf3ca016a9f76095606';
+
+ $login = $this->_getAuthenticator();
+ $lmv1Result = $this->_invokePrivateMethod('createLMPassword', $login, array($password, $this->hex2bin($challenge)));
+
+ $this->assertEquals($lmv1, bin2hex($lmv1Result), '%s: The keys should be the same cause we use the same values to generate them.');
+ }
+
+ public function testLMv2Generator()
+ {
+ $username = 'user';
+ $password = 'SecREt01';
+ $domain = 'DOMAIN';
+ $challenge = '0123456789abcdef';
+ $lmv2 = 'd6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344';
+
+ $login = $this->_getAuthenticator();
+ $lmv2Result = $this->_invokePrivateMethod('createLMv2Password', $login, array($password, $username, $domain, $this->hex2bin($challenge), $this->hex2bin('ffffff0011223344')));
+
+ $this->assertEquals($lmv2, bin2hex($lmv2Result), '%s: The keys should be the same cause we use the same values to generate them.');
+ }
+
+ public function testMessage3v1Generator()
+ {
+ $username = 'test';
+ $domain = 'TESTNT';
+ $workstation = 'MEMBER';
+ $lmResponse = '1879f60127f8a877022132ec221bcbf3ca016a9f76095606';
+ $ntlmResponse = 'e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a';
+ $message3T = '4e544c4d5353500003000000180018006000000018001800780000000c000c0040000000080008004c0000000c000c0054000000000000009a0000000102000054004500530054004e00540074006500730074004d0045004d004200450052001879f60127f8a877022132ec221bcbf3ca016a9f76095606e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a';
+
+ $login = $this->_getAuthenticator();
+ $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse)));
+
+ $this->assertEquals($message3T, bin2hex($message3), '%s: We send the same information as the example is created with so this should be the same');
+ }
+
+ public function testMessage3v2Generator()
+ {
+ $username = 'test';
+ $domain = 'TESTNT';
+ $workstation = 'MEMBER';
+ $lmResponse = 'bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9';
+ $ntlmResponse = 'caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000';
+
+ $login = $this->_getAuthenticator();
+ $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse)));
+
+ $this->assertEquals($this->_message3, bin2hex($message3), '%s: We send the same information as the example is created with so this should be the same');
+ }
+
+ public function testGetDomainAndUsername()
+ {
+ $username = "DOMAIN\user";
+
+ $login = $this->_getAuthenticator();
+ list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));
+
+ $this->assertEquals('DOMAIN', $domain, '%s: the fetched domain did not match');
+ $this->assertEquals('user', $user, '%s: the fetched user did not match');
+ }
+
+ public function testGetDomainAndUsernameWithExtension()
+ {
+ $username = "domain.com\user";
+
+ $login = $this->_getAuthenticator();
+ list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));
+
+ $this->assertEquals('domain.com', $domain, '%s: the fetched domain did not match');
+ $this->assertEquals('user', $user, '%s: the fetched user did not match');
+ }
+
+ public function testGetDomainAndUsernameWithAtSymbol()
+ {
+ $username = 'user@DOMAIN';
+
+ $login = $this->_getAuthenticator();
+ list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));
+
+ $this->assertEquals('DOMAIN', $domain, '%s: the fetched domain did not match');
+ $this->assertEquals('user', $user, '%s: the fetched user did not match');
+ }
+
+ public function testGetDomainAndUsernameWithAtSymbolAndExtension()
+ {
+ $username = 'user@domain.com';
+
+ $login = $this->_getAuthenticator();
+ list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));
+
+ $this->assertEquals('domain.com', $domain, '%s: the fetched domain did not match');
+ $this->assertEquals('user', $user, '%s: the fetched user did not match');
+ }
+
+ public function testGetDomainAndUsernameWithoutDomain()
+ {
+ $username = 'user';
+
+ $login = $this->_getAuthenticator();
+ list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));
+
+ $this->assertEquals('', $domain, '%s: the fetched domain did not match');
+ $this->assertEquals('user', $user, '%s: the fetched user did not match');
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ $domain = 'TESTNT';
+ $username = 'test';
+ $secret = 'test1234';
+
+ $ntlm = $this->_getAuthenticator();
+ $agent = $this->_getAgent();
+ $agent->shouldReceive('executeCommand')
+ ->once()
+ ->with('AUTH NTLM '.base64_encode(
+ $this->_invokePrivateMethod('createMessage1', $ntlm)
+ )."\r\n", array(334))
+ ->andReturn('334 '.base64_encode($this->hex2bin('4e544c4d53535000020000000c000c003000000035828980514246973ea892c10000000000000000460046003c00000054004500530054004e00540002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d0000000000')));
+ $agent->shouldReceive('executeCommand')
+ ->once()
+ ->with(base64_encode(
+ $this->_invokePrivateMethod('createMessage3', $ntlm, array($domain, $username, $this->hex2bin('4d0045004d00420045005200'), $this->hex2bin('bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9'), $this->hex2bin('caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000'))
+ ))."\r\n", array(235));
+
+ $this->assertTrue($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin('30fa7e3c677bc301'), $this->hex2bin('f5ce3d2401c8f6e9')), '%s: The buffer accepted all commands authentication should succeed');
+ }
+
+ public function testAuthenticationFailureSendRsetAndReturnFalse()
+ {
+ $domain = 'TESTNT';
+ $username = 'test';
+ $secret = 'test1234';
+
+ $ntlm = $this->_getAuthenticator();
+ $agent = $this->_getAgent();
+ $agent->shouldReceive('executeCommand')
+ ->once()
+ ->with('AUTH NTLM '.base64_encode(
+ $this->_invokePrivateMethod('createMessage1', $ntlm)
+ )."\r\n", array(334))
+ ->andThrow(new Swift_TransportException(''));
+ $agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("RSET\r\n", array(250));
+
+ $this->assertFalse($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin('30fa7e3c677bc301'), $this->hex2bin('f5ce3d2401c8f6e9')), '%s: Authentication fails, so RSET should be sent');
+ }
+
+ private function _getAuthenticator()
+ {
+ return new Swift_Transport_Esmtp_Auth_NTLMAuthenticator();
+ }
+
+ private function _getAgent()
+ {
+ return $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
+ }
+
+ private function _invokePrivateMethod($method, $instance, array $args = array())
+ {
+ $methodC = new ReflectionMethod($instance, trim($method));
+ $methodC->setAccessible(true);
+
+ return $methodC->invokeArgs($instance, $args);
+ }
+
+ /**
+ * Hex2bin replacement for < PHP 5.4.
+ *
+ * @param string $hex
+ *
+ * @return string Binary
+ */
+ protected function hex2bin($hex)
+ {
+ return function_exists('hex2bin') ? hex2bin($hex) : pack('H*', $hex);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php
new file mode 100644
index 0000000..73a9062
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php
@@ -0,0 +1,67 @@
+<?php
+
+class Swift_Transport_Esmtp_Auth_PlainAuthenticatorTest extends \SwiftMailerTestCase
+{
+ private $_agent;
+
+ protected function setUp()
+ {
+ $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
+ }
+
+ public function testKeywordIsPlain()
+ {
+ /* -- RFC 4616, 1.
+ The name associated with this mechanism is "PLAIN".
+ */
+
+ $login = $this->_getAuthenticator();
+ $this->assertEquals('PLAIN', $login->getAuthKeyword());
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ /* -- RFC 4616, 2.
+ The client presents the authorization identity (identity to act as),
+ followed by a NUL (U+0000) character, followed by the authentication
+ identity (identity whose password will be used), followed by a NUL
+ (U+0000) character, followed by the clear-text password.
+ */
+
+ $plain = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with('AUTH PLAIN '.base64_encode(
+ 'jack'.chr(0).'jack'.chr(0).'pass'
+ )."\r\n", array(235));
+
+ $this->assertTrue($plain->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: The buffer accepted all commands authentication should succeed'
+ );
+ }
+
+ public function testAuthenticationFailureSendRsetAndReturnFalse()
+ {
+ $plain = $this->_getAuthenticator();
+
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with('AUTH PLAIN '.base64_encode(
+ 'jack'.chr(0).'jack'.chr(0).'pass'
+ )."\r\n", array(235))
+ ->andThrow(new Swift_TransportException(''));
+ $this->_agent->shouldReceive('executeCommand')
+ ->once()
+ ->with("RSET\r\n", array(250));
+
+ $this->assertFalse($plain->authenticate($this->_agent, 'jack', 'pass'),
+ '%s: Authentication fails, so RSET should be sent'
+ );
+ }
+
+ private function _getAuthenticator()
+ {
+ return new Swift_Transport_Esmtp_Auth_PlainAuthenticator();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php
new file mode 100644
index 0000000..d52328a
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php
@@ -0,0 +1,165 @@
+<?php
+
+class Swift_Transport_Esmtp_AuthHandlerTest extends \SwiftMailerTestCase
+{
+ private $_agent;
+
+ protected function setUp()
+ {
+ $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
+ }
+
+ public function testKeywordIsAuth()
+ {
+ $auth = $this->_createHandler(array());
+ $this->assertEquals('AUTH', $auth->getHandledKeyword());
+ }
+
+ public function testUsernameCanBeSetAndFetched()
+ {
+ $auth = $this->_createHandler(array());
+ $auth->setUsername('jack');
+ $this->assertEquals('jack', $auth->getUsername());
+ }
+
+ public function testPasswordCanBeSetAndFetched()
+ {
+ $auth = $this->_createHandler(array());
+ $auth->setPassword('pass');
+ $this->assertEquals('pass', $auth->getPassword());
+ }
+
+ public function testAuthModeCanBeSetAndFetched()
+ {
+ $auth = $this->_createHandler(array());
+ $auth->setAuthMode('PLAIN');
+ $this->assertEquals('PLAIN', $auth->getAuthMode());
+ }
+
+ public function testMixinMethods()
+ {
+ $auth = $this->_createHandler(array());
+ $mixins = $auth->exposeMixinMethods();
+ $this->assertTrue(in_array('getUsername', $mixins),
+ '%s: getUsername() should be accessible via mixin'
+ );
+ $this->assertTrue(in_array('setUsername', $mixins),
+ '%s: setUsername() should be accessible via mixin'
+ );
+ $this->assertTrue(in_array('getPassword', $mixins),
+ '%s: getPassword() should be accessible via mixin'
+ );
+ $this->assertTrue(in_array('setPassword', $mixins),
+ '%s: setPassword() should be accessible via mixin'
+ );
+ $this->assertTrue(in_array('setAuthMode', $mixins),
+ '%s: setAuthMode() should be accessible via mixin'
+ );
+ $this->assertTrue(in_array('getAuthMode', $mixins),
+ '%s: getAuthMode() should be accessible via mixin'
+ );
+ }
+
+ public function testAuthenticatorsAreCalledAccordingToParamsAfterEhlo()
+ {
+ $a1 = $this->_createMockAuthenticator('PLAIN');
+ $a2 = $this->_createMockAuthenticator('LOGIN');
+
+ $a1->shouldReceive('authenticate')
+ ->never()
+ ->with($this->_agent, 'jack', 'pass');
+ $a2->shouldReceive('authenticate')
+ ->once()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(true);
+
+ $auth = $this->_createHandler(array($a1, $a2));
+ $auth->setUsername('jack');
+ $auth->setPassword('pass');
+
+ $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN'));
+ $auth->afterEhlo($this->_agent);
+ }
+
+ public function testAuthenticatorsAreNotUsedIfNoUsernameSet()
+ {
+ $a1 = $this->_createMockAuthenticator('PLAIN');
+ $a2 = $this->_createMockAuthenticator('LOGIN');
+
+ $a1->shouldReceive('authenticate')
+ ->never()
+ ->with($this->_agent, 'jack', 'pass');
+ $a2->shouldReceive('authenticate')
+ ->never()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(true);
+
+ $auth = $this->_createHandler(array($a1, $a2));
+
+ $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN'));
+ $auth->afterEhlo($this->_agent);
+ }
+
+ public function testSeveralAuthenticatorsAreTriedIfNeeded()
+ {
+ $a1 = $this->_createMockAuthenticator('PLAIN');
+ $a2 = $this->_createMockAuthenticator('LOGIN');
+
+ $a1->shouldReceive('authenticate')
+ ->once()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(false);
+ $a2->shouldReceive('authenticate')
+ ->once()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(true);
+
+ $auth = $this->_createHandler(array($a1, $a2));
+ $auth->setUsername('jack');
+ $auth->setPassword('pass');
+
+ $auth->setKeywordParams(array('PLAIN', 'LOGIN'));
+ $auth->afterEhlo($this->_agent);
+ }
+
+ public function testFirstAuthenticatorToPassBreaksChain()
+ {
+ $a1 = $this->_createMockAuthenticator('PLAIN');
+ $a2 = $this->_createMockAuthenticator('LOGIN');
+ $a3 = $this->_createMockAuthenticator('CRAM-MD5');
+
+ $a1->shouldReceive('authenticate')
+ ->once()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(false);
+ $a2->shouldReceive('authenticate')
+ ->once()
+ ->with($this->_agent, 'jack', 'pass')
+ ->andReturn(true);
+ $a3->shouldReceive('authenticate')
+ ->never()
+ ->with($this->_agent, 'jack', 'pass');
+
+ $auth = $this->_createHandler(array($a1, $a2));
+ $auth->setUsername('jack');
+ $auth->setPassword('pass');
+
+ $auth->setKeywordParams(array('PLAIN', 'LOGIN', 'CRAM-MD5'));
+ $auth->afterEhlo($this->_agent);
+ }
+
+ private function _createHandler($authenticators)
+ {
+ return new Swift_Transport_Esmtp_AuthHandler($authenticators);
+ }
+
+ private function _createMockAuthenticator($type)
+ {
+ $authenticator = $this->getMockery('Swift_Transport_Esmtp_Authenticator')->shouldIgnoreMissing();
+ $authenticator->shouldReceive('getAuthKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn($type);
+
+ return $authenticator;
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php
new file mode 100644
index 0000000..166e160
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php
@@ -0,0 +1,529 @@
+<?php
+
+require_once dirname(__DIR__).'/EsmtpTransportTest.php';
+
+interface Swift_Transport_EsmtpHandlerMixin extends Swift_Transport_EsmtpHandler
+{
+ public function setUsername($user);
+
+ public function setPassword($pass);
+}
+
+class Swift_Transport_EsmtpTransport_ExtensionSupportTest extends Swift_Transport_EsmtpTransportTest
+{
+ public function testExtensionHandlersAreSortedAsNeeded()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('STARTTLS')
+ ->andReturn(1);
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext2->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('AUTH')
+ ->andReturn(-1);
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2));
+ $this->assertEquals(array($ext2, $ext1), $smtp->getExtensionHandlers());
+ }
+
+ public function testHandlersAreNotifiedOfParams()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .*?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('setKeywordParams')
+ ->once()
+ ->with(array('PLAIN', 'LOGIN'));
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('setKeywordParams')
+ ->zeroOrMoreTimes()
+ ->with(array('123456'));
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2));
+ $smtp->start();
+ }
+
+ public function testSupportedExtensionHandlersAreRunAfterEhlo()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .*?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('afterEhlo')
+ ->once()
+ ->with($smtp);
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('afterEhlo')
+ ->zeroOrMoreTimes()
+ ->with($smtp);
+ $ext3->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext3->shouldReceive('afterEhlo')
+ ->never()
+ ->with($smtp);
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
+ $smtp->start();
+ }
+
+ public function testExtensionsCanModifyMailFromParams()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher();
+ $smtp = new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .*?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain> FOO ZIP\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar>\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn("250 OK\r\n");
+ $this->_finishBuffer($buf);
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('getMailParams')
+ ->once()
+ ->andReturn('FOO');
+ $ext1->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('AUTH')
+ ->andReturn(-1);
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('getMailParams')
+ ->once()
+ ->andReturn('ZIP');
+ $ext2->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('AUTH')
+ ->andReturn(1);
+ $ext3->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext3->shouldReceive('getMailParams')
+ ->never();
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testExtensionsCanModifyRcptParams()
+ {
+ $buf = $this->_getBuffer();
+ $dispatcher = $this->_createEventDispatcher();
+ $smtp = new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('me@domain' => 'Me'));
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null));
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("MAIL FROM:<me@domain>\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("250 OK\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("RCPT TO:<foo@bar> FOO ZIP\r\n")
+ ->andReturn(3);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(3)
+ ->andReturn("250 OK\r\n");
+ $this->_finishBuffer($buf);
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('getRcptParams')
+ ->once()
+ ->andReturn('FOO');
+ $ext1->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('AUTH')
+ ->andReturn(-1);
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('getRcptParams')
+ ->once()
+ ->andReturn('ZIP');
+ $ext2->shouldReceive('getPriorityOver')
+ ->zeroOrMoreTimes()
+ ->with('AUTH')
+ ->andReturn(1);
+ $ext3->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext3->shouldReceive('getRcptParams')
+ ->never();
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
+ $smtp->start();
+ $smtp->send($message);
+ }
+
+ public function testExtensionsAreNotifiedOnCommand()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("FOO\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn("250 Cool\r\n");
+ $this->_finishBuffer($buf);
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('onCommand')
+ ->once()
+ ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any());
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('onCommand')
+ ->once()
+ ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any());
+ $ext3->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext3->shouldReceive('onCommand')
+ ->never()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
+ $smtp->start();
+ $smtp->executeCommand("FOO\r\n", array(250, 251));
+ }
+
+ public function testChainOfCommandAlgorithmWhenNotifyingExtensions()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+ $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 server.com foo\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-ServerName.tld\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250-AUTH PLAIN LOGIN\r\n");
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn("250 SIZE=123456\r\n");
+ $buf->shouldReceive('write')
+ ->never()
+ ->with("FOO\r\n");
+ $this->_finishBuffer($buf);
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('onCommand')
+ ->once()
+ ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any())
+ ->andReturnUsing(function ($a, $b, $c, $d, &$e) {
+ $e = true;
+
+ return '250 ok';
+ });
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('SIZE');
+ $ext2->shouldReceive('onCommand')
+ ->never()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $ext3->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $ext3->shouldReceive('onCommand')
+ ->never()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
+ $smtp->start();
+ $smtp->executeCommand("FOO\r\n", array(250, 251));
+ }
+
+ public function testExtensionsCanExposeMixinMethods()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('exposeMixinMethods')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('setUsername', 'setPassword'));
+ $ext1->shouldReceive('setUsername')
+ ->once()
+ ->with('mick');
+ $ext1->shouldReceive('setPassword')
+ ->once()
+ ->with('pass');
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2));
+ $smtp->setUsername('mick');
+ $smtp->setPassword('pass');
+ }
+
+ public function testMixinMethodsBeginningWithSetAndNullReturnAreFluid()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('exposeMixinMethods')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('setUsername', 'setPassword'));
+ $ext1->shouldReceive('setUsername')
+ ->once()
+ ->with('mick')
+ ->andReturn(null);
+ $ext1->shouldReceive('setPassword')
+ ->once()
+ ->with('pass')
+ ->andReturn(null);
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2));
+ $ret = $smtp->setUsername('mick');
+ $this->assertEquals($smtp, $ret);
+ $ret = $smtp->setPassword('pass');
+ $this->assertEquals($smtp, $ret);
+ }
+
+ public function testMixinSetterWhichReturnValuesAreNotFluid()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
+ $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
+
+ $ext1->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('AUTH');
+ $ext1->shouldReceive('exposeMixinMethods')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('setUsername', 'setPassword'));
+ $ext1->shouldReceive('setUsername')
+ ->once()
+ ->with('mick')
+ ->andReturn('x');
+ $ext1->shouldReceive('setPassword')
+ ->once()
+ ->with('pass')
+ ->andReturn('x');
+ $ext2->shouldReceive('getHandledKeyword')
+ ->zeroOrMoreTimes()
+ ->andReturn('STARTTLS');
+ $this->_finishBuffer($buf);
+
+ $smtp->setExtensionHandlers(array($ext1, $ext2));
+ $this->assertEquals('x', $smtp->setUsername('mick'));
+ $this->assertEquals('x', $smtp->setPassword('pass'));
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php
new file mode 100644
index 0000000..e6cca15
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php
@@ -0,0 +1,297 @@
+<?php
+
+class Swift_Transport_EsmtpTransportTest extends Swift_Transport_AbstractSmtpEventSupportTest
+{
+ protected function _getTransport($buf, $dispatcher = null)
+ {
+ if (!$dispatcher) {
+ $dispatcher = $this->_createEventDispatcher();
+ }
+
+ return new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
+ }
+
+ public function testHostCanBeSetAndFetched()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $smtp->setHost('foo');
+ $this->assertEquals('foo', $smtp->getHost(), '%s: Host should be returned');
+ }
+
+ public function testPortCanBeSetAndFetched()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $smtp->setPort(25);
+ $this->assertEquals(25, $smtp->getPort(), '%s: Port should be returned');
+ }
+
+ public function testTimeoutCanBeSetAndFetched()
+ {
+ $buf = $this->_getBuffer();
+ $buf->shouldReceive('setParam')
+ ->once()
+ ->with('timeout', 10);
+
+ $smtp = $this->_getTransport($buf);
+ $smtp->setTimeout(10);
+ $this->assertEquals(10, $smtp->getTimeout(), '%s: Timeout should be returned');
+ }
+
+ public function testEncryptionCanBeSetAndFetched()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $smtp->setEncryption('tls');
+ $this->assertEquals('tls', $smtp->getEncryption(), '%s: Crypto should be returned');
+ }
+
+ public function testStartSendsHeloToInitiate()
+ {
+ //Overridden for EHLO instead
+ }
+
+ public function testStartSendsEhloToInitiate()
+ {
+ /* -- RFC 2821, 3.2.
+
+ 3.2 Client Initiation
+
+ Once the server has sent the welcoming message and the client has
+ received it, the client normally sends the EHLO command to the
+ server, indicating the client's identity. In addition to opening the
+ session, use of EHLO indicates that the client is able to process
+ service extensions and requests that the server provide a list of the
+ extensions it supports. Older SMTP systems which are unable to
+ support service extensions and contemporary clients which do not
+ require service extensions in the mail session being initiated, MAY
+ use HELO instead of EHLO. Servers MUST NOT return the extended
+ EHLO-style response to a HELO command. For a particular connection
+ attempt, if the server returns a "command not recognized" response to
+ EHLO, the client SHOULD be able to fall back and send HELO.
+
+ In the EHLO command the host sending the command identifies itself;
+ the command may be interpreted as saying "Hello, I am <domain>" (and,
+ in the case of EHLO, "and I support service extension requests").
+
+ -- RFC 2281, 4.1.1.1.
+
+ ehlo = "EHLO" SP Domain CRLF
+ helo = "HELO" SP Domain CRLF
+
+ -- RFC 2821, 4.3.2.
+
+ EHLO or HELO
+ S: 250
+ E: 504, 550
+
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 ServerName'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ } catch (Exception $e) {
+ $this->fail('Starting Esmtp should send EHLO and accept 250 response');
+ }
+ }
+
+ public function testHeloIsUsedAsFallback()
+ {
+ /* -- RFC 2821, 4.1.4.
+
+ If the EHLO command is not acceptable to the SMTP server, 501, 500,
+ or 502 failure replies MUST be returned as appropriate. The SMTP
+ server MUST stay in the same state after transmitting these replies
+ that it was in before the EHLO was received.
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('501 WTF'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^HELO .+?\r\n$~D')
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 HELO'."\r\n");
+
+ $this->_finishBuffer($buf);
+ try {
+ $smtp->start();
+ } catch (Exception $e) {
+ $this->fail(
+ 'Starting Esmtp should fallback to HELO if needed and accept 250 response'
+ );
+ }
+ }
+
+ public function testInvalidHeloResponseCausesException()
+ {
+ //Overridden to first try EHLO
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('501 WTF'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^HELO .+?\r\n$~D')
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('504 WTF'."\r\n");
+ $this->_finishBuffer($buf);
+
+ try {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+ $smtp->start();
+ $this->fail('Non 250 HELO response should raise Exception');
+ } catch (Exception $e) {
+ $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
+ }
+ }
+
+ public function testDomainNameIsPlacedInEhlo()
+ {
+ /* -- RFC 2821, 4.1.4.
+
+ The SMTP client MUST, if possible, ensure that the domain parameter
+ to the EHLO command is a valid principal host name (not a CNAME or MX
+ name) for its host. If this is not possible (e.g., when the client's
+ address is dynamically assigned and the client does not have an
+ obvious name), an address literal SHOULD be substituted for the
+ domain name and supplemental information provided that will assist in
+ identifying the client.
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("EHLO mydomain.com\r\n")
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('250 ServerName'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->setLocalDomain('mydomain.com');
+ $smtp->start();
+ }
+
+ public function testDomainNameIsPlacedInHelo()
+ {
+ //Overridden to include ESMTP
+ /* -- RFC 2821, 4.1.4.
+
+ The SMTP client MUST, if possible, ensure that the domain parameter
+ to the EHLO command is a valid principal host name (not a CNAME or MX
+ name) for its host. If this is not possible (e.g., when the client's
+ address is dynamically assigned and the client does not have an
+ obvious name), an address literal SHOULD be substituted for the
+ domain name and supplemental information provided that will assist in
+ identifying the client.
+ */
+
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(0)
+ ->andReturn("220 some.server.tld bleh\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with('~^EHLO .+?\r\n$~D')
+ ->andReturn(1);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(1)
+ ->andReturn('501 WTF'."\r\n");
+ $buf->shouldReceive('write')
+ ->once()
+ ->with("HELO mydomain.com\r\n")
+ ->andReturn(2);
+ $buf->shouldReceive('readLine')
+ ->once()
+ ->with(2)
+ ->andReturn('250 ServerName'."\r\n");
+
+ $this->_finishBuffer($buf);
+ $smtp->setLocalDomain('mydomain.com');
+ $smtp->start();
+ }
+
+ public function testFluidInterface()
+ {
+ $buf = $this->_getBuffer();
+ $smtp = $this->_getTransport($buf);
+ $buf->shouldReceive('setParam')
+ ->once()
+ ->with('timeout', 30);
+
+ $ref = $smtp
+ ->setHost('foo')
+ ->setPort(25)
+ ->setEncryption('tls')
+ ->setTimeout(30)
+ ;
+ $this->assertEquals($ref, $smtp);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php
new file mode 100644
index 0000000..e56e37f
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php
@@ -0,0 +1,518 @@
+<?php
+
+class Swift_Transport_FailoverTransportTest extends \SwiftMailerTestCase
+{
+ public function testFirstTransportIsUsed()
+ {
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState) {
+ return $connectionState;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState) {
+ if (!$connectionState) {
+ $connectionState = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->twice()
+ ->with(\Mockery::anyOf($message1, $message2), \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState) {
+ if ($connectionState) {
+ return 1;
+ }
+ });
+ $t2->shouldReceive('start')->never();
+ $t2->shouldReceive('send')->never();
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message1));
+ $this->assertEquals(1, $transport->send($message2));
+ }
+
+ public function testMessageCanBeTriedOnNextTransportIfExceptionThrown()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ return 1;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message));
+ }
+
+ public function testZeroIsReturnedIfTransportReturnsZero()
+ {
+ $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ $t1 = $this->getMockery('Swift_Transport')->shouldIgnoreMissing();
+
+ $connectionState = false;
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState) {
+ return $connectionState;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState) {
+ if (!$connectionState) {
+ $connectionState = true;
+ }
+ });
+ $testCase = $this;
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState, $testCase) {
+ if (!$connectionState) {
+ $testCase->fail();
+ }
+
+ return 0;
+ });
+
+ $transport = $this->_getTransport(array($t1));
+ $transport->start();
+ $this->assertEquals(0, $transport->send($message));
+ }
+
+ public function testTransportsWhichThrowExceptionsAreNotRetried()
+ {
+ $e = new Swift_TransportException('maur b0rken');
+
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $message3 = $this->getMockery('Swift_Mime_Message');
+ $message4 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message3, \Mockery::any());
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message4, \Mockery::any());
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->times(4)
+ ->with(\Mockery::anyOf($message1, $message2, $message3, $message4), \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ return 1;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message1));
+ $this->assertEquals(1, $transport->send($message2));
+ $this->assertEquals(1, $transport->send($message3));
+ $this->assertEquals(1, $transport->send($message4));
+ }
+
+ public function testExceptionIsThrownIfAllTransportsDie()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ throw $e;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ try {
+ $transport->send($message);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testStoppingTransportStopsAllDelegates()
+ {
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+
+ $connectionState1 = true;
+ $connectionState2 = true;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('stop')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if ($connectionState1) {
+ $connectionState1 = false;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('stop')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if ($connectionState2) {
+ $connectionState2 = false;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $transport->stop();
+ }
+
+ public function testTransportShowsAsNotStartedIfAllDelegatesDead()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ $connectionState1 = false;
+ throw $e;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ $connectionState2 = false;
+ throw $e;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ try {
+ $transport->send($message);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ $this->assertFalse($transport->isStarted());
+ }
+ }
+
+ public function testRestartingTransportRestartsDeadDelegates()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->twice()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ $connectionState1 = false;
+ throw $e;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message2, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if ($connectionState1) {
+ return 10;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ $connectionState2 = false;
+ throw $e;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ try {
+ $transport->send($message1);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ $this->assertFalse($transport->isStarted());
+ }
+ //Restart and re-try
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ $this->assertEquals(10, $transport->send($message2));
+ }
+
+ public function testFailureReferenceIsPassedToDelegates()
+ {
+ $failures = array();
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+
+ $connectionState = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use ($connectionState) {
+ return $connectionState;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use ($connectionState) {
+ if (!$connectionState) {
+ $connectionState = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, $failures)
+ ->andReturnUsing(function () use ($connectionState) {
+ if ($connectionState) {
+ return 1;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1));
+ $transport->start();
+ $transport->send($message, $failures);
+ }
+
+ public function testRegisterPluginDelegatesToLoadedTransports()
+ {
+ $plugin = $this->_createPlugin();
+
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $t1->shouldReceive('registerPlugin')
+ ->once()
+ ->with($plugin);
+ $t2->shouldReceive('registerPlugin')
+ ->once()
+ ->with($plugin);
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->registerPlugin($plugin);
+ }
+
+ private function _getTransport(array $transports)
+ {
+ $transport = new Swift_Transport_FailoverTransport();
+ $transport->setTransports($transports);
+
+ return $transport;
+ }
+
+ private function _createPlugin()
+ {
+ return $this->getMockery('Swift_Events_EventListener');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php
new file mode 100644
index 0000000..f6bb819
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php
@@ -0,0 +1,749 @@
+<?php
+
+class Swift_Transport_LoadBalancedTransportTest extends \SwiftMailerTestCase
+{
+ public function testEachTransportIsUsedInTurn()
+ {
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $testCase = $this;
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $testCase) {
+ if ($connectionState1) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message2, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $testCase) {
+ if ($connectionState2) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t2->shouldReceive('send')
+ ->never()
+ ->with($message1, \Mockery::any());
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message1));
+ $this->assertEquals(1, $transport->send($message2));
+ }
+
+ public function testTransportsAreReusedInRotatingFashion()
+ {
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $message3 = $this->getMockery('Swift_Mime_Message');
+ $message4 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $testCase = $this;
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $testCase) {
+ if ($connectionState1) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message3, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $testCase) {
+ if ($connectionState1) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message4, \Mockery::any());
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message2, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $testCase) {
+ if ($connectionState2) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t2->shouldReceive('send')
+ ->never()
+ ->with($message1, \Mockery::any());
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message4, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $testCase) {
+ if ($connectionState2) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+ $t2->shouldReceive('send')
+ ->never()
+ ->with($message3, \Mockery::any());
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+
+ $this->assertEquals(1, $transport->send($message1));
+ $this->assertEquals(1, $transport->send($message2));
+ $this->assertEquals(1, $transport->send($message3));
+ $this->assertEquals(1, $transport->send($message4));
+ }
+
+ public function testMessageCanBeTriedOnNextTransportIfExceptionThrown()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $testCase = $this;
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ $testCase->fail();
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $testCase) {
+ if ($connectionState2) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message));
+ }
+
+ public function testMessageIsTriedOnNextTransportIfZeroReturned()
+ {
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if ($connectionState1) {
+ return 0;
+ }
+
+ return 1;
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if ($connectionState2) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message));
+ }
+
+ public function testZeroIsReturnedIfAllTransportsReturnZero()
+ {
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if ($connectionState1) {
+ return 0;
+ }
+
+ return 1;
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if ($connectionState2) {
+ return 0;
+ }
+
+ return 1;
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(0, $transport->send($message));
+ }
+
+ public function testTransportsWhichThrowExceptionsAreNotRetried()
+ {
+ $e = new Swift_TransportException('maur b0rken');
+
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $message3 = $this->getMockery('Swift_Mime_Message');
+ $message4 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $testCase = $this;
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ $testCase->fail();
+ });
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message3, \Mockery::any());
+ $t1->shouldReceive('send')
+ ->never()
+ ->with($message4, \Mockery::any());
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->times(4)
+ ->with(\Mockery::anyOf($message1, $message3, $message3, $message4), \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $testCase) {
+ if ($connectionState2) {
+ return 1;
+ }
+ $testCase->fail();
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertEquals(1, $transport->send($message1));
+ $this->assertEquals(1, $transport->send($message2));
+ $this->assertEquals(1, $transport->send($message3));
+ $this->assertEquals(1, $transport->send($message4));
+ }
+
+ public function testExceptionIsThrownIfAllTransportsDie()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ throw $e;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ try {
+ $transport->send($message);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ }
+ }
+
+ public function testStoppingTransportStopsAllDelegates()
+ {
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = true;
+ $connectionState2 = true;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('stop')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if ($connectionState1) {
+ $connectionState1 = false;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('stop')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if ($connectionState2) {
+ $connectionState2 = false;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $transport->stop();
+ }
+
+ public function testTransportShowsAsNotStartedIfAllDelegatesDead()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ throw $e;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ throw $e;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ try {
+ $transport->send($message);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ $this->assertFalse($transport->isStarted());
+ }
+ }
+
+ public function testRestartingTransportRestartsDeadDelegates()
+ {
+ $e = new Swift_TransportException('b0rken');
+
+ $message1 = $this->getMockery('Swift_Mime_Message');
+ $message2 = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+ $connectionState1 = false;
+ $connectionState2 = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ return $connectionState1;
+ });
+ $t1->shouldReceive('start')
+ ->twice()
+ ->andReturnUsing(function () use (&$connectionState1) {
+ if (!$connectionState1) {
+ $connectionState1 = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ $connectionState1 = false;
+ throw $e;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message2, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState1, $e) {
+ if ($connectionState1) {
+ return 10;
+ }
+ });
+
+ $t2->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ return $connectionState2;
+ });
+ $t2->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState2) {
+ if (!$connectionState2) {
+ $connectionState2 = true;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->once()
+ ->with($message1, \Mockery::any())
+ ->andReturnUsing(function () use (&$connectionState2, $e) {
+ if ($connectionState2) {
+ throw $e;
+ }
+ });
+ $t2->shouldReceive('send')
+ ->never()
+ ->with($message2, \Mockery::any());
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ try {
+ $transport->send($message1);
+ $this->fail('All transports failed so Exception should be thrown');
+ } catch (Exception $e) {
+ $this->assertFalse($transport->isStarted());
+ }
+ //Restart and re-try
+ $transport->start();
+ $this->assertTrue($transport->isStarted());
+ $this->assertEquals(10, $transport->send($message2));
+ }
+
+ public function testFailureReferenceIsPassedToDelegates()
+ {
+ $failures = array();
+ $testCase = $this;
+
+ $message = $this->getMockery('Swift_Mime_Message');
+ $t1 = $this->getMockery('Swift_Transport');
+ $connectionState = false;
+
+ $t1->shouldReceive('isStarted')
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(function () use (&$connectionState) {
+ return $connectionState;
+ });
+ $t1->shouldReceive('start')
+ ->once()
+ ->andReturnUsing(function () use (&$connectionState) {
+ if (!$connectionState) {
+ $connectionState = true;
+ }
+ });
+ $t1->shouldReceive('send')
+ ->once()
+ ->with($message, \Mockery::on(function (&$var) use (&$failures, $testCase) {
+ return $testCase->varsAreReferences($var, $failures);
+ }))
+ ->andReturnUsing(function () use (&$connectionState) {
+ if ($connectionState) {
+ return 1;
+ }
+ });
+
+ $transport = $this->_getTransport(array($t1));
+ $transport->start();
+ $transport->send($message, $failures);
+ }
+
+ public function testRegisterPluginDelegatesToLoadedTransports()
+ {
+ $plugin = $this->_createPlugin();
+
+ $t1 = $this->getMockery('Swift_Transport');
+ $t2 = $this->getMockery('Swift_Transport');
+
+ $t1->shouldReceive('registerPlugin')
+ ->once()
+ ->with($plugin);
+ $t2->shouldReceive('registerPlugin')
+ ->once()
+ ->with($plugin);
+
+ $transport = $this->_getTransport(array($t1, $t2));
+ $transport->registerPlugin($plugin);
+ }
+
+ /**
+ * Adapted from Yay_Matchers_ReferenceMatcher.
+ */
+ public function varsAreReferences(&$ref1, &$ref2)
+ {
+ if (is_object($ref2)) {
+ return $ref1 === $ref2;
+ }
+ if ($ref1 !== $ref2) {
+ return false;
+ }
+
+ $copy = $ref2;
+ $randomString = uniqid('yay');
+ $ref2 = $randomString;
+ $isRef = ($ref1 === $ref2);
+ $ref2 = $copy;
+
+ return $isRef;
+ }
+
+ private function _getTransport(array $transports)
+ {
+ $transport = new Swift_Transport_LoadBalancedTransport();
+ $transport->setTransports($transports);
+
+ return $transport;
+ }
+
+ private function _createPlugin()
+ {
+ return $this->getMockery('Swift_Events_EventListener');
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php
new file mode 100644
index 0000000..6672a3d
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php
@@ -0,0 +1,533 @@
+<?php
+
+/**
+ * @group legacy
+ */
+class Swift_Transport_MailTransportTest extends \SwiftMailerTestCase
+{
+ public function testTransportInvokesMailOncePerMessage()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $invoker->shouldReceive('mail')
+ ->once();
+
+ $transport->send($message);
+ }
+
+ public function testTransportUsesToFieldBodyInSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $to = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'To' => $to,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $to->shouldReceive('getFieldBody')
+ ->zeroOrMoreTimes()
+ ->andReturn('Foo <foo@bar>');
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with('Foo <foo@bar>', \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testTransportUsesSubjectFieldBodyInSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $subj = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'Subject' => $subj,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $subj->shouldReceive('getFieldBody')
+ ->zeroOrMoreTimes()
+ ->andReturn('Thing');
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), 'Thing', \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testTransportUsesBodyOfMessage()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ "To: Foo <foo@bar>\r\n".
+ "\r\n".
+ 'This body'
+ );
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), 'This body', \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testTransportSettingUsingReturnPathForExtraParams()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('getReturnPath')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ 'foo@bar'
+ );
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-ffoo@bar');
+
+ $transport->send($message);
+ }
+
+ public function testTransportSettingEmptyExtraParams()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('getReturnPath')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $message->shouldReceive('getSender')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null);
+
+ $transport->send($message);
+ }
+
+ public function testTransportSettingSettingExtraParamsWithF()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+ $transport->setExtraParams('-x\'foo\' -f%s');
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('getReturnPath')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ 'foo@bar'
+ );
+ $message->shouldReceive('getSender')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\' -ffoo@bar');
+
+ $transport->send($message);
+ }
+
+ public function testTransportSettingSettingExtraParamsWithoutF()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+ $transport->setExtraParams('-x\'foo\'');
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('getReturnPath')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ 'foo@bar'
+ );
+ $message->shouldReceive('getSender')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\'');
+
+ $transport->send($message);
+ }
+
+ public function testTransportSettingInvalidFromEmail()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('getReturnPath')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ '"attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php "@email.com'
+ );
+ $message->shouldReceive('getSender')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $message->shouldReceive('getFrom')
+ ->zeroOrMoreTimes()
+ ->andReturn(null);
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null);
+
+ $transport->send($message);
+ }
+
+ public function testTransportUsesHeadersFromMessage()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $message->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ "Subject: Stuff\r\n".
+ "\r\n".
+ 'This body'
+ );
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), 'Subject: Stuff'.PHP_EOL, \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testTransportReturnsCountOfAllRecipientsIfInvokerReturnsTrue()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessage($headers);
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null, 'zip@button' => null));
+ $message->shouldReceive('getCc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('test@test' => null));
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn(true);
+
+ $this->assertEquals(3, $transport->send($message));
+ }
+
+ public function testTransportReturnsZeroIfInvokerReturnsFalse()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessage($headers);
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => null, 'zip@button' => null));
+ $message->shouldReceive('getCc')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('test@test' => null));
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any())
+ ->andReturn(false);
+
+ $this->assertEquals(0, $transport->send($message));
+ }
+
+ public function testToHeaderIsRemovedFromHeaderSetDuringSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $to = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'To' => $to,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $headers->shouldReceive('remove')
+ ->once()
+ ->with('To');
+ $headers->shouldReceive('remove')
+ ->zeroOrMoreTimes();
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testSubjectHeaderIsRemovedFromHeaderSetDuringSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $subject = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'Subject' => $subject,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $headers->shouldReceive('remove')
+ ->once()
+ ->with('Subject');
+ $headers->shouldReceive('remove')
+ ->zeroOrMoreTimes();
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testToHeaderIsPutBackAfterSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $to = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'To' => $to,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $headers->shouldReceive('set')
+ ->once()
+ ->with($to);
+ $headers->shouldReceive('set')
+ ->zeroOrMoreTimes();
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testSubjectHeaderIsPutBackAfterSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $subject = $this->_createHeader();
+ $headers = $this->_createHeaders(array(
+ 'Subject' => $subject,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+
+ $headers->shouldReceive('set')
+ ->once()
+ ->with($subject);
+ $headers->shouldReceive('set')
+ ->zeroOrMoreTimes();
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ public function testMessageHeadersOnlyHavePHPEolsDuringSending()
+ {
+ $invoker = $this->_createInvoker();
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $subject = $this->_createHeader();
+ $subject->shouldReceive('getFieldBody')->andReturn("Foo\r\nBar");
+
+ $headers = $this->_createHeaders(array(
+ 'Subject' => $subject,
+ ));
+ $message = $this->_createMessageWithRecipient($headers);
+ $message->shouldReceive('toString')
+ ->zeroOrMoreTimes()
+ ->andReturn(
+ "From: Foo\r\n<foo@bar>\r\n".
+ "\r\n".
+ "This\r\n".
+ 'body'
+ );
+
+ if ("\r\n" != PHP_EOL) {
+ $expectedHeaders = "From: Foo\n<foo@bar>\n";
+ $expectedSubject = "Foo\nBar";
+ $expectedBody = "This\nbody";
+ } else {
+ $expectedHeaders = "From: Foo\r\n<foo@bar>\r\n";
+ $expectedSubject = "Foo\r\nBar";
+ $expectedBody = "This\r\nbody";
+ }
+
+ $invoker->shouldReceive('mail')
+ ->once()
+ ->with(\Mockery::any(), $expectedSubject, $expectedBody, $expectedHeaders, \Mockery::any());
+
+ $transport->send($message);
+ }
+
+ /**
+ * @expectedException \Swift_TransportException
+ * @expectedExceptionMessage Cannot send message without a recipient
+ */
+ public function testExceptionWhenNoRecipients()
+ {
+ $invoker = $this->_createInvoker();
+ $invoker->shouldReceive('mail');
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessage($headers);
+
+ $transport->send($message);
+ }
+
+ public function noExceptionWhenRecipientsExistProvider()
+ {
+ return array(
+ array('To'),
+ array('Cc'),
+ array('Bcc'),
+ );
+ }
+
+ /**
+ * @dataProvider noExceptionWhenRecipientsExistProvider
+ *
+ * @param string $header
+ */
+ public function testNoExceptionWhenRecipientsExist($header)
+ {
+ $invoker = $this->_createInvoker();
+ $invoker->shouldReceive('mail');
+ $dispatcher = $this->_createEventDispatcher();
+ $transport = $this->_createTransport($invoker, $dispatcher);
+
+ $headers = $this->_createHeaders();
+ $message = $this->_createMessage($headers);
+ $message->shouldReceive(sprintf('get%s', $header))->andReturn(array('foo@bar' => 'Foo'));
+
+ $transport->send($message);
+ }
+
+ private function _createTransport($invoker, $dispatcher)
+ {
+ return new Swift_Transport_MailTransport($invoker, $dispatcher);
+ }
+
+ private function _createEventDispatcher()
+ {
+ return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
+ }
+
+ private function _createInvoker()
+ {
+ return $this->getMockery('Swift_Transport_MailInvoker');
+ }
+
+ private function _createMessage($headers)
+ {
+ $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
+ $message->shouldReceive('getHeaders')
+ ->zeroOrMoreTimes()
+ ->andReturn($headers);
+
+ return $message;
+ }
+
+ private function _createMessageWithRecipient($headers, $recipient = array('foo@bar' => 'Foo'))
+ {
+ $message = $this->_createMessage($headers);
+ $message->shouldReceive('getTo')->andReturn($recipient);
+
+ return $message;
+ }
+
+ private function _createHeaders($headers = array())
+ {
+ $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();
+
+ if (count($headers) > 0) {
+ foreach ($headers as $name => $header) {
+ $set->shouldReceive('get')
+ ->zeroOrMoreTimes()
+ ->with($name)
+ ->andReturn($header);
+ $set->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->with($name)
+ ->andReturn(true);
+ }
+ }
+
+ $header = $this->_createHeader();
+ $set->shouldReceive('get')
+ ->zeroOrMoreTimes()
+ ->andReturn($header);
+ $set->shouldReceive('has')
+ ->zeroOrMoreTimes()
+ ->andReturn(true);
+
+ return $set;
+ }
+
+ private function _createHeader()
+ {
+ return $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing();
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php
new file mode 100644
index 0000000..9040f9e
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php
@@ -0,0 +1,151 @@
+<?php
+
+class Swift_Transport_SendmailTransportTest extends Swift_Transport_AbstractSmtpEventSupportTest
+{
+ protected function _getTransport($buf, $dispatcher = null, $command = '/usr/sbin/sendmail -bs')
+ {
+ if (!$dispatcher) {
+ $dispatcher = $this->_createEventDispatcher();
+ }
+ $transport = new Swift_Transport_SendmailTransport($buf, $dispatcher);
+ $transport->setCommand($command);
+
+ return $transport;
+ }
+
+ protected function _getSendmail($buf, $dispatcher = null)
+ {
+ if (!$dispatcher) {
+ $dispatcher = $this->_createEventDispatcher();
+ }
+ $sendmail = new Swift_Transport_SendmailTransport($buf, $dispatcher);
+
+ return $sendmail;
+ }
+
+ public function testCommandCanBeSetAndFetched()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getSendmail($buf);
+
+ $sendmail->setCommand('/usr/sbin/sendmail -bs');
+ $this->assertEquals('/usr/sbin/sendmail -bs', $sendmail->getCommand());
+ $sendmail->setCommand('/usr/sbin/sendmail -oi -t');
+ $this->assertEquals('/usr/sbin/sendmail -oi -t', $sendmail->getCommand());
+ }
+
+ public function testSendingMessageIn_t_ModeUsesSimplePipe()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getSendmail($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
+ $message->shouldReceive('toByteStream')
+ ->once()
+ ->with($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('terminate')
+ ->once();
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array("\r\n" => "\n", "\n." => "\n.."));
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array());
+
+ $sendmail->setCommand('/usr/sbin/sendmail -t');
+ $this->assertEquals(2, $sendmail->send($message));
+ }
+
+ public function testSendingIn_t_ModeWith_i_FlagDoesntEscapeDot()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getSendmail($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
+ $message->shouldReceive('toByteStream')
+ ->once()
+ ->with($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('terminate')
+ ->once();
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array("\r\n" => "\n"));
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array());
+
+ $sendmail->setCommand('/usr/sbin/sendmail -i -t');
+ $this->assertEquals(2, $sendmail->send($message));
+ }
+
+ public function testSendingInTModeWith_oi_FlagDoesntEscapeDot()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getSendmail($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
+ $message->shouldReceive('toByteStream')
+ ->once()
+ ->with($buf);
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('terminate')
+ ->once();
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array("\r\n" => "\n"));
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array());
+
+ $sendmail->setCommand('/usr/sbin/sendmail -oi -t');
+ $this->assertEquals(2, $sendmail->send($message));
+ }
+
+ public function testSendingMessageRegeneratesId()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getSendmail($buf);
+ $message = $this->_createMessage();
+
+ $message->shouldReceive('getTo')
+ ->zeroOrMoreTimes()
+ ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
+ $message->shouldReceive('generateId');
+ $buf->shouldReceive('initialize')
+ ->once();
+ $buf->shouldReceive('terminate')
+ ->once();
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array("\r\n" => "\n", "\n." => "\n.."));
+ $buf->shouldReceive('setWriteTranslations')
+ ->once()
+ ->with(array());
+
+ $sendmail->setCommand('/usr/sbin/sendmail -t');
+ $this->assertEquals(2, $sendmail->send($message));
+ }
+
+ public function testFluidInterface()
+ {
+ $buf = $this->_getBuffer();
+ $sendmail = $this->_getTransport($buf);
+
+ $ref = $sendmail->setCommand('/foo');
+ $this->assertEquals($ref, $sendmail);
+ }
+}
diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php
new file mode 100644
index 0000000..5109b56
--- /dev/null
+++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php
@@ -0,0 +1,43 @@
+<?php
+
+class Swift_Transport_StreamBufferTest extends \PHPUnit_Framework_TestCase
+{
+ public function testSettingWriteTranslationsCreatesFilters()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->once())
+ ->method('createFilter')
+ ->with('a', 'b')
+ ->will($this->returnCallback(array($this, '_createFilter')));
+
+ $buffer = $this->_createBuffer($factory);
+ $buffer->setWriteTranslations(array('a' => 'b'));
+ }
+
+ public function testOverridingTranslationsOnlyAddsNeededFilters()
+ {
+ $factory = $this->_createFactory();
+ $factory->expects($this->exactly(2))
+ ->method('createFilter')
+ ->will($this->returnCallback(array($this, '_createFilter')));
+
+ $buffer = $this->_createBuffer($factory);
+ $buffer->setWriteTranslations(array('a' => 'b'));
+ $buffer->setWriteTranslations(array('x' => 'y', 'a' => 'b'));
+ }
+
+ private function _createBuffer($replacementFactory)
+ {
+ return new Swift_Transport_StreamBuffer($replacementFactory);
+ }
+
+ private function _createFactory()
+ {
+ return $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock();
+ }
+
+ public function _createFilter()
+ {
+ return $this->getMockBuilder('Swift_StreamFilter')->getMock();
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/.gitignore b/vendor/symfony/event-dispatcher/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md
new file mode 100644
index 0000000..bb42ee1
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/CHANGELOG.md
@@ -0,0 +1,23 @@
+CHANGELOG
+=========
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+ object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+ same event
+ * added ImmutableEventDispatcher
diff --git a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
new file mode 100644
index 0000000..4dcede7
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
@@ -0,0 +1,183 @@
+<?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\EventDispatcher;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Lazily loads listeners and subscribers from the dependency injection
+ * container.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ */
+class ContainerAwareEventDispatcher extends EventDispatcher
+{
+ private $container;
+
+ /**
+ * The service IDs of the event listeners and subscribers.
+ */
+ private $listenerIds = array();
+
+ /**
+ * The services registered as listeners.
+ */
+ private $listeners = array();
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Adds a service as event listener.
+ *
+ * @param string $eventName Event for which the listener is added
+ * @param array $callback The service ID of the listener service & the method
+ * name that has to be called
+ * @param int $priority The higher this value, the earlier an event listener
+ * will be triggered in the chain.
+ * Defaults to 0.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addListenerService($eventName, $callback, $priority = 0)
+ {
+ if (!\is_array($callback) || 2 !== \count($callback)) {
+ throw new \InvalidArgumentException('Expected an array("service", "method") argument');
+ }
+
+ $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
+ }
+
+ public function removeListener($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as $i => $args) {
+ list($serviceId, $method) = $args;
+ $key = $serviceId.'.'.$method;
+ if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
+ unset($this->listeners[$eventName][$key]);
+ if (empty($this->listeners[$eventName])) {
+ unset($this->listeners[$eventName]);
+ }
+ unset($this->listenerIds[$eventName][$i]);
+ if (empty($this->listenerIds[$eventName])) {
+ unset($this->listenerIds[$eventName]);
+ }
+ }
+ }
+ }
+
+ parent::removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ return $this->listenerIds || $this->listeners || parent::hasListeners();
+ }
+
+ if (isset($this->listenerIds[$eventName])) {
+ return true;
+ }
+
+ return parent::hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ foreach ($this->listenerIds as $serviceEventName => $args) {
+ $this->lazyLoad($serviceEventName);
+ }
+ } else {
+ $this->lazyLoad($eventName);
+ }
+
+ return parent::getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ return parent::getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * Adds a service as event subscriber.
+ *
+ * @param string $serviceId The service ID of the subscriber service
+ * @param string $class The service's class name (which must implement EventSubscriberInterface)
+ */
+ public function addSubscriberService($serviceId, $class)
+ {
+ foreach ($class::getSubscribedEvents() as $eventName => $params) {
+ if (\is_string($params)) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params, 0);
+ } elseif (\is_string($params[0])) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ public function getContainer()
+ {
+ return $this->container;
+ }
+
+ /**
+ * Lazily loads listeners for this event from the dependency injection
+ * container.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ */
+ protected function lazyLoad($eventName)
+ {
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as $args) {
+ list($serviceId, $method, $priority) = $args;
+ $listener = $this->container->get($serviceId);
+
+ $key = $serviceId.'.'.$method;
+ if (!isset($this->listeners[$eventName][$key])) {
+ $this->addListener($eventName, array($listener, $method), $priority);
+ } elseif ($this->listeners[$eventName][$key] !== $listener) {
+ parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
+ $this->addListener($eventName, array($listener, $method), $priority);
+ }
+
+ $this->listeners[$eventName][$key] = $listener;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
new file mode 100644
index 0000000..53d7c5d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -0,0 +1,375 @@
+<?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\EventDispatcher\Debug;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class TraceableEventDispatcher implements TraceableEventDispatcherInterface
+{
+ protected $logger;
+ protected $stopwatch;
+
+ private $called;
+ private $dispatcher;
+ private $wrappedListeners;
+
+ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
+ {
+ $this->dispatcher = $dispatcher;
+ $this->stopwatch = $stopwatch;
+ $this->logger = $logger;
+ $this->called = array();
+ $this->wrappedListeners = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->dispatcher->addListener($eventName, $listener, $priority);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (isset($this->wrappedListeners[$eventName])) {
+ foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+ if ($wrappedListener->getWrappedListener() === $listener) {
+ $listener = $wrappedListener;
+ unset($this->wrappedListeners[$eventName][$index]);
+ break;
+ }
+ }
+ }
+
+ return $this->dispatcher->removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ return $this->dispatcher->removeSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ if (!method_exists($this->dispatcher, 'getListenerPriority')) {
+ return 0;
+ }
+
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ if (null !== $this->logger && $event->isPropagationStopped()) {
+ $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
+ }
+
+ $this->preProcess($eventName);
+ $this->preDispatch($eventName, $event);
+
+ $e = $this->stopwatch->start($eventName, 'section');
+
+ $this->dispatcher->dispatch($eventName, $event);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ $this->postDispatch($eventName, $event);
+ $this->postProcess($eventName);
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCalledListeners()
+ {
+ $called = array();
+ foreach ($this->called as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ $called[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+
+ return $called;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNotCalledListeners()
+ {
+ try {
+ $allListeners = $this->getListeners();
+ } catch (\Exception $e) {
+ if (null !== $this->logger) {
+ $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
+ }
+
+ // unable to retrieve the uncalled listeners
+ return array();
+ }
+
+ $notCalled = array();
+ foreach ($allListeners as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $called = false;
+ if (isset($this->called[$eventName])) {
+ foreach ($this->called[$eventName] as $l) {
+ if ($l->getWrappedListener() === $listener) {
+ $called = true;
+
+ break;
+ }
+ }
+ }
+
+ if (!$called) {
+ $info = $this->getListenerInfo($listener, $eventName);
+ $notCalled[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+ }
+
+ uasort($notCalled, array($this, 'sortListenersByPriority'));
+
+ return $notCalled;
+ }
+
+ /**
+ * Proxies all method calls to the original event dispatcher.
+ *
+ * @param string $method The method name
+ * @param array $arguments The method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, $arguments)
+ {
+ return \call_user_func_array(array($this->dispatcher, $method), $arguments);
+ }
+
+ /**
+ * Called before dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function preDispatch($eventName, Event $event)
+ {
+ }
+
+ /**
+ * Called after dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function postDispatch($eventName, Event $event)
+ {
+ }
+
+ private function preProcess($eventName)
+ {
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ $info = $this->getListenerInfo($listener, $eventName);
+ $name = isset($info['class']) ? $info['class'] : $info['type'];
+ $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
+ $this->wrappedListeners[$eventName][] = $wrappedListener;
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']);
+ }
+ }
+
+ private function postProcess($eventName)
+ {
+ unset($this->wrappedListeners[$eventName]);
+ $skipped = false;
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+ continue;
+ }
+ // Unwrap listener
+ $priority = $this->getListenerPriority($eventName, $listener);
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
+
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ if ($listener->wasCalled()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
+ }
+
+ if (!isset($this->called[$eventName])) {
+ $this->called[$eventName] = new \SplObjectStorage();
+ }
+
+ $this->called[$eventName]->attach($listener);
+ }
+
+ if (null !== $this->logger && $skipped) {
+ $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
+ }
+
+ if ($listener->stoppedPropagation()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
+ }
+
+ $skipped = true;
+ }
+ }
+ }
+
+ /**
+ * Returns information about the listener.
+ *
+ * @param object $listener The listener
+ * @param string $eventName The event name
+ *
+ * @return array Information about the listener
+ */
+ private function getListenerInfo($listener, $eventName)
+ {
+ $info = array(
+ 'event' => $eventName,
+ 'priority' => $this->getListenerPriority($eventName, $listener),
+ );
+
+ // unwrap for correct listener info
+ if ($listener instanceof WrappedListener) {
+ $listener = $listener->getWrappedListener();
+ }
+
+ if ($listener instanceof \Closure) {
+ $info += array(
+ 'type' => 'Closure',
+ 'pretty' => 'closure',
+ );
+ } elseif (\is_string($listener)) {
+ try {
+ $r = new \ReflectionFunction($listener);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Function',
+ 'function' => $listener,
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $listener,
+ );
+ } elseif (\is_array($listener) || (\is_object($listener) && \is_callable($listener))) {
+ if (!\is_array($listener)) {
+ $listener = array($listener, '__invoke');
+ }
+ $class = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0];
+ try {
+ $r = new \ReflectionMethod($class, $listener[1]);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Method',
+ 'class' => $class,
+ 'method' => $listener[1],
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $class.'::'.$listener[1],
+ );
+ }
+
+ return $info;
+ }
+
+ private function sortListenersByPriority($a, $b)
+ {
+ if (\is_int($a['priority']) && !\is_int($b['priority'])) {
+ return 1;
+ }
+
+ if (!\is_int($a['priority']) && \is_int($b['priority'])) {
+ return -1;
+ }
+
+ if ($a['priority'] === $b['priority']) {
+ return 0;
+ }
+
+ if ($a['priority'] > $b['priority']) {
+ return -1;
+ }
+
+ return 1;
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
new file mode 100644
index 0000000..5483e81
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
@@ -0,0 +1,34 @@
+<?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\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface TraceableEventDispatcherInterface extends EventDispatcherInterface
+{
+ /**
+ * Gets the called listeners.
+ *
+ * @return array An array of called listeners
+ */
+ public function getCalledListeners();
+
+ /**
+ * Gets the not called listeners.
+ *
+ * @return array An array of not called listeners
+ */
+ public function getNotCalledListeners();
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
new file mode 100644
index 0000000..1552af0
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@@ -0,0 +1,71 @@
+<?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\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class WrappedListener
+{
+ private $listener;
+ private $name;
+ private $called;
+ private $stoppedPropagation;
+ private $stopwatch;
+ private $dispatcher;
+
+ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+ {
+ $this->listener = $listener;
+ $this->name = $name;
+ $this->stopwatch = $stopwatch;
+ $this->dispatcher = $dispatcher;
+ $this->called = false;
+ $this->stoppedPropagation = false;
+ }
+
+ public function getWrappedListener()
+ {
+ return $this->listener;
+ }
+
+ public function wasCalled()
+ {
+ return $this->called;
+ }
+
+ public function stoppedPropagation()
+ {
+ return $this->stoppedPropagation;
+ }
+
+ public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+ {
+ $this->called = true;
+
+ $e = $this->stopwatch->start($this->name, 'event_listener');
+
+ \call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ if ($event->isPropagationStopped()) {
+ $this->stoppedPropagation = true;
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
new file mode 100644
index 0000000..5a94ae8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
@@ -0,0 +1,100 @@
+<?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\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+ protected $dispatcherService;
+ protected $listenerTag;
+ protected $subscriberTag;
+
+ /**
+ * @param string $dispatcherService Service name of the event dispatcher in processed container
+ * @param string $listenerTag Tag name used for listener
+ * @param string $subscriberTag Tag name used for subscribers
+ */
+ public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
+ {
+ $this->dispatcherService = $dispatcherService;
+ $this->listenerTag = $listenerTag;
+ $this->subscriberTag = $subscriberTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+ return;
+ }
+
+ $definition = $container->findDefinition($this->dispatcherService);
+
+ foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
+ }
+
+ foreach ($events as $event) {
+ $priority = isset($event['priority']) ? $event['priority'] : 0;
+
+ if (!isset($event['event'])) {
+ throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+ }
+
+ if (!isset($event['method'])) {
+ $event['method'] = 'on'.preg_replace_callback(array(
+ '/(?<=\b)[a-z]/i',
+ '/[^a-z0-9]/i',
+ ), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+ $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+ }
+
+ $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
+ }
+ }
+
+ foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
+ }
+
+ // We must assume that the class value has been correctly filled, even if the service is created by a factory
+ $class = $container->getParameterBag()->resolveValue($def->getClass());
+ $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
+
+ if (!is_subclass_of($class, $interface)) {
+ if (!class_exists($class, false)) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+
+ throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
+ }
+
+ $definition->addMethodCall('addSubscriberService', array($id, $class));
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php
new file mode 100644
index 0000000..320919a
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Event.php
@@ -0,0 +1,120 @@
+<?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\EventDispatcher;
+
+/**
+ * Event is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class Event
+{
+ /**
+ * @var bool Whether no further event listeners should be triggered
+ */
+ private $propagationStopped = false;
+
+ /**
+ * @var EventDispatcherInterface Dispatcher that dispatched this event
+ */
+ private $dispatcher;
+
+ /**
+ * @var string This event's name
+ */
+ private $name;
+
+ /**
+ * Returns whether further event listeners should be triggered.
+ *
+ * @see Event::stopPropagation()
+ *
+ * @return bool Whether propagation was already stopped for this event
+ */
+ public function isPropagationStopped()
+ {
+ return $this->propagationStopped;
+ }
+
+ /**
+ * Stops the propagation of the event to further event listeners.
+ *
+ * If multiple event listeners are connected to the same event, no
+ * further event listener will be triggered once any trigger calls
+ * stopPropagation().
+ */
+ public function stopPropagation()
+ {
+ $this->propagationStopped = true;
+ }
+
+ /**
+ * Stores the EventDispatcher that dispatches this Event.
+ *
+ * @param EventDispatcherInterface $dispatcher
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
+ */
+ public function setDispatcher(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * Returns the EventDispatcher that dispatches this Event.
+ *
+ * @return EventDispatcherInterface
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
+ */
+ public function getDispatcher()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED);
+
+ return $this->dispatcher;
+ }
+
+ /**
+ * Gets the event's name.
+ *
+ * @return string
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
+ */
+ public function getName()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED);
+
+ return $this->name;
+ }
+
+ /**
+ * Sets the event's name property.
+ *
+ * @param string $name The event name
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php
new file mode 100644
index 0000000..b41b98e
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcher.php
@@ -0,0 +1,198 @@
+<?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\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+ private $listeners = array();
+ private $sorted = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ $event->setDispatcher($this);
+ $event->setName($eventName);
+
+ if ($listeners = $this->getListeners($eventName)) {
+ $this->doDispatch($listeners, $eventName, $event);
+ }
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null !== $eventName) {
+ if (!isset($this->listeners[$eventName])) {
+ return array();
+ }
+
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+
+ return $this->sorted[$eventName];
+ }
+
+ foreach ($this->listeners as $eventName => $eventListeners) {
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+ }
+
+ return array_filter($this->sorted);
+ }
+
+ /**
+ * Gets the listener priority for a specific event.
+ *
+ * Returns null if the event or the listener does not exist.
+ *
+ * @param string $eventName The name of the event
+ * @param callable $listener The listener
+ *
+ * @return int|null The event listener priority
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== \in_array($listener, $listeners, true)) {
+ return $priority;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return (bool) $this->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->listeners[$eventName][$priority][] = $listener;
+ unset($this->sorted[$eventName]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== ($key = array_search($listener, $listeners, true))) {
+ unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_string($params)) {
+ $this->addListener($eventName, array($subscriber, $params));
+ } elseif (\is_string($params[0])) {
+ $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_array($params) && \is_array($params[0])) {
+ foreach ($params as $listener) {
+ $this->removeListener($eventName, array($subscriber, $listener[0]));
+ }
+ } else {
+ $this->removeListener($eventName, array($subscriber, \is_string($params) ? $params : $params[0]));
+ }
+ }
+ }
+
+ /**
+ * Triggers the listeners of an event.
+ *
+ * This method can be overridden to add functionality that is executed
+ * for each listener.
+ *
+ * @param callable[] $listeners The event listeners
+ * @param string $eventName The name of the event to dispatch
+ * @param Event $event The event object to pass to the event handlers/listeners
+ */
+ protected function doDispatch($listeners, $eventName, Event $event)
+ {
+ foreach ($listeners as $listener) {
+ if ($event->isPropagationStopped()) {
+ break;
+ }
+ \call_user_func($listener, $event, $eventName, $this);
+ }
+ }
+
+ /**
+ * Sorts the internal list of listeners for the given event by priority.
+ *
+ * @param string $eventName The name of the event
+ */
+ private function sortListeners($eventName)
+ {
+ krsort($this->listeners[$eventName]);
+ $this->sorted[$eventName] = \call_user_func_array('array_merge', $this->listeners[$eventName]);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
new file mode 100644
index 0000000..60160a9
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
@@ -0,0 +1,81 @@
+<?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\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventDispatcherInterface
+{
+ /**
+ * Dispatches an event to all registered listeners.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ * @param Event $event The event to pass to the event handlers/listeners
+ * If not supplied, an empty Event instance is created
+ *
+ * @return Event
+ */
+ public function dispatch($eventName, Event $event = null);
+
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param string $eventName The event to listen on
+ * @param callable $listener The listener
+ * @param int $priority The higher this value, the earlier an event
+ * listener will be triggered in the chain (defaults to 0)
+ */
+ public function addListener($eventName, $listener, $priority = 0);
+
+ /**
+ * Adds an event subscriber.
+ *
+ * The subscriber is asked for all the events he is
+ * interested in and added as a listener for these events.
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Removes an event listener from the specified events.
+ *
+ * @param string $eventName The event to remove a listener from
+ * @param callable $listener The listener to remove
+ */
+ public function removeListener($eventName, $listener);
+
+ public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Gets the listeners of a specific event or all listeners sorted by descending priority.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return array The event listeners for the specified event, or all event listeners by event name
+ */
+ public function getListeners($eventName = null);
+
+ /**
+ * Checks whether an event has any registered listeners.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return bool true if the specified event has any listeners, false otherwise
+ */
+ public function hasListeners($eventName = null);
+}
diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
new file mode 100644
index 0000000..8af7789
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
@@ -0,0 +1,46 @@
+<?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\EventDispatcher;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventSubscriberInterface
+{
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * The array keys are event names and the value can be:
+ *
+ * * The method name to call (priority defaults to 0)
+ * * An array composed of the method name to call and the priority
+ * * An array of arrays composed of the method names to call and respective
+ * priorities, or 0 if unset
+ *
+ * For instance:
+ *
+ * * array('eventName' => 'methodName')
+ * * array('eventName' => array('methodName', $priority))
+ * * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
+ *
+ * @return array The event names to listen to
+ */
+ public static function getSubscribedEvents();
+}
diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php
new file mode 100644
index 0000000..f0be7e1
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -0,0 +1,175 @@
+<?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\EventDispatcher;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+ protected $subject;
+ protected $arguments;
+
+ /**
+ * Encapsulate an event with $subject and $args.
+ *
+ * @param mixed $subject The subject of the event, usually an object or a callable
+ * @param array $arguments Arguments to store in the event
+ */
+ public function __construct($subject = null, array $arguments = array())
+ {
+ $this->subject = $subject;
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Getter for subject property.
+ *
+ * @return mixed The observer subject
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Get argument by key.
+ *
+ * @param string $key Key
+ *
+ * @return mixed Contents of array key
+ *
+ * @throws \InvalidArgumentException if key is not found
+ */
+ public function getArgument($key)
+ {
+ if ($this->hasArgument($key)) {
+ return $this->arguments[$key];
+ }
+
+ throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+ }
+
+ /**
+ * Add argument to event.
+ *
+ * @param string $key Argument name
+ * @param mixed $value Value
+ *
+ * @return $this
+ */
+ public function setArgument($key, $value)
+ {
+ $this->arguments[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Getter for all arguments.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Set args property.
+ *
+ * @param array $args Arguments
+ *
+ * @return $this
+ */
+ public function setArguments(array $args = array())
+ {
+ $this->arguments = $args;
+
+ return $this;
+ }
+
+ /**
+ * Has argument.
+ *
+ * @param string $key Key of arguments array
+ *
+ * @return bool
+ */
+ public function hasArgument($key)
+ {
+ return array_key_exists($key, $this->arguments);
+ }
+
+ /**
+ * ArrayAccess for argument getter.
+ *
+ * @param string $key Array key
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException if key does not exist in $this->args
+ */
+ public function offsetGet($key)
+ {
+ return $this->getArgument($key);
+ }
+
+ /**
+ * ArrayAccess for argument setter.
+ *
+ * @param string $key Array key to set
+ * @param mixed $value Value
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->setArgument($key, $value);
+ }
+
+ /**
+ * ArrayAccess for unset argument.
+ *
+ * @param string $key Array key
+ */
+ public function offsetUnset($key)
+ {
+ if ($this->hasArgument($key)) {
+ unset($this->arguments[$key]);
+ }
+ }
+
+ /**
+ * ArrayAccess has argument.
+ *
+ * @param string $key Array key
+ *
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->hasArgument($key);
+ }
+
+ /**
+ * IteratorAggregate for iterating over the object like an array.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->arguments);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
new file mode 100644
index 0000000..b3cf56c
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
@@ -0,0 +1,91 @@
+<?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\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+ private $dispatcher;
+
+ public function __construct(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ return $this->dispatcher->dispatch($eventName, $event);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE
new file mode 100644
index 0000000..21d7fb9
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+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.
diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md
new file mode 100644
index 0000000..185c3fe
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/README.md
@@ -0,0 +1,15 @@
+EventDispatcher Component
+=========================
+
+The EventDispatcher component provides tools that allow your application
+components to communicate with each other by dispatching events and listening to
+them.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
new file mode 100644
index 0000000..48de632
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
@@ -0,0 +1,398 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class AbstractEventDispatcherTest extends TestCase
+{
+ /* Some pseudo events */
+ const preFoo = 'pre.foo';
+ const postFoo = 'post.foo';
+ const preBar = 'pre.bar';
+ const postBar = 'post.bar';
+
+ /**
+ * @var EventDispatcher
+ */
+ private $dispatcher;
+
+ private $listener;
+
+ protected function setUp()
+ {
+ $this->dispatcher = $this->createEventDispatcher();
+ $this->listener = new TestEventListener();
+ }
+
+ protected function tearDown()
+ {
+ $this->dispatcher = null;
+ $this->listener = null;
+ }
+
+ abstract protected function createEventDispatcher();
+
+ public function testInitialState()
+ {
+ $this->assertEquals(array(), $this->dispatcher->getListeners());
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddListener()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->assertTrue($this->dispatcher->hasListeners());
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener1->name = '1';
+ $listener2->name = '2';
+ $listener3->name = '3';
+
+ $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
+ $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
+ $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
+
+ $expected = array(
+ array($listener2, 'preFoo'),
+ array($listener3, 'preFoo'),
+ array($listener1, 'preFoo'),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
+ }
+
+ public function testGetAllListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener4 = new TestEventListener();
+ $listener5 = new TestEventListener();
+ $listener6 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->addListener('post.foo', $listener4, -10);
+ $this->dispatcher->addListener('post.foo', $listener5);
+ $this->dispatcher->addListener('post.foo', $listener6, 10);
+
+ $expected = array(
+ 'pre.foo' => array($listener3, $listener2, $listener1),
+ 'post.foo' => array($listener6, $listener5, $listener4),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenerPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+
+ $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
+ $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
+ }
+
+ public function testDispatch()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertTrue($this->listener->preFooInvoked);
+ $this->assertFalse($this->listener->postFooInvoked);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
+ $event = new Event();
+ $return = $this->dispatcher->dispatch(self::preFoo, $event);
+ $this->assertSame($event, $return);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyDispatch()
+ {
+ $event = new Event();
+ $this->dispatcher->dispatch(self::preFoo, $event);
+ $this->assertEquals('pre.foo', $event->getName());
+ }
+
+ public function testDispatchForClosure()
+ {
+ $invoked = 0;
+ $listener = function () use (&$invoked) {
+ ++$invoked;
+ };
+ $this->dispatcher->addListener('pre.foo', $listener);
+ $this->dispatcher->addListener('post.foo', $listener);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(1, $invoked);
+ }
+
+ public function testStopEventPropagation()
+ {
+ $otherListener = new TestEventListener();
+
+ // postFoo() stops the propagation, so only one listener should
+ // be executed
+ // Manually set priority to enforce $this->listener to be called first
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
+ $this->dispatcher->addListener('post.foo', array($otherListener, 'postFoo'));
+ $this->dispatcher->dispatch(self::postFoo);
+ $this->assertTrue($this->listener->postFooInvoked);
+ $this->assertFalse($otherListener->postFooInvoked);
+ }
+
+ public function testDispatchByPriority()
+ {
+ $invoked = array();
+ $listener1 = function () use (&$invoked) {
+ $invoked[] = '1';
+ };
+ $listener2 = function () use (&$invoked) {
+ $invoked[] = '2';
+ };
+ $listener3 = function () use (&$invoked) {
+ $invoked[] = '3';
+ };
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(array('3', '2', '1'), $invoked);
+ }
+
+ public function testRemoveListener()
+ {
+ $this->dispatcher->addListener('pre.bar', $this->listener);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('pre.bar', $this->listener);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('notExists', $this->listener);
+ }
+
+ public function testAddSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
+ }
+
+ public function testAddSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertEquals('preFoo2', $listeners[0][1]);
+ }
+
+ public function testRemoveSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testRemoveSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ public function testRemoveSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyEventReceivesTheDispatcherInstance()
+ {
+ $dispatcher = null;
+ $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) {
+ $dispatcher = $event->getDispatcher();
+ });
+ $this->dispatcher->dispatch('test');
+ $this->assertSame($this->dispatcher, $dispatcher);
+ }
+
+ public function testEventReceivesTheDispatcherInstanceAsArgument()
+ {
+ $listener = new TestWithDispatcher();
+ $this->dispatcher->addListener('test', array($listener, 'foo'));
+ $this->assertNull($listener->name);
+ $this->assertNull($listener->dispatcher);
+ $this->dispatcher->dispatch('test');
+ $this->assertEquals('test', $listener->name);
+ $this->assertSame($this->dispatcher, $listener->dispatcher);
+ }
+
+ /**
+ * @see https://bugs.php.net/bug.php?id=62976
+ *
+ * This bug affects:
+ * - The PHP 5.3 branch for versions < 5.3.18
+ * - The PHP 5.4 branch for versions < 5.4.8
+ * - The PHP 5.5 branch is not affected
+ */
+ public function testWorkaroundForPhpBug62976()
+ {
+ $dispatcher = $this->createEventDispatcher();
+ $dispatcher->addListener('bug.62976', new CallableClass());
+ $dispatcher->removeListener('bug.62976', function () {});
+ $this->assertTrue($dispatcher->hasListeners('bug.62976'));
+ }
+
+ public function testHasListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+
+ public function testGetListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertSame(array(), $this->dispatcher->getListeners());
+ }
+
+ public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
+ {
+ $this->assertFalse($this->dispatcher->hasListeners('foo'));
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+}
+
+class CallableClass
+{
+ public function __invoke()
+ {
+ }
+}
+
+class TestEventListener
+{
+ public $preFooInvoked = false;
+ public $postFooInvoked = false;
+
+ /* Listener methods */
+
+ public function preFoo(Event $e)
+ {
+ $this->preFooInvoked = true;
+ }
+
+ public function postFoo(Event $e)
+ {
+ $this->postFooInvoked = true;
+
+ $e->stopPropagation();
+ }
+}
+
+class TestWithDispatcher
+{
+ public $name;
+ public $dispatcher;
+
+ public function foo(Event $e, $name, $dispatcher)
+ {
+ $this->name = $name;
+ $this->dispatcher = $dispatcher;
+ }
+}
+
+class TestEventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
+ }
+}
+
+class TestEventSubscriberWithPriorities implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'pre.foo' => array('preFoo', 10),
+ 'post.foo' => array('postFoo'),
+ );
+ }
+}
+
+class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => array(
+ array('preFoo1'),
+ array('preFoo2', 10),
+ ));
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
new file mode 100644
index 0000000..224a292
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
@@ -0,0 +1,277 @@
+<?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\EventDispatcher\Tests;
+
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Scope;
+use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ $container = new Container();
+
+ return new ContainerAwareEventDispatcher($container);
+ }
+
+ public function testAddAListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ public function testAddASubscriberService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('onEventWithPriority')
+ ->with($event)
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('onEventNested')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.subscriber', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
+
+ $dispatcher->dispatch('onEvent', $event);
+ $dispatcher->dispatch('onEventWithPriority', $event);
+ $dispatcher->dispatch('onEventNested', $event);
+ }
+
+ public function testPreventDuplicateListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @group legacy
+ */
+ public function testTriggerAListenerServiceOutOfScope()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $scope = new Scope('scope');
+ $container = new Container();
+ $container->addScope($scope);
+ $container->enterScope('scope');
+
+ $container->set('service.listener', $service, 'scope');
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $container->leaveScope('scope');
+ $dispatcher->dispatch('onEvent');
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testReEnteringAScope()
+ {
+ $event = new Event();
+
+ $service1 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service1
+ ->expects($this->exactly(2))
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $scope = new Scope('scope');
+ $container = new Container();
+ $container->addScope($scope);
+ $container->enterScope('scope');
+
+ $container->set('service.listener', $service1, 'scope');
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+ $dispatcher->dispatch('onEvent', $event);
+
+ $service2 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service2
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container->enterScope('scope');
+ $container->set('service.listener', $service2, 'scope');
+
+ $dispatcher->dispatch('onEvent', $event);
+
+ $container->leaveScope('scope');
+
+ $dispatcher->dispatch('onEvent');
+ }
+
+ public function testHasListenersOnLazyLoad()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $event->setDispatcher($dispatcher);
+ $event->setName('onEvent');
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $this->assertTrue($dispatcher->hasListeners());
+
+ if ($dispatcher->hasListeners('onEvent')) {
+ $dispatcher->dispatch('onEvent');
+ }
+ }
+
+ public function testGetListenersOnLazyLoad()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $listeners = $dispatcher->getListeners();
+
+ $this->assertArrayHasKey('onEvent', $listeners);
+
+ $this->assertCount(1, $dispatcher->getListeners('onEvent'));
+ }
+
+ public function testRemoveAfterDispatch()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', new Event());
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+
+ public function testRemoveBeforeDispatch()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+}
+
+class Service
+{
+ public function onEvent(Event $e)
+ {
+ }
+}
+
+class SubscriberService implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'onEvent' => 'onEvent',
+ 'onEventWithPriority' => array('onEventWithPriority', 10),
+ 'onEventNested' => array(array('onEventNested')),
+ );
+ }
+
+ public function onEvent(Event $e)
+ {
+ }
+
+ public function onEventWithPriority(Event $e)
+ {
+ }
+
+ public function onEventNested(Event $e)
+ {
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
new file mode 100644
index 0000000..ffdda38
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
@@ -0,0 +1,254 @@
+<?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\EventDispatcher\Tests\Debug;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+class TraceableEventDispatcherTest extends TestCase
+{
+ public function testAddRemoveListener()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame($listener, $listeners[0]);
+
+ $tdispatcher->removeListener('foo', $listener);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ public function testGetListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
+ }
+
+ public function testHasListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $this->assertFalse($dispatcher->hasListeners('foo'));
+ $this->assertFalse($tdispatcher->hasListeners('foo'));
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertTrue($dispatcher->hasListeners('foo'));
+ $this->assertTrue($tdispatcher->hasListeners('foo'));
+ }
+
+ public function testGetListenerPriority()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', function () {}, 123);
+
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+
+ // Verify that priority is preserved when listener is removed and re-added
+ // in preProcess() and postProcess().
+ $tdispatcher->dispatch('foo', new Event());
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+ }
+
+ public function testGetListenerPriorityReturnsZeroWhenWrappedMethodDoesNotExist()
+ {
+ $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+ $traceableEventDispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $traceableEventDispatcher->addListener('foo', function () {}, 123);
+ $listeners = $traceableEventDispatcher->getListeners('foo');
+
+ $this->assertSame(0, $traceableEventDispatcher->getListenerPriority('foo', $listeners[0]));
+ }
+
+ public function testAddRemoveSubscriber()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $subscriber = new EventSubscriber();
+
+ $tdispatcher->addSubscriber($subscriber);
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame(array($subscriber, 'call'), $listeners[0]);
+
+ $tdispatcher->removeSubscriber($subscriber);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ /**
+ * @dataProvider isWrappedDataProvider
+ *
+ * @param bool $isWrapped
+ */
+ public function testGetCalledListeners($isWrapped)
+ {
+ $dispatcher = new EventDispatcher();
+ $stopWatch = new Stopwatch();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, $stopWatch);
+
+ $listener = function () {};
+ if ($isWrapped) {
+ $listener = new WrappedListener($listener, 'foo', $stopWatch, $dispatcher);
+ }
+
+ $tdispatcher->addListener('foo', $listener, 5);
+
+ $this->assertEquals(array(), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getNotCalledListeners());
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
+ }
+
+ public function isWrappedDataProvider()
+ {
+ return array(
+ array(false),
+ array(true),
+ );
+ }
+
+ public function testGetCalledListenersNested()
+ {
+ $tdispatcher = null;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
+ $tdispatcher = $dispatcher;
+ $dispatcher->dispatch('bar');
+ });
+ $dispatcher->addListener('bar', function (Event $event) {});
+ $dispatcher->dispatch('foo');
+ $this->assertSame($dispatcher, $tdispatcher);
+ $this->assertCount(2, $dispatcher->getCalledListeners());
+ }
+
+ public function testLogger()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function () {});
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testLoggerWithStoppedEvent()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".');
+ $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testDispatchCallListeners()
+ {
+ $called = array();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
+ $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertSame(array('foo2', 'foo1'), $called);
+ }
+
+ public function testDispatchNested()
+ {
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $loop = 1;
+ $dispatchedEvents = 0;
+ $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
+ ++$loop;
+ if (2 == $loop) {
+ $dispatcher->dispatch('foo');
+ }
+ });
+ $dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
+ ++$dispatchedEvents;
+ });
+
+ $dispatcher->dispatch('foo');
+
+ $this->assertSame(2, $dispatchedEvents);
+ }
+
+ public function testDispatchReusedEventNested()
+ {
+ $nestedCall = false;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
+ $dispatcher->dispatch('bar', $e);
+ });
+ $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
+ $nestedCall = true;
+ });
+
+ $this->assertFalse($nestedCall);
+ $dispatcher->dispatch('foo');
+ $this->assertTrue($nestedCall);
+ }
+
+ public function testListenerCanRemoveItselfWhenExecuted()
+ {
+ $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
+ $dispatcher->removeListener('foo', $listener1);
+ };
+ $eventDispatcher->addListener('foo', $listener1);
+ $eventDispatcher->addListener('foo', function () {});
+ $eventDispatcher->dispatch('foo');
+
+ $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
+ }
+}
+
+class EventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('foo' => 'call');
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
new file mode 100644
index 0000000..7490d0d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
@@ -0,0 +1,154 @@
+<?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\EventDispatcher\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+
+class RegisterListenersPassTest extends TestCase
+{
+ /**
+ * Tests that event subscribers not implementing EventSubscriberInterface
+ * trigger an exception.
+ *
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEventSubscriberWithoutInterface()
+ {
+ $builder = new ContainerBuilder();
+ $builder->register('event_dispatcher');
+ $builder->register('my_event_subscriber', 'stdClass')
+ ->addTag('kernel.event_subscriber');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+ }
+
+ public function testValidEventSubscriber()
+ {
+ $services = array(
+ 'my_event_subscriber' => array(0 => array()),
+ );
+
+ $builder = new ContainerBuilder();
+ $eventDispatcherDefinition = $builder->register('event_dispatcher');
+ $builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')
+ ->addTag('kernel.event_subscriber');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+
+ $this->assertEquals(array(array('addSubscriberService', array('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'))), $eventDispatcherDefinition->getMethodCalls());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded.
+ */
+ public function testPrivateEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded.
+ */
+ public function testPrivateEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded.
+ */
+ public function testAbstractEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded.
+ */
+ public function testAbstractEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ public function testEventSubscriberResolvableClassName()
+ {
+ $container = new ContainerBuilder();
+
+ $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+
+ $definition = $container->getDefinition('event_dispatcher');
+ $expected_calls = array(
+ array(
+ 'addSubscriberService',
+ array(
+ 'foo',
+ 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService',
+ ),
+ ),
+ );
+ $this->assertSame($expected_calls, $definition->getMethodCalls());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
+ */
+ public function testEventSubscriberUnresolvableClassName()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+}
+
+class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
new file mode 100644
index 0000000..5faa5c8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
@@ -0,0 +1,22 @@
+<?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\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class EventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ return new EventDispatcher();
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Tests/EventTest.php
new file mode 100644
index 0000000..bdc14ab
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventTest.php
@@ -0,0 +1,97 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Test class for Event.
+ */
+class EventTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\EventDispatcher\Event
+ */
+ protected $event;
+
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventDispatcher
+ */
+ protected $dispatcher;
+
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->event = new Event();
+ $this->dispatcher = new EventDispatcher();
+ }
+
+ /**
+ * Tears down the fixture, for example, closes a network connection.
+ * This method is called after a test is executed.
+ */
+ protected function tearDown()
+ {
+ $this->event = null;
+ $this->dispatcher = null;
+ }
+
+ public function testIsPropagationStopped()
+ {
+ $this->assertFalse($this->event->isPropagationStopped());
+ }
+
+ public function testStopPropagationAndIsPropagationStopped()
+ {
+ $this->event->stopPropagation();
+ $this->assertTrue($this->event->isPropagationStopped());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacySetDispatcher()
+ {
+ $this->event->setDispatcher($this->dispatcher);
+ $this->assertSame($this->dispatcher, $this->event->getDispatcher());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyGetDispatcher()
+ {
+ $this->assertNull($this->event->getDispatcher());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyGetName()
+ {
+ $this->assertNull($this->event->getName());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacySetName()
+ {
+ $this->event->setName('foo');
+ $this->assertEquals('foo', $this->event->getName());
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
new file mode 100644
index 0000000..b63f69d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
@@ -0,0 +1,136 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Test class for Event.
+ */
+class GenericEventTest extends TestCase
+{
+ /**
+ * @var GenericEvent
+ */
+ private $event;
+
+ private $subject;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ $this->subject = new \stdClass();
+ $this->event = new GenericEvent($this->subject, array('name' => 'Event'));
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->subject = null;
+ $this->event = null;
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
+ }
+
+ /**
+ * Tests Event->getArgs().
+ */
+ public function testGetArguments()
+ {
+ // test getting all
+ $this->assertSame(array('name' => 'Event'), $this->event->getArguments());
+ }
+
+ public function testSetArguments()
+ {
+ $result = $this->event->setArguments(array('foo' => 'bar'));
+ $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
+ $this->assertSame($this->event, $result);
+ }
+
+ public function testSetArgument()
+ {
+ $result = $this->event->setArgument('foo2', 'bar2');
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ $this->assertEquals($this->event, $result);
+ }
+
+ public function testGetArgument()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event->getArgument('name'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetArgException()
+ {
+ $this->event->getArgument('nameNotExist');
+ }
+
+ public function testOffsetGet()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event['name']);
+
+ // test getting invalid arg
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
+ $this->assertFalse($this->event['nameNotExist']);
+ }
+
+ public function testOffsetSet()
+ {
+ $this->event['foo2'] = 'bar2';
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ }
+
+ public function testOffsetUnset()
+ {
+ unset($this->event['name']);
+ $this->assertAttributeSame(array(), 'arguments', $this->event);
+ }
+
+ public function testOffsetIsset()
+ {
+ $this->assertArrayHasKey('name', $this->event);
+ $this->assertArrayNotHasKey('nameNotExist', $this->event);
+ }
+
+ public function testHasArgument()
+ {
+ $this->assertTrue($this->event->hasArgument('name'));
+ $this->assertFalse($this->event->hasArgument('nameNotExist'));
+ }
+
+ public function testGetSubject()
+ {
+ $this->assertSame($this->subject, $this->event->getSubject());
+ }
+
+ public function testHasIterator()
+ {
+ $data = array();
+ foreach ($this->event as $key => $value) {
+ $data[$key] = $value;
+ }
+ $this->assertEquals(array('name' => 'Event'), $data);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
new file mode 100644
index 0000000..04f2861
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
@@ -0,0 +1,106 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcherTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $innerDispatcher;
+
+ /**
+ * @var ImmutableEventDispatcher
+ */
+ private $dispatcher;
+
+ protected function setUp()
+ {
+ $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+ $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
+ }
+
+ public function testDispatchDelegates()
+ {
+ $event = new Event();
+
+ $this->innerDispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with('event', $event)
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->dispatch('event', $event));
+ }
+
+ public function testGetListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('getListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->getListeners('event'));
+ }
+
+ public function testHasListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('hasListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->hasListeners('event'));
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddListenerDisallowed()
+ {
+ $this->dispatcher->addListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddSubscriberDisallowed()
+ {
+ $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveListenerDisallowed()
+ {
+ $this->dispatcher->removeListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveSubscriberDisallowed()
+ {
+ $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+ $this->dispatcher->removeSubscriber($subscriber);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json
new file mode 100644
index 0000000..14fc24b
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "symfony/event-dispatcher",
+ "type": "library",
+ "description": "Symfony EventDispatcher 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.3.9"
+ },
+ "require-dev": {
+ "symfony/dependency-injection": "~2.6|~3.0.0",
+ "symfony/expression-language": "~2.6|~3.0.0",
+ "symfony/config": "^2.0.5|~3.0.0",
+ "symfony/stopwatch": "~2.3|~3.0.0",
+ "psr/log": "~1.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/phpunit.xml.dist b/vendor/symfony/event-dispatcher/phpunit.xml.dist
new file mode 100644
index 0000000..f2eb169
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony EventDispatcher Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>